Overview
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.
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.
Redistribute Script
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
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 = ""
How does the script work?
Well you need to understand that there are two main WMI classes that we need to work with:
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.
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:
- Gets a collection of all SMS_DistributionDPStatus objects where the MessageState is greater than 2.
- Extracts the PackageID and DP server name for each of these objects.
- Gets a collection of all SMS_DistributionPoint objects where the PackageID and DP match from our SMS_DistributionDPStatus collection.
- 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.