Create Collections for SCCM Software Update Installation Failures by Error Code

Recently I published a blog about creating collections for SCCM client installation failures by error code. In this post, I will do the same for Software Update installation failures.

If you’re lucky enough not to have any errors installing software updates with SCCM, then this post isn’t for you, but if you do experience installation failures it can be helpful to collate machines with the same error into collections so you can easily target them for remediation using the SCCM Scripts feature for example, or just for visibility and reporting.

To find which software update installation errors you are experiencing in your environment, you can run the following SQL query against the SCCM database. This will find systems in the “Error” or “Unknown” enforcement states for software update deployments and group them by the enforcement error code.

Select Count(ResourceID),LastEnforcementErrorCode
from vSMS_SUMDeploymentStatusPerAsset 
where StatusType in (4,5)
and LastEnforcementErrorCode is not null
Group by LastEnforcementErrorCode

Next is a PowerShell script that will create collections for each error code. You need to specify the error codes in the Error Code translation table in the script. I’ve included some common error codes for software updates and their friendly descriptions – add or remove error codes according to your own environment. To translate error codes to friendly descriptions, see here. Run the script on a site server or anywhere with the SCCM console installed.

I’ve split the collections between those with an “error” enforcement state and those with “unknown” as you may wish to handle them separately, and placed the collections for each state in different sub-folders.

You may wish to be more targeted in the WQL query for the collection rule, targeting only certain collections or deployments etc. For example, you can add a ‘where’ clause for SUM.CollectionName to target particular collections, or SUM.AssignmentName to target specific SUG deployments.

Here’s what the end result will look like. The error description is added to the Comment field, so just add that in the console view.

##########################################################################
## Script to create collections for Software Update installation errors ##
##########################################################################
<#
Find SUP error codes in your environment (SQL):
"
Select Count(ResourceID),LastEnforcementErrorCode
from vSMS_SUMDeploymentStatusPerAsset
where StatusType in (4,5)
and LastEnforcementErrorCode is not null
Group by LastEnforcementErrorCode
"
#>
###############
## VARIABLES ##
###############
# Limiting collection
$LimitingCollection = "All Systems"
# Folders to place the collections in (must exist)
$ErrorFolder = "Devicecollection\SUP\SUP Errors\Enforcement State Error"
$UnknownFolder = "Devicecollection\SUP\SUP Errors\Enforcement State Unknown"
# Error Code Translation table
$ErrorCodes = @{
0 = 'Success'
-2016409844 = 'Software update execution timeout'
-2016409966 = 'Group policy conflict'
-2016410008 = 'Software update still detected as actionable after apply'
-2016410012 = 'Updates handler job was cancelled'
-2016410026 = 'Updates handler was unable to continue due to some generic internal error'
-2016410031 = 'Post install scan failed'
-2016410032 = 'Pre install scan failed'
-2016410855 = 'Unknown error'
-2016411012 = 'CI documents download timed out'
-2016411115 = 'Item not found'
-2145107951 = 'WUServer policy value is missing in the registry.'
-2145120257 = 'An operation failed due to reasons not covered by another error code.'
-2145123272 = 'There is no route or network connectivity to the endpoint.'
-2145124320 = 'Operation did not complete because there is no logged-on interactive user.'
-2145124341 = 'Operation was cancelled.'
-2146498304 = 'Unknown error'
-2146762496 = 'No signature was present in the subject.'
-2146889721 = 'The hash value is not correct.'
-2147010798 = 'The component store has been corrupted.'
-2147010815 = 'The referenced assembly could not be found.'
-2147010893 = 'The referenced assembly is not installed on your system.'
-2147018095 = 'Transaction support within the specified resource manager is not started or was shut down due to an error.'
-2147021879 = 'The requested operation failed. A system reboot is required to roll back changes made.'
-2147023436 = 'This operation returned because the timeout period expired.'
-2147023728 = 'Element not found.'
-2147023890 = 'The volume for a file has been externally altered so that the opened file is no longer valid.'
-2147024598 = 'Too many posts were made to a semaphore.'
-2147024784 = 'There is not enough space on the disk.'
-2147217865 = 'Unknown error'
-2147467259 = 'Unspecified error'
-2147467260 = 'Operation aborted'
}
#################
## MAIN SCRIPT ##
#################
# Import ConfigMgr Module
Import-Module $env:SMS_ADMIN_UI_PATH.Replace('i386','ConfigurationManager.psd1')
$SiteCode = (Get-PSDrive PSProvider CMSITE).Name
Set-Location ("$SiteCode" + ":")
# Create collections for each error code in the error code table
Foreach ($ErrorCode in $ErrorCodes.Keys)
{
## Create collections for the "error" enforcement state
# Set Target folder location
$TargetFolder = "$SiteCode" + ":\" + $ErrorFolder
# Set WQL Queries
$Query = "select SYS.ResourceID,SYS.ResourceType,SYS.Name,SYS.SMSUniqueIdentifier,SYS.ResourceDomainORWorkgroup,SYS.Client from SMS_R_System as SYS Inner Join SMS_SUMDeploymentAssetDetails as SUM on SYS.ResourceID = SUM.ResourceID WHERE SUM.StatusType = 5 and SUM.LastEnforcementErrorCode = $ErrorCode"
# Create Collection
Write-host "Creating collection: '[Enforcement State: Error] Code $ErrorCode"
$Collection = New-CMDeviceCollection LimitingCollectionName $LimitingCollection Name "[Enforcement State: Error] Code $ErrorCode" Comment "$($ErrorCodes[$ErrorCode])" RefreshType Periodic RefreshSchedule (Convert-CMSchedule ScheduleString "920A8C0000100008")
Add-CMDeviceCollectionQueryMembershipRule CollectionName $Collection.Name QueryExpression $Query RuleName "$($Collection.Name)"
$Collection | Move-CMObject FolderPath $TargetFolder
## Create collections for the "unknown" enforcement state
# Set Target folder location
$TargetFolder = "$SiteCode" + ":\" + $UnknownFolder
# Set WQL Queries
$Query = "select SYS.ResourceID,SYS.ResourceType,SYS.Name,SYS.SMSUniqueIdentifier,SYS.ResourceDomainORWorkgroup,SYS.Client from SMS_R_System as SYS Inner Join SMS_SUMDeploymentAssetDetails as SUM on SYS.ResourceID = SUM.ResourceID WHERE SUM.StatusType = 4 and SUM.LastEnforcementErrorCode = $ErrorCode"
# Create Collection
Write-host "Creating collection: '[Enforcement State: Unknown] Code $ErrorCode"
$Collection = New-CMDeviceCollection LimitingCollectionName $LimitingCollection Name "[Enforcement State: Unknown] Code $ErrorCode" Comment "$($ErrorCodes[$ErrorCode])" RefreshType Periodic RefreshSchedule (Convert-CMSchedule ScheduleString "920A8C0000100008")
Add-CMDeviceCollectionQueryMembershipRule CollectionName $Collection.Name QueryExpression $Query RuleName "$($Collection.Name)"
$Collection | Move-CMObject FolderPath $TargetFolder
}

