Checking for Failed Package Distributions in ConfigMgr with PowerShell

Recently when I checked the status of my Content distributions in the Monitoring node of the ConfigMgr console, I noticed some where the compliance was not reporting as 100%.  This means there was a failure in the distribution which needs to be addressed, usually by redistributing. However, ConfigMgr does not notify you of such distributions failures, which means you have to check in the console, or the status messages or the log files to find out.  It will retry a distribution up to 100 times, but after that I guess it just remains in a failed state.

So I wrote a Powershell script that will check for failed distributions, and give me various info such as the package name, type and ID, the distribution point where the failure occurred and the reason for the failure.  It can be run in a PS console window, but can also send results by email, which makes it useful to be run regularly as a scheduled task, so you can monitor for distribution failures and take the necessary action to remedy them.

Here’s an example:

1

After running the script I can see that there are some distributions where the content hash failed to be validated, not an uncommon error.  A redistribution should fix that.  There is also an OS Image package that looks like it couldn’t talk to the distribution point, and given the date it’s given up trying.

If I choose the -SendEmail switch, it will send me a nice (?) neon-green HTML report 🙂

2

Even though it is designed for failed distributions, it will also report on distributions that are active, basically anything not yet in the successful state, so you could use it to view the progress of current distributions too.

For example, this time using the -GridView switch, I can see some Software Update Packages that are currently being distributed, and some that failed.  Since the failures are very recent, ConfigMgr will try to redistribute those packages.  If they remain in the failed state over time, then it needs investigating.

4

Configure the Script

All parameters in the script are non-mandatory, so set the defaults if you don’t want to enter them each time:

$SiteCode – Your ConfigMgr Site Code

$SiteServer – Your ConfigMgr Site Server

$From – the From email address

$To – the To email address

$Smtpserver – Your Smtp server name

$Subject – The email subject

The Script

<#

.SYNOPSIS
    Checks for failed or incomplete package distributions in ConfigMgr

.DESCRIPTION
    This script checks for any package distributions that are not in the successful state, and return the results either into the console window or by email in an HTML table.
    The following fields are returned:
    - Package Name
    - PackageID
    - Package Type
    - Distribution Point Name
    - Distribution State
    - Status Message
    - Last Update Time
    It can be run as a scheduled task to monitor for failed distributions.  Data is retrieved from WMI on the Site Server.

.PARAMETER SiteCode
    Your ConfigMgr SiteCode

.PARAMETER SiteServer
    Your ConfigMgr Site Server

.PARAMETER SendEmail
    Use this switch to send results by email.  Console output is the default.

.PARAMETER GridView
    Use this switch to output results to Powershell's GridView. Console output is the default.

.PARAMETER From
    If sending email, the From address

.PARAMETER To
    If sending email, the To address

.PARAMETER SmtpServer
    If sending email, the Smtp server name

.PARAMETER Subject
    If sending email, the email subject

.EXAMPLE
    .\Get-CMFailedDistributions.ps1
    Checks for unsuccesful package distributions and returns the results to the console window.

.EXAMPLE
    .\Get-CMFailedDistributions.ps1 -SendEmail
    Checks for unsuccesful package distributions and sends the results by email using the default parameters.

.EXAMPLE
    .\Get-CMFailedDistributions.ps1 -SiteCode XYZ -SiteServer sccmserver02 -SendEmail -To bill.gates@microsoft.com
    Checks for unsuccesful package distributions and sends the results by email using specified parameters.

.NOTES
    Script name: Get-CMFailedDistributions.ps1
    Author:      Trevor Jones
    Contact:     @trevor_smsagent
    DateCreated: 2015-05-12
    Link:        https://smsagent.wordpress.com
    Credits:     MessageIDs from Dan Richings http://www.danrichings.com/?p=166

#>

[CmdletBinding()]
    param
        (
        [Parameter(Mandatory=$False)]
            [string]$SiteCode = "ABC",
        [Parameter(Mandatory=$False)]
            [string]$SiteServer = "sccmserver-01",
        [parameter(Mandatory=$False)]
            [switch]$SendEmail,
        [parameter(Mandatory=$False)]
            [switch]$GridView,
        [parameter(Mandatory=$False)]
            [string]$From = "ConfigMgr@contoso.com",
        [parameter(Mandatory=$False)]
            [string]$To = "ConfigMgr_Admins@contoso.com",
        [parameter(Mandatory=$False)]
            [string]$SmtpServer = "mysmtpserver",
        [parameter(Mandatory=$False)]
            [string]$Subject = "ConfigMgr Failed Distributions"
        )

