FacebooktwitterredditlinkedinmailFacebooktwitterredditlinkedinmail

Recently I came across an issue with System Center Configuration Manager (ConfigMgr) 2012 where a large number of packages and applications had failed to be distributed to a new Distribution Point (DP). There were many thousands of packages in the environment and a few hundred were showing in the error state in the console. Unfortunately, once packages get into this state then they don’t fix themselves as it appears that the ConfigMgr validation schedule only reports that there was a problem distributing – it doesn’t actually trigger a redistribution.

To fix the problem you have to identify each package that has a problem and go into its properties. Then you need to select the DPs you want to redistribute to and click Redistribute.

CM2012 Console Redistribute

You have to to do this for each package that has a problem taking care to only redistribute to the DPs that have have failures. In an environment with a few hundred problem packages with thousands of DPs this is completely impractical. What we need is a script that can do the following:

  • Identify packages that were unsuccessfully distributed.
  • Mass redistribute packages that are in the error state.
  • Only redistribute packages to DPs that need it to avoid needless processing and network traffic.
  • Optionally, limit the process to a specific DP. For example, a newly built DP was incorrectly configured, has subsequently been fixed but all the packages assigned to it have entered an error state.

The VBScript is listed below and can also be downloaded here: CM2012_DP_Redist.zip

' ****************************************************************************
'
' Copyright (c) 2013, Jonathan Bennett / AutoIt Consulting Ltd
' All rights reserved.
' http://www.autoitconsulting.com
'
' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
' ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
' WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
' DISCLAIMED. IN NO EVENT SHALL AUTOIT CONSULTING LTD BE LIABLE FOR ANY
' DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
' (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
' LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
' ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
' (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
' SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'
' ****************************************************************************
'
' Purpose:   Checks all packages assigned to a DP and redistributes any with errors.
' 
' Usage:     cscript.exe CM2012_DP_Redist.vbs
' 
' Version:   1.0.0
' 
' History:
' 1.0.0 20-Nov-2013 - Jonathan Bennett 
'       First version.
'
' ****************************************************************************

' ****************************************************************************
' Global constant and variable declarations
' ****************************************************************************

Option Explicit
 
' CHANGEABLE VARIABLES

' The name of the CAS/Primary site server
Public Const CASServerName = "CASSERVERNAME"
 
' Which DP to refresh packages for - leave this blank to check ALL DPs
Public Const DPServerName = "DPSERVERNAME"
 
' END OF CHANGABLE VARIABLES

 
Public Const wbemFlagReturnImmediately = 16
Public Const wbemFlagForwardOnly = 32
 
Dim oSWbemServices
 
' ****************************************************************************
' End declarations
' ****************************************************************************

' ****************************************************************************
' Main routine
' ****************************************************************************

' Connect to CM 2012 (CAS)
If ConnectServer(CASServerName) = False Then
    WScript.Echo "Unable to connect to server."
    WScript.Quit 1
End If
 
 
' Find all packages with a bad status
Dim queryString
Dim dpStatuses, dpStatus, serverName, packageID, packageDPs, packageDP, nalPath
 
If DPServerName = "" Then
    queryString = "SELECT Name,PackageID,MessageState FROM SMS_DistributionDPStatus WHERE MessageState > 2"
Else
    queryString = "SELECT Name,PackageID,MessageState FROM SMS_DistributionDPStatus WHERE Name LIKE '%" & DPServerName & "%' AND MessageState > 2"
End If
 
Set dpStatuses = oSWbemServices.ExecQuery(queryString,, wbemFlagForwardOnly+wbemFlagReturnImmediately)
For Each dpStatus in dpStatuses
    serverName = UCase(dpStatus.Name)
    packageID = dpStatus.PackageID
 
    queryString = "SELECT * FROM SMS_DistributionPoint WHERE PackageID = '" & packageID & "'"
    Set packageDPs = oSWbemServices.ExecQuery(queryString,, wbemFlagForwardOnly+wbemFlagReturnImmediately)
    For Each packageDP in packageDPs
        nalPath = UCase(packageDP.ServerNalPath)
 
        If InStr(1, nalPath, serverName) > 0 Then
            WScript.Echo "Redistributing " & packageID & " on " & serverName & "..."
            packageDP.RefreshNow = True
            On Error Resume Next
            packageDP.Put_
            On Error Goto 0
        End If
 
    Next
Next
 
 
WScript.Quit 0
 
 
' ****************************************************************************
' Functions
' ****************************************************************************

Function ConnectServer(ByVal serverName)
    If serverName = "" Then serverName = "."
    
    Dim oSWbemLocator : Set oSWbemLocator = CreateObject("WbemScripting.SWbemLocator")
    
    On Error Resume Next
    Set oSWbemServices = oSWbemLocator.ConnectServer(serverName, "root\sms")
    If Err.Number <> 0 Then
        ConnectServer = False
        Exit Function
    End If
    On Error Goto 0
    
    Dim ProviderLoc : Set ProviderLoc = oSWbemServices.ExecQuery("Select * FROM SMS_ProviderLocation WHERE ProviderForLocalSite='True'")
 
    Dim Location
    For Each Location In ProviderLoc
        Set oSWbemServices = oSWbemLocator.ConnectServer(Location.Machine, "root\sms\site_" + Location.SiteCode)
        Exit For
    Next
    ConnectServer = True