Create Collections for SCCM Client Installation Failures by Error Code

Ok, so in a perfect SCCM world you would never have any SCCM client installation failures and this post would be totally unnecessary. But in the real world, you are very likely to have a number of systems that fail to install the SCCM client and the reasons can be many.

To identify such systems, it can be helpful to create collections for some of the common client installation failure codes so you can easily see and report on which type of installation failures you are experiencing and the number of systems affected.

To identify the installation failure error codes you have in your environment for Windows systems, run the following SQL query against the SCCM database:

select 
	Count(cdr.Name) as 'Count',
	cdr.CP_LastInstallationError as 'Last Installation Error Code'
from v_CombinedDeviceResources cdr
where
	cdr.CP_LastInstallationError is not null
	and cdr.IsClient = 0
	and cdr.DeviceOS like '%Windows%'
group by cdr.CP_LastInstallationError
order by 'Count' desc
Client installation error counts

Next simply create a collection for each error code using the following WQL query, changing the LastInstallationError value to the relevant error code:

select 
    SYS.ResourceID,
    SYS.ResourceType,
    SYS.Name,
    SYS.SMSUniqueIdentifier,
    SYS.ResourceDomainORWorkgroup,
    SYS.Client 
from SMS_R_System as SYS 
Inner Join SMS_CM_RES_COLL_SMS00001 as COL on SYS.ResourceID = COL.ResourceID  
Where COL.LastInstallationError = 53 
And (SYS.Client = 0  Or SYS.Client is null)

Error codes are all fine and dandy, but unless you have an error code database in your head you’ll want to translate those codes to friendly descriptions. To do that, I use a PowerShell function I created that pulls the description from the SrsResources.dll which you can find in any SCCM console installation. There’s more than one way to translate error codes though – see my blog post here. Better yet, create yourself an error code SQL database which you can join to in your SQL queries and is super useful for reporting purposes – see my post here.