#region Functions
function New-Table (
$Title,
$Topic1,
$Topic2,
$Topic3,
$Topic4,
$Topic5,
$Topic6,
$Topic7
)
    {
       Add-Content $msgfile "<style>table {border-collapse: collapse;font-family: ""Trebuchet MS"", Arial, Helvetica, sans-serif;}"
       Add-Content $msgfile "h2 {font-family: ""Trebuchet MS"", Arial, Helvetica, sans-serif;}"
       Add-Content $msgfile "th, td {font-size: 1em;border: 1px solid #1FE093;padding: 3px 7px 2px 7px;}"
       Add-Content $msgfile "th {font-size: 1.2em;text-align: left;padding-top: 5px;padding-bottom: 4px;background-color: #1FE093;color: #ffffff;}</style>"
       Add-Content $msgfile "<h2>$Title</h2>"
       Add-Content $msgfile "<p><table>"
       Add-Content $msgfile "<tr><th>$Topic1</th><th>$Topic2</th><th>$Topic3</th><th>$Topic4</th><th>$Topic5</th><th>$Topic6</th><th>$Topic7</th></tr>"
    }
function New-TableRow (
$col1,
$col2,
$col3,
$col4,
$col5,
$col6,
$col7
)
    {
        Add-Content $msgfile "<tr><td>$col1</td><td>$col2</td><td>$col3</td><td>$col4</td><td>$col5</td><td>$col6</td><td>$col7</td></tr>"
    }
function New-TableEnd {
    Add-Content $msgfile "</table></p>"}
#endregion

# Set WQL Query
$packages = @()
$Query = "
Select PackageID,Name,MessageState,ObjectTypeID,MessageID,LastUpdateDate
from SMS_DistributionDPStatus
where MessageState<>1
order by LastUpdateDate Desc
"

