Using wsusscn2.cab to find missing WSUS updates and extending the ConfigMgr inventory with the result

By | February 4, 2024

If you are patching your Windows machines with ConfigMgr and WSUS, you rely on the selection that you make in the product catalog: The Windows Update Agent will only search updates for products that you selected. Unfortunately, the list has a long history and there’s no documentation available. If you select products that are not in the field the performance of IIS and the WSUS database suffers and the scan time on the clients increases. If you miss checkboxes, you put the security of your organization at stake.

A way to make a validated decision is to scan your machines against the wsusscn2.cab. Microsoft provides frequent updates of the file (usually on the 2nd Tuesday in month) and sample code to initiate a scan [1].

In this article, I’ll describe a solution to run the scan, write the result to a WMI class and to include it in the SCCM inventory. The result will be displayed in the Resource Explorer or can be queried by WMI or SQL.

The basics

To collect the relevant information, the script will create and fill a WMI class with the following information:

  • kbNumber of the missing patch
  • cab signature timestamp. This becomes importance if you want to run the scan more than once. In that case you can distinguish between old and fresh scan results.
  • Category – to which category does the patch belong to? See if it is check-marked in your product catalog.
  • Last change time. When was the patch released or last updated?
  • Title

The catalog contains several other fields but we want to keep the inventory small.

We’ll deploy the script and the wsusscn2.cab by a package. A solution based on an application or a compliance baseline is possible but harder to implement.

About the target collection: You may deploy to selected machines or to all servers and workstations to get a complete picture and find machines where the update process is broken. The only consideration is the available bandwidth: While I’m writing these words, the size of the wsusscn2.cab is 599MB. Not an issue for most networks. The additional size of the hardware inventory can be neglected.

Step 1: Download the sources

We’ll start by downloading the wsusscn2.cab:

https://catalog.s.download.windowsupdate.com/microsoftupdate/v6/wsusscan/wsusscn2.cab

Place the file in a folder and add the PowerShell script you find on Github or at the end of this page to it.

Your source folder should look like this:

Next, let’s update the script.

Step 2: Setting the cab signature timestamp entry in the script

The entry should be set to enable a tracking of the release date of the wsusscn2.cab that was used. Right-click the cab and open the Digital Signatures tab. Enter the date in line 12 of the script. The format doesn’t matter, in WMI it’s string formatted.

 

You may wonder why the script doesn’t simply read the date. This turned out to be difficult, the only way I found was by using signtool.exe [3].

Step 3: Create the ConfigMgr package

In the ConfigMgr console under Software -> Packages, select Add Package. In the wizard, enter the general information:

Click next.

Under Choose the program type that you want to create select Standard Program:

Name: RunScan
Command line: powershell -executionpolicy BYPASS -noprofile .\WSUSOfflineScan.ps1

Run: Hidden

Program can run: Whether or not a user is logged on

Click next.

Under Specify the requirements for this standard program set the following values:

Estimated disc space: 800 MB

Maximum allowed run time (minutes): 30

Finish the wizard

Step 4: Deploy the package

Right-click the package and select Deploy.

Select the proper Collection. 

Under Content, select the appropriate distribution points or distribution point group.

Under Schedule, set a time and set the rerun behaviour: In case you want to run the scan multiple times, don’t miss to set Always rerun program.

Finally, click through the other pages of the wizard to finish the deployment.

Step 5: Run the package and extend the hardware inventory

Next, run the package on one of the targeted machines. The scan will take around 5 minutes. You can follow the steps if you open the log file in %windir%\Logs\WSUSOfflineCatalog.log

After the scan is finished, you might want to explore the result in WMI: Open wbemtest and connect to root\cimv2.

Next, click on Enumerate classes and select recursive. In the list of classes, scroll down to MissingWSUSUpdates. Open Instances to see the kbNumbers of the patches that are missing. If you double-click a kbNumber, you’ll get the stored information.

With the class registered on one machine, we can extend the ConfigMgr hardware inventory. In the ConfigMgr console, open Client Settings and right-click Default Client Settings.

In the Default Settings select Hardware Inventory. Press the Classes Button

In the Hardware Inventory Classes dialog, press Add

In the Add Hardware Inventory Class dialog, connect to a machine where the package ran successfully and select the class MissingWSUSUpdates from the list.

Tip: You might want to include the class Quick Fix Engineering as well. It will list all installed updates without any additional effort.

In background, SQL tables and views for fresh and historic data will be created and ConfigMgr will start to collect data after some time. 

Step 6: Create a report

To get a consolidated view of the result, a report is helpful. You find a simple report that lists the missing updates and it’s number in the Github repository)

The report is based on the query

select wu.kbNumber0 as [kbNumber], count(kbNumber0) as [# Missing], wu.Title0 as [Title], Categories0 as [Categories], MsrcSeverity0 as [Severity] from v_GS_MISSINGWSUSUPDATES as wu
group by wu.kbNumber0, wu.Title0, wu.Categories0, wu.MsrcSeverity0

Finally, here’s the code for a WQL query that lists all missing updates:

select SMS_R_System.Name, SMS_G_System_MISSINGWSUSUPDATES.kbNumber, SMS_G_System_MISSINGWSUSUPDATES.Title, SMS_G_System_MISSINGWSUSUPDATES.Categories from SMS_R_System inner join SMS_G_System_MISSINGWSUSUPDATES on SMS_G_System_MISSINGWSUSUPDATES.ResourceId = SMS_R_System.ResourceId where SMS_G_System_MISSINGWSUSUPDATES.kbNumber like "%"

Some Remarks:

  • In case you want to run the scan manually or use a different deployment methods the WMI class probably doesn’t make sense. You could just uncomment the line “#$Updates | Export-Csv -Path $env:windir\Logs\MissingWSUSUpdates.csv -Force” and remove the function call CreateMissingWSUSUpdatesWMIClass to modify the output.
  • Note that the WMI class will not be updated unless you rerun the package. In case you don’t align the schedule with the hardware inventory cycle the result will become inaccurate.
  • If no patches are missing the entry MissingWSUSUpdates will not appear in Resource Explorer.

Script Code:

#———————————————————————–
# WSUSOfflineScan
# Last Update Feb 01, 2024
#———————————————————————–

$Version = ‘Version 1.0.3’
$CabPath = “$PSScriptRoot\wsusscn2.cab”
$logFile = “$env:windir\Logs\WSUSOfflineCatalog.log”
#Important: Read the digital signature date from the WSUSScn2.cab and update this date below.
# To get the signature timestamp: Right-click the cab, open the Digital Signature tab and click on detail
$WSUSScn2SignatureDate = “09.01.2024”

#—————— Begin Functions ————————————
function Log([string]$ContentLog)
{
Write-Host “$(Get-Date -Format “dd.MM.yyyy HH:mm:ss,ff”) $($ContentLog)”
Add-Content -Path $logFile -Value “$(Get-Date -Format “dd.MM.yyyy HH:mm:ss,ff”) $($ContentLog)”
}

# Creates a new class in WMI to store our data
function CreateMissingWSUSUpdatesWMIClass()
{
$newClass = New-Object System.Management.ManagementClass(“root\cimv2”, [String]::Empty, $null);

$newClass[“__CLASS”] = “MissingWSUSUpdates”;

$newClass.Qualifiers.Add(“Static”, $true)
$newClass.Properties.Add(“kbNumber”, [System.Management.CimType]::String, $false)
$newClass.Properties[“kbNumber”].Qualifiers.Add(“key”, $true)
$newClass.Properties[“kbNumber”].Qualifiers.Add(“read”, $true)
$newClass.Properties.Add(“Title”, [System.Management.CimType]::String, $false)
$newClass.Properties[“Title”].Qualifiers.Add(“read”, $true)
$newClass.Properties.Add(“MsrcSeverity”, [System.Management.CimType]::String, $false)
$newClass.Properties[“MsrcSeverity”].Qualifiers.Add(“read”, $true)
$newClass.Properties.Add(“Categories”, [System.Management.CimType]::String, $false)
$newClass.Properties[“Categories”].Qualifiers.Add(“read”, $true)
$newClass.Properties.Add(“LastChangeTime”, [System.Management.CimType]::String, $false)
$newClass.Properties[“LastChangeTime”].Qualifiers.Add(“read”, $true)
$newClass.Properties.Add(“CabSignatureTimestamp”, [System.Management.CimType]::String, $false)
$newClass.Properties[“CabSignatureTimestamp”].Qualifiers.Add(“read”, $true)
$newClass.Put()
}

#—————— End Functions ————————————–

if(Test-Path $logFile){Remove-Item $logfile}

Log “Stating Script $Version”

# Check if cab exists
if(!(Test-Path $CabPath))
{
Log “Error: Can’t find wsusscn2.cab at $CabPath”
exit 1
}
else{
$cabLastWriteTime = ((Get-Item -Path $CabPath).LastWriteTime)
}

Log “Creating Windows Update session”
$UpdateSession = New-Object -ComObject Microsoft.Update.Session
$UpdateServiceManager = New-Object -ComObject Microsoft.Update.ServiceManager

$UpdateService = $UpdateServiceManager.AddScanPackageService(“Offline Sync Service”, $CabPath, 1)

Log “Creating Windows Update Searcher”
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
$UpdateSearcher.ServerSelection = 3
$UpdateSearcher.ServiceID = $UpdateService.ServiceID.ToString()

Log “Searching for missing updates”
$SearchResult = $UpdateSearcher.Search(“IsInstalled=0”)

$Updates = $SearchResult.Updates
#Optional: Output as csv
#$Updates | Export-Csv -Path $env:windir\Logs\MissingWSUSUpdates.csv -Force

Log (([string]($Updates.Count)) + ” updates missing”)

#——————
$UpdatesSummary = If ($searchresult.Updates.Count -gt 0)
{
#Updates that are missing
$count = $searchresult.Updates.Count

For ($i=0; $i -lt $Count; $i++)
{
$Update = $searchresult.Updates.Item($i)

[pscustomobject]@{
Title = $Update.Title
KB = $($Update.KBArticleIDs)
Severity = $($Update.MsrcSeverity)
Categories = [string](($Update.Categories | Select-Object -ExpandProperty Name )-join ‘; ‘)
LastChangeTime = $Update.LastDeploymentChangeTime.ToString(“MM/dd/yyyy”)
CabSignatureTimestamp = $WSUSScn2SignatureDate
}
}
}
else
{
Log “No missing updates found”
}

Log “Checking whether we already created our custom WMI class MissingWSUSUpdates on this PC, if not, we’ll do”
[void](Get-WMIObject MissingWSUSUpdates -ErrorAction SilentlyContinue -ErrorVariable wmiclasserror)
if ($wmiclasserror)
{
try { CreateMissingWSUSUpdatesWMIClass }
catch
{
Log “Could not create WMI class”
Exit 1
}
}
else{
Log “WMI Class MissingWSUSUpdates created”
}

Log “Clearing WMI”
Get-WmiObject MissingWSUSUpdates | Remove-WmiObject

Log “Storing the missing updates information in WMI”
for ($i=0; $i -lt $UpdatesSummary.Count; $i++)
{
[void](Set-WmiInstance -Path \\.\root\cimv2:MissingWSUSUpdates -Arguments @{kbNumber=$UpdatesSummary[$i].KB; Title=$UpdatesSummary[$i].Title; `
MsrcSeverity=$UpdatesSummary[$i].MsrcSeverity; Categories=$UpdatesSummary[$i].Categories; LastChangeTime=$UpdatesSummary[$i].LastChangeTime; CabSignatureTimestamp = $UpdatesSummary[$i].CabSignatureTimestamp})
}
Log “Open wbemtest, connect to root\cimv2 and run ‘select * from MissingWSUSUpdates’ to check the WMI.”

Log “Finishing”

Links

[1] Using WUA to Scan for Updates Offline

https://learn.microsoft.com/en-us/windows/win32/wua_sdk/using-wua-to-scan-for-updates-offline?tabs=vbscript

https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/Wua_Sdk/using-wua-to-scan-for-updates-offline.md

[2] Custom WMI Classes and reporting into SCCM 2012

https://sccmshenanigans.blogspot.com/2013/11/custom-wmi-classes-and-reporting-into.html

[3] SignTool

https://learn.microsoft.com/en-us/windows/win32/seccrypto/signtool

Leave a Reply

Your email address will not be published. Required fields are marked *