Anyway, once you’ve translated the error codes, you can name your collections with them for easy reference:

Client installation failure collections

Now comes the hard part – figuring out how to fix those errors and working through all the affected systems 😬

Monitoring Changes to Active Directory Sites and Subnets with PowerShell

If you work with SCCM and you use AD Forest Discovery to automatically create boundaries from AD Sites or Subnets, you know how important it is for AD to stay up to date with the current information. And when something is changed in Sites or Subnets, you need to be made aware of it so you can reflect the change in your SCCM boundaries and boundary groups. Unfortunately, communication between IT teams is not always what it should be, so I wrote this script to run as a scheduled task and keep an eye on any changes made in AD Sites and IP subnets.

The script works by retrieving the current site and subnet information and exporting it to cache files. The next time the script runs, it will compare the current information with the information in the cached files, and if anything has changed, a report will be sent by email detailing the changes.

It’s one way of ensuring you’re keeping SCCM in sync with your AD!

######################################################################################
## ##
## This script compares the current list of AD sites and subnets with a cached list ##
## If anything has changed, the cached list will be updated and the changes emailed ##
## ##
######################################################################################
################
## PARAMETERS ##
################
# Location of cache files
$ADSitesFile = "G:\Scheduled Task Scripts\Cache Files\ADSites.csv"
$ADSubnetsFile = "G:\Scheduled Task Scripts\Cache Files\ADSubnets.csv"
# Email parameters
$EmailParams = @{
smtpserver = "contoso-com.mail.protection.outlook.com"
To = "SCCMAdmins@contoso.com"
From = "SCCMReports@contoso.com"
Subject = "Active Directory Site and Subnet Changes"
}
# Html CSS style
$Style = @"
<style>
table {
border-collapse: collapse;
}
td, th {
border: 1px solid #ddd;
padding: 8px;
}
th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
background-color: #4286f4;
color: white;
}
</style>
"@
#################
## MAIN SCRIPT ##
#################
# ArrayLists to hold the data
$ADSites = [System.Collections.ArrayList]::new()
$ADSubnets = [System.Collections.ArrayList]::new()
# Retrieve the list of AD sites for the current forest
$Sites = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites
# Retrieve the AD subnets for each AD site and convert to a custom object
foreach ($ADSite in $Sites)
{
[void]$ADSites.Add(
[PSCustomObject]@{
'AD Site' = $ADSite.Name
}
)
Foreach ($Subnet in $ADSite.Subnets)
{
[void]$ADSubnets.Add(
[pscustomobject]@{
Name = $Subnet.Name
Site = $Subnet.Site
Location = $Subnet.Location
}
)
}
}
# Test whether the cached lists exist, if not create them assuming first run
If (!(Test-Path $ADSitesFile))
{
$ADSites | Sort 'AD Site' | Export-Csv Path $ADSitesFile NoTypeInformation Force
}
If (!(Test-Path $ADSubnetsFile))
{
$ADSubnets | Sort Name | Export-Csv Path $ADSubnetsFile NoTypeInformation Force
}
# Load in the cached lists
$ADSitesCached = Import-Csv Path $ADSitesFile
$ADSubnetsCached = Import-Csv Path $ADSubnetsFile
# More ArrayLists to hold the data
$ADSitesAdded = [System.Collections.ArrayList]::new()
$ADSitesRemoved = [System.Collections.ArrayList]::new()
$ADSubnetsAdded = [System.Collections.ArrayList]::new()
$ADSubnetsRemoved = [System.Collections.ArrayList]::new()
$ADSubnetsModified = [System.Collections.ArrayList]::new()
# New AD sites
Foreach ($Item in $ADSites)
{
If($Item.'AD Site' -notin $ADSitesCached.'AD Site')
{
[void]$ADSitesAdded.Add($Item)
}
}
# Removed AD Sites
Foreach ($Item in $ADSitesCached)
{
If($Item.'AD Site' -notin $ADSites.'AD Site')
{
[void]$ADSitesRemoved.Add($Item)
}
}
# IP subnet where AD Site has changed, or new IP subnet added
Foreach ($Item in $ADSubnets)
{
$Sub = $ADSubnetsCached.Where({$_.Name -eq $Item.Name})
If ($Sub)
{
If ($Sub.Site -ne $Item.Site)
{
[void]$ADSubnetsModified.Add(
[PSCustomObject]@{
Name = $Item.Name
OldSite = $Sub.Site
NewSite = $Item.Site
}
)
}
}
Else
{
[void]$ADSubnetsAdded.Add($Item)
}
}
# IP subnet removed
Foreach ($Item in $ADSubnetsCached)
{
$Sub = $ADSubnets.Where({$_.Name -eq $Item.Name})
If ($Sub){}
Else
{
[void]$ADSubnetsRemoved.Add($Item)
}
}
# Prepare the HTML
If ($ADSitesAdded.Count -ge 1)
{
$HTML1 = $ADSitesAdded |
ConvertTo-Html Head $Style Property 'AD Site' Body "<h2>The following AD Sites have been ADDED in Active Directory</h2>" CssUri "http://www.w3schools.com/lib/w3.css" |
Out-String
}
If ($ADSitesRemoved.Count -ge 1)
{
$HTML2 = $ADSitesRemoved |
ConvertTo-Html Head $Style Property 'AD Site' Body "<h2>The following AD Sites have been REMOVED from Active Directory</h2>" CssUri "http://www.w3schools.com/lib/w3.css" |
Out-String
}
If ($ADSubnetsAdded.Count -ge 1)
{
$HTML3 = $ADSubnetsAdded |
ConvertTo-Html Head $Style Property Name,Site,Location Body "<h2>The following IP Subnets have been ADDED in Active Directory</h2>" CssUri "http://www.w3schools.com/lib/w3.css" |
Out-String
}
If ($ADSubnetsRemoved.Count -ge 1)
{
$HTML4 = $ADSubnetsRemoved |
ConvertTo-Html Head $Style Property Name,Site,Location Body "<h2>The following IP Subnets have been REMOVED from Active Directory</h2>" CssUri "http://www.w3schools.com/lib/w3.css" |
Out-String
}
If ($ADSubnetsModified.Count -ge 1)
{
$HTML5 = $ADSubnetsModified |
ConvertTo-Html Head $Style Property Name,OldSite,NewSite Body "<h2>The AD Site for the following IP Subnets has been MODIFIED in Active Directory</h2>" CssUri "http://www.w3schools.com/lib/w3.css" |
Out-String
}
$HTML = $HTML1 + $HTML2 + $HTML3 + $HTML4 + $HTML5
# Send the email report and update the cached lists if required
If ($HTML.Length -ge 1)
{
Try
{
Send-MailMessage Body $HTML @EmailParams BodyAsHtml Priority High ErrorAction Stop
}
Catch
{
$_
Break
}
$ADSites | Sort 'AD Site' | Export-Csv Path $ADSitesFile NoTypeInformation Force
$ADSubnets | Sort Name | Export-Csv Path $ADSubnetsFile NoTypeInformation Force
}

New Tool: Delivery Optimization Monitor

Delivery Optimization Monitor is a tool for viewing Delivery Optimization data on the local or a remote PC.

It is based on the built-in Delivery Optimization UI in Windows 10 but allows you to view data graphically from remote computers as well.

The tool uses the Delivery Optimization PowerShell cmdlets built in to Windows 10 to retrieve and display DO data, including stats and charts for the current month, performance snapshot data and data on any current DO jobs.

Requirements

  • A supported version of Windows 10 (1703 onward)
  • PowerShell 5 minimum
  • .Net Framework 4.6.2 minimum
  • PS Remoting enabled to view data from remote computers.

This WPF tool is coded in Xaml and PowerShell and uses the MahApps.Metro and LiveCharts open source libraries.

Download

Download the tool from the Technet Gallery.

Use

To use the tool, extract the ZIP file, right-click the Delivery Optimization Monitor.ps1 and run with PowerShell.

To run against the local machine, you must run the tool elevated. To do so, create a shortcut to the ps1 file. Edit the properties of the shortcut and change the target to read:

PowerShell.exe -ExecutionPolicy Bypass -File “<pathtoPS1file>”

Right-click the shortcut and run as administrator, or edit the shortcut properties (under Advanced) to run as administrator.

For completeness, you can also change the icon of the shortcut to the icon file included in the bin directory.

Delivery Optimization Statistics

There are 3 tabs – the first displays DO data for the current month together with charts for download and upload statistics.

The second tab displays PerfSnap data and the third displays any current DO jobs.

Shout Out

Shout out to Kevin Rahetilahy over at dev4sys.com for blogging about LiveCharts in PowerShell.

Source Code

Source code can be found on GitHub.