# Get unsuccessful distributions
$Distributions = Get-WmiObject -ComputerName $SiteServer -Namespace root\SMS\site_$SiteCode -Query $Query
foreach ($Distribution in $Distributions)
    {
        # Translate Package Type
        if ($Distribution.ObjectTypeID -eq "2")
            {$Type = "Standard Package"}
        if ($Distribution.ObjectTypeID -eq "14")
            {$Type = "OS Install Package"}
        if ($Distribution.ObjectTypeID -eq "18")
            {$Type = "OS Image Package"}
        if ($Distribution.ObjectTypeID -eq "19")
            {$Type = "Boot Image Package"}
        if ($Distribution.ObjectTypeID -eq "21")
            {$Type = "Device Setting Package"}
        if ($Distribution.ObjectTypeID -eq "23")
            {$Type = "Driver Package"}
        if ($Distribution.ObjectTypeID -eq "24")
            {$Type = "Software Updates Package"}
        if ($Distribution.ObjectTypeID -eq "31")
            {$Type = "Application Content Package"}

        # Translate Distribution State
        if ($Distribution.MessageState -eq "2")
            {$Status = "In Progress"}
        if ($Distribution.MessageState -eq "3")
            {$Status = "Error"}
        if ($Distribution.MessageState -eq "4")
            {$Status = "Failed"}

        # Translate Common MessageIDs
        if ($Distribution.MessageID -eq "2303")
            {$MSGID = "Content was successfully refreshed"}
        if ($Distribution.MessageID -eq "2324")
            {$MSGID = "Failed to access or create the content share"}
        if ($Distribution.MessageID -eq "2330")
            {$MSGID = "Content was distributed to distribution point"}
        if ($Distribution.MessageID -eq "2384")
            {$MSGID = "Content hash has been successfully verified"}
        if ($Distribution.MessageID -eq "2323")
            {$MSGID = "Failed to initialize NAL"}
        if ($Distribution.MessageID -eq "2354")
            {$MSGID = "Failed to validate content status file"}
        if ($Distribution.MessageID -eq "2357")
            {$MSGID = "Content transfer manager was instructed to send content to the distribution point"}
        if ($Distribution.MessageID -eq "2360")
            {$MSGID = "Status message 2360 unknown"}
        if ($Distribution.MessageID -eq "2370")
            {$MSGID = "Failed to install distribution point"}
        if ($Distribution.MessageID -eq "2371")
            {$MSGID = "Waiting for prestaged content"}
        if ($Distribution.MessageID -eq "2372")
            {$MSGID = "Waiting for content"}
        if ($Distribution.MessageID -eq "2375")
            {$MSGID = "Created virtual directories on the defined share or volume on the distribution point succesfully"}
        if ($Distribution.MessageID -eq "2380")
            {$MSGID = "Content evaluation has started"}
        if ($Distribution.MessageID -eq "2381")
            {$MSGID = "An evaluation task is running. Content was added to the queue"}
        if ($Distribution.MessageID -eq "2382")
            {$MSGID = "Content hash is invalid"}
        if ($Distribution.MessageID -eq "2383")
            {$MSGID = "Failed to validate the package on the distribution point. The package may not be present or may be corrupt. Please redistribute it"}
        if ($Distribution.MessageID -eq "2384")
            {$MSGID = "Package has been successfully verified on the distribution point"}
        if ($Distribution.MessageID -eq "2388")
            {$MSGID = "Failed to retrieve the package list on the distribution point. Or the package list in content library doesn't match the one in WMI. Review smsdpmon.log for more information about this failure."}
        if ($Distribution.MessageID -eq "2391")
            {$MSGID = "Failed to connect to remote distribution point"}
        if ($Distribution.MessageID -eq "2397")
            {$MSGID = "Detail will be available after the server finishes processing the messages."}
        if ($Distribution.MessageID -eq "2398")
            {$MSGID = "Content Status not found"}
        if ($Distribution.MessageID -eq "2399")
            {$MSGID = "Successfully completed the installation or upgrade of the distribution point"}
        if ($Distribution.MessageID -eq "8203")
            {$MSGID = "Failed to update package"}
        if ($Distribution.MessageID -eq "8204")
            {$MSGID = "Content is being distributed to the distribution point"}
        if ($Distribution.MessageID -eq "8211")
            {$MSGID = "Package Transfer Manager failed to update the package on the distribution point. Review PkgXferMgr.log for more information about this failure."}

        # Get / Set Additional info
        $PKGID = $Distribution.PackageID
        $LastUPDTime = [System.Management.ManagementDateTimeconverter]::ToDateTime($Distribution.LastUpdateDate)
        $PKG = Get-WmiObject -ComputerName $SiteServer -Namespace root\SMS\site_$SiteCode -Query "Select * from SMS_PackageBaseclass where PackageID = '$PKGID'"

        # Add to a PS object
        $Package = New-Object psobject
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Package Name" -Value $PKG.Name
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "PackageID" -Value $Distribution.PackageID
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Package Type" -Value $Type
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Distribution Point Name" -Value $Distribution.Name
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Distribution State" -Value $Status
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Status Message" -Value $MSGID
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Last Update Time" -Value $LastUPDTime
        $packages += $package
    }

if ($SendEmail)
    {
        # Create Tempfile to store data
        $msgfile = "$env:TEMP\mailmessage.txt"
        New-Item $msgfile -ItemType file -Force | Out-Null

        # Populate HTML table
        if ($packages -ne $null ) {
            $Params = @{
                Title = "ConfigMgr Failed Package Distributions"
                Topic1 = "Package Name"
                Topic2 = "PackageID"
                Topic3 = "Package Type"
                Topic4 = "Distribution Point Name"
                Topic5 = "Distribution State"
                Topic6 = "Status Message"
                Topic7 = "Last Update Time"
                }
        New-Table @Params
        foreach ($item in $packages )
            {
                $params = @{
                col1 = $item.'Package Name'
                col2 = $item.PackageID
                col3 = $item.'Package Type'
                col4 = $item.'Distribution Point Name'
                col5 = $item.'Distribution State'
                col6 = $item.'Status Message'
                col7 = $item.'Last Update Time'
                }
                New-TableRow @Params
            }
        New-TableEnd

        # Send email or return results
        $mailbody = Get-Content $msgfile
        Send-MailMessage -From $From -To $To -SmtpServer $SmtpServer -Subject $Subject -Body "$mailbody" -BodyAsHtml 

        # Delete tempfile
        Remove-Item $msgfile
        }
        Else {Write-Warning "No results were returned!"}
    }

if ($GridView)
    {
        if ($packages -ne $null)
            {$packages | Out-GridView -Title "ConfigMgr Failed Package Distributions"}
        Else {Write-Warning "No results were returned!"}
    }

If (!$GridView -and !$SendEmail)
    {
        if ($packages -ne $null)
            {$packages | ft -AutoSize}
        Else {Write-Warning "No results were returned!"}
    }

SCCM Package Distribution Monitor

If you’ve installed the ConfigMgr 2012 R2 Toolkit, you’ve probably used the handy “Distribution Point Job Queue Manager” to monitor your distributions.  But you can also monitor them with Powershell if you wish to.  Here’s a simple script that will do just that.  It will identify the package ID, name and type, the distribution points that are currently receiving the distribution, when the distribution started, the total size of the package, the remaining size to be distributed, and overall progress of each individual distribution.

Note that ConfigMgr does some post-processing before the actual distribution is started, so if you try to monitor a distribution immediately after you distributed it, you might see no results.  One way to check when it’s started is to search for ‘Created package transfer job to send package … to distribution point’ in the distmgr.log on the site server.  Thinking about it, that could probably be scripted too, but that’s for another day 🙂

dp


## Variables ##
#!!Enter the sitecode here!!
$SiteCode = "ABC"

## Start of script

Import-Module 'C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
$Drive = $SiteCode + ':'
cd $Drive

cls
$Djobs = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_DistributionJob | Select PkgID,NALPath,StartTime,RemainingSize,TotalSize

if ($djobs -eq $Null)
{
Write-host "There are no active distributions!" -ForegroundColor Yellow
Write-host "If you just distributed something, try again in a couple of minutes as the server may be processing the request."
write-host "Press any key"
$HOST.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | OUT-NULL
$HOST.UI.RawUI.Flushinputbuffer()
exit
}

foreach ($Djob in $Djobs)
{
$ID = $djob.PkgID
# Get PackageName and Type

$PKG = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_Package -Filter "PackageID='$ID'" | Select Name
If ($PKG -ne $null)
{
$Name = $PKG.Name
$Type = "Standard Package"
}

$BIPKG = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_BootImagePackage -Filter "PackageID='$ID'" | Select Name
If ($BIPKG -ne $null)
{
$Name = $BIPKG.Name
$Type = "Boot Image Package"
}

$CNPKG = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_ContentPackage -Filter "PackageID='$ID'" | Select Name
If ($CNPKG -ne $null)
{
$Name = $CNPKG.Name
$Type = "Application Content Package"
}

$DRVPKG = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_DriverPackage -Filter "PackageID='$ID'" | Select Name
If ($DRVPKG -ne $null)
{
$Name = $DRVPKG.Name
$Type = "Driver Package"
}

$IMGPKG = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_ImagePackage -Filter "PackageID='$ID'" | Select Name
If ($IMGPKG -ne $null)
{
$Name = $IMGPKG.Name
$Type = "Image Package"
}

$PkgID = $Djob.PkgID
$NALPath = $Djob.NALPath
if ($Djob.StartTime -ne $null)
{
$StartTime = [System.Management.ManagementDateTimeConverter]::ToDateTime($Djob.StartTime).DateTime
}
$RemainingSize = $Djob.RemainingSize
$TotalSize = $Djob.TotalSize
if ($RemainingSize -ne $Null)
{
$percentComplete = "{0:N0}" -f (100-$RemainingSize/$TotalSize*100) + " %"
}
Else
{
$percentComplete = "Not calculated yet"
}
$RemainingSize = "{0:N0}" -f ($RemainingSize / 1MB) + " MB"
$TotalSize = "{0:N0}" -f ($TotalSize / 1MB) + " MB"

write-host ""
write-host "Package Name: $Name" -ForegroundColor Cyan
write-host "Packge Type: $Type"
Write-host "Package ID: $PkgID"
Write-host "Distribution Point: $NALPath"
Write-host "Start Time: $StartTime"
write-host "Remaining Size: $RemainingSize"
write-host "Total Size: $TotalSize"
Write-host "Percent Complete: $percentComplete"
}