End Function
' ****************************************************************************
'
' Copyright (c) 2013, Jonathan Bennett / AutoIt Consulting Ltd
' All rights reserved.
' http://www.autoitconsulting.com
'
' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
' ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
' WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
' DISCLAIMED. IN NO EVENT SHALL AUTOIT CONSULTING LTD BE LIABLE FOR ANY
' DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
' (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
' LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
' ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
' (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
' SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'
' ****************************************************************************
'
' Purpose:   Checks all packages assigned to a DP and redistributes any with errors.
' 
' Usage:     cscript.exe CM2012_DP_Redist.vbs
' 
' Version:   1.0.0
' 
' History:
' 1.0.0	20-Nov-2013 - Jonathan Bennett 
'       First version.
'
' ****************************************************************************

' ****************************************************************************
' Global constant and variable declarations
' ****************************************************************************

Option Explicit

' CHANGEABLE VARIABLES

' The name of the CAS/Primary site server
Public Const CASServerName = "CASSERVERNAME"

' Which DP to refresh packages for - leave this blank to check ALL DPs
Public Const DPServerName = "DPSERVERNAME"

' END OF CHANGABLE VARIABLES


Public Const wbemFlagReturnImmediately = 16
Public Const wbemFlagForwardOnly = 32

Dim oSWbemServices

' ****************************************************************************
' End declarations
' ****************************************************************************

' ****************************************************************************
' Main routine
' ****************************************************************************

' Connect to CM 2012 (CAS)
If ConnectServer(CASServerName) = False Then
	WScript.Echo "Unable to connect to server."
	WScript.Quit 1
End If


' Find all packages with a bad status
Dim queryString
Dim dpStatuses, dpStatus, serverName, packageID, packageDPs, packageDP, nalPath

If DPServerName = "" Then
	queryString = "SELECT Name,PackageID,MessageState FROM SMS_DistributionDPStatus WHERE MessageState > 2"
Else
	queryString = "SELECT Name,PackageID,MessageState FROM SMS_DistributionDPStatus WHERE Name LIKE '%" & DPServerName & "%' AND MessageState > 2"
End If

Set dpStatuses = oSWbemServices.ExecQuery(queryString,, wbemFlagForwardOnly+wbemFlagReturnImmediately)
For Each dpStatus in dpStatuses
	serverName = UCase(dpStatus.Name)
	packageID = dpStatus.PackageID

	queryString = "SELECT * FROM SMS_DistributionPoint WHERE PackageID = '" & packageID & "'"
	Set packageDPs = oSWbemServices.ExecQuery(queryString,, wbemFlagForwardOnly+wbemFlagReturnImmediately)
	For Each packageDP in packageDPs
		nalPath = UCase(packageDP.ServerNalPath)

		If InStr(1, nalPath, serverName) > 0 Then
			WScript.Echo "Redistributing " & packageID & " on " & serverName & "..."
			packageDP.RefreshNow = True
			On Error Resume Next
			packageDP.Put_
			On Error Goto 0
		End If

	Next
Next


WScript.Quit 0


' ****************************************************************************
' Functions
' ****************************************************************************

Function ConnectServer(ByVal serverName)
	If serverName = "" Then serverName = "."
	
	Dim oSWbemLocator : Set oSWbemLocator = CreateObject("WbemScripting.SWbemLocator")
	
	On Error Resume Next
	Set oSWbemServices = oSWbemLocator.ConnectServer(serverName, "root\sms")
	If Err.Number <> 0 Then
		ConnectServer = False
		Exit Function
	End If
	On Error Goto 0
	
	Dim ProviderLoc : Set ProviderLoc = oSWbemServices.ExecQuery("Select * FROM SMS_ProviderLocation WHERE ProviderForLocalSite='True'")

	Dim Location
	For Each Location In ProviderLoc
		Set oSWbemServices = oSWbemLocator.ConnectServer(Location.Machine, "root\sms\site_" + Location.SiteCode)
		Exit For
	Next
	ConnectServer = True
End Function

At the top of the script you will want to change the CASServerName and DPServerName values to match your environment. If DPServerName is left blank then all DPs will be in scope, if a server is specified then redistributions are limited to just that DP. For example, my CAS is called server04 and I want to trigger packages on all DPs so I would use:

Public Const CASServerName = "server04"
Public Const DPServerName = ""
Public Const CASServerName = "server04"
Public Const DPServerName = ""

How does the script work?

Well you need to understand that there are two main WMI classes that we need to work with:

SMS_DistributionDPStatus

This class details the status of a package on a DP. The main properties we are interested in are Name (the DP server name), PackageID and MessageState. The MSDN article for this class defines a MessageState of 3 to indicate an error. While testing the results where that this is not correct and the correct value is 4. Just in case, our script will assume anything greater than 2 to indicate an error.

SMS_DistributionPoint 

This class represents an instance of a package assigned to a DP. It provides the RefreshNow method which triggers the redistribution of the specific package on the specific DP.

It might be surprising to learn that there is an instance of each of these classes for each package for each DP. So if you have 100 packages and 50 DPs then there are 5000 SMS_DistributionDPStatus and 5000 SMS_DistributionPoint objects. This is useful to understand if you are using a WMI browser when troubleshooting.

The script follows this process:

  1. Gets a collection of all SMS_DistributionDPStatus objects where the MessageState is greater than 2.
  2. Extracts the PackageID and DP server name for each of these objects.
  3. Gets a collection of all SMS_DistributionPoint objects where the PackageID and DP match from our SMS_DistributionDPStatus collection.
  4. Triggers the SMS_DistributionPoint.RefreshNow method for each member of the collection.

After running the script any packages that have failed will be redistributed and the progress can be seen in the normal way in the ConfigMgr console.

As the script only redistributes failed packages it is safe to rerun as often as required.

 

FacebooktwitterredditlinkedinmailFacebooktwitterredditlinkedinmail