WSUS Cleanup in an SCCM has been done before - many times but I wanted a solution that focussed purely on the built-in WSUS cleanup options and something that was simple to deploy in any SCCM environment - using an SCCM Configuration Item.

WSUS Cleanup - SCCM built-in maintenance

Windows Server Updates Services (WSUS) is the underlying service used by System Center Configuration Manager (SCCM) to perform software updates (Windows patches for software and OS) on Windows servers and workstations.

The database that WSUS uses to store metadata about all the available updates has become very large over the years, covering more and more Microsoft operating systems and software. It is highly recommended by Microsoft to perform regular WSUS database maintenance as suggested here: Complete guide to Microsoft WSUS and Configuration Manager SUP maintenance.

When WSUS is used in conjunction with SCCM, SCCM can perform some automated cleanup tasks after each WSUS sync job has completed. In SCCM 1806 this cleanup is further improved and documented here: Software Updates Maintenance. Specifically:

Starting in version 1806, the WSUS cleanup option occurs after every sync and does the following cleanup items:

The Expired updates option for WSUS servers on CAS and primary sites.

  • WSUS servers for secondary sites, don’t run WSUS cleanup for expired updates.

Configuration Manager builds a list of superseded updates from its database. The list is based on the supersedence behavior in the Software Update Point component properties.

  • The update configuration items meeting the supersedence behavior criteria are expired in the Configuration Manager console.
  • The updates are declined in WSUS for CAS and primary sites but not for secondary sites.

A cleanup for software update configuration items in the Configuration Manager database occurs every seven days and removes unneeded updates from the console.

  • This cleanup won’t remove expired updates from the Configuration Manager console if they’re currently deployed.

The document then goes on to state:

The following WSUS Server Cleanup Wizard options aren’t run on the CAS and primary sites:

  • Unused updates and update revisions
  • Computers not contacting the server
  • Unneeded update files

The WSUS Cleanup option in SCCM is exposed here (in the configuration of the root SCCM sites’ SUP properties) :
WSUS Cleanup

Unfortunately what the Microsoft documentation does not cover is the order in which to run the cleanup jobs and on which level of the WSUS hierarchy.

In an SCCM hierarchy there are usually multiple tiers of WSUS:

  1. A single root WSUS (The top tier/level) which updates from the internet.
  2. Multiple lower level WSUS servers that sync with the root level WSUS.

Note: WSUS servers can also share the same SQL database so the maintenance would only need to be run once per database, rather than once per WSUS server, not that it would harm anything by running it multiple times per database.

There are comments from those in the know that certain WSUS cleanup tasks recommended-order-for-running-cleanup-wizard-in-wsus-upstream-replica-hierarchy should only be run from the top level WSUS down, so to clarify for sure, I went ahead to see how the SCCM team had implemented WSUS cleanup in a hierarchy by analysing the wsyncmgr.log:

  1. The CAS server (*VM01*) initiates a sync on the top level WSUS - (*565):
  2. Once the WSUS sync with the Microsoft Update catalog is done, SCCM syncs its database with WSUS:
  3. Cleanup is then performed on the top tier WSUS. Cleanup in this case is marking updates as declined, this is important:
  4. At this stage, nothing has happened with the lower tier WSUS servers. The CAS now updates the Primary Sites inboxes, requesting a WSUS Sync to the top tier WSUS:
  5. So far all the above snippets are from the CAS. Now we jump to a Primary Site server. We can see the Sync has started, 1 minute after the sync and cleanup has finished on the CAS.
  6. The Sync on the Primary site completes and run its own cleanup:

So above we can see that SCCM is processing the ‘ decline updates’ part of the cleanup in the correct order - from the TOP WSUS, down.

Now to process the remaining WSUS cleanup options that Microsoft have stated SCCM doesn’t do :

  • Unused updates and update revisions
  • Computers not contacting the server
  • Unneeded update files

The Custom Cleanup script

I have taken some Powershell from @Bdam55 to cover the above three WSUS cleanup tasks and converted it to run as an SCCM Configuration Item on a schedule:

SCCM WSUS Cleanup script on Github

The actual cleanup of WSUS is nothing new, the PowerShell used here is regularly available on the internet, the cool thing is that this is a CI and is running in the SYSTEM context.

The script is designed to run against WSUS in an SCCM 1806 and above environment, as the additional WSUS Cleanup rules were introduced in this version.

Here are the benefits of this deployment method of the cleanup script, over say something like Group Policy Scheduled Tasks or BigFix scheduled script:

  • A Configuration Baseline can be targeted at a dynamic device collection containing all the SCCM site servers in the hierarchy.
  • All SCCM infrastructure maintenance is kept and controlled within SCCM.
  • No credentials are required in the script as it runs as SYSTEM on the SCCM Site Server
  • Uses the ConfigMgr PoSh module on the Site Server and then passes variables to the remote WSUS

When the script runs on the SCCM Site Server, it actually connects to each of the SUP (WSUS) servers in the site using PowerShell remoting (invoke-command) and initiates a cleanup if a sync job is no running. If the script detects it’s running on the root WSUS server, it will add a large delay in processing the cleanup, to give the lower level WSUS cleanup jobs time to finish.

Top level WSUS detection:

$TopTierWsus = Get-CMSoftwareUpdateSyncStatus | Where-Object -FilterScript {$_.WSUSSourceServer -like "*Microsoft Update*" -and $_.SiteCode -eq $SiteCode} | Select-Object -Unique -ExpandProperty WSUSServerName

As per the script now also configures the minimum values for:

  • WSUS App Pool queue length
  • WSUS App Pool memory size

WSUS IIS AppPool Variables

$WSUSSiteNameFilter = "WSUS*"  
$IISAppPoolQueueMinSize = 2000  
$IISAppPoolMemoryMinSize = 4194304  
$ISSWebAdminModuleNAme = "WebAdministration"

$IISModule = $true  
If(-not(get-module -Name $ISSWebAdminModuleNAme)){  
 Import-Module -Name $ISSWebAdminModuleNAme  
 } Catch {  
 $IISModule = $false  


$WebSite = Get-Website | Where-Object -FilterScript {$_.Name -like $WSUSSiteNameFilter}  
 If(-not($WebSite)){Add-TextToCMLog -LogFile $LogFile -Value "Could not find IIS website using filter '$WSUSSiteNameFilter'. Skipping IIS config" -Component $component -Severity 3}

$WSUSAppPoolName = $WebSite.applicationPool

$AppPool = Get-ItemProperty -Path IIS:\AppPools\$WSUSAppPoolName

# WSUS App Pool Queue Length  
 $AppPoolQueueLength = $AppPool.queueLength

If($AppPoolQueueLength -lt $IISAppPoolQueueMinSize){  
 Add-TextToCMLog -LogFile $LogFile -Value "Setting $WSUSAppPoolName IIS App Pool length to $IISAppPoolQueueMinSize" -Component $component -Severity 1  
 Set-ItemProperty -Path $AppPool.PSPath -Name queueLength -Value $IISAppPoolQueueMinSize  
 } else {  
 Add-TextToCMLog -LogFile $LogFile -Value "$WSUSAppPoolName IIS App Pool length is $AppPoolQueueLength so is already above $IISAppPoolQueueMinSize" -Component $component -Severity 1  

# WSUS App Pool Private Memory Size  
 $AppPoolMemorySize = (Get-ItemProperty -Path $AppPool.PSPath -Name recycling.periodicrestart.privateMemory).Value

If($AppPoolMemorySize -lt $IISAppPoolMemoryMinSize){  
 Add-TextToCMLog -LogFile $LogFile -Value "Setting $WSUSAppPoolName IIS App Pool memory size to $IISAppPoolMemoryMinSize" -Component $component -Severity 1  
 Set-ItemProperty -Path $AppPool.PSPath -Name recycling.periodicrestart.privateMemory -Value $IISAppPoolMemoryMinSize  
 } else {  
 Add-TextToCMLog -LogFile $LogFile -Value "$WSUSAppPoolName IIS App Pool memory is $AppPoolMemorySize so is already above $IISAppPoolMemoryMinSize" -Component $component -Severity 1  

 Add-TextToCMLog -LogFile $LogFile -Value "Could not load module $ISSWebAdminModuleNAme. Skipping IIS config" -Component $component -Severity 3  

If the SCCM hierarchy is expanded to cope with additional load, or new servers are brought online to do a side-by-side migration, the Configuration Item should kick-in and cover these off automatically.

Thanks to: @Bdam55 for the Add-TextToCMLog function and most of the WSUS cleanup script.

Any questions, feel free to reach out.