Using PowerShell to Cleanup WSUS

If you’ve ever had a WSUS Server get a little bit out of control you’ll know that running the WSUS Server Cleanup Wizard can solve the problem. Running this wizard is unfortunately a manual process and it can be a long cleanup process as well because the wizard can take hours and hours to run, even over day in extreme cases! Of course it get’s worse when you need to perform the same steps for every WSUS Server you have. Thankfully you can use Windows PowerShell to automate this for you. And that’s a good idea to script this largely because you can run this wizard as a preventative maintenance as well.

So let’s quickly see how we can do this manually. Fire up the Windows Server Update Services console.

Load WSUS

 

Next, click “Options”.

Click Options

 

Then launch the “Server Cleanup Wizard”

Server Cleanup Wizard

 

And finally run the cleanup. However, one thing to be aware of, often this process will fail if you select all of the options. Based on feedback from Microsoft, I would suggest running them in this order:

WSUS Cleanup Order

So this means for the first pass, only select “Unused updates and update revisions”. When the wizard is finished, run it again and choose “Unneeded update files” only. On the third pass select the remaining options. This will ensure that the wizard shouldn’t crash and is the most reliable method.

But, if you’re a PowerShell person then here’s a script that will do the same thing: Some credit goes to the original author of this script (whoever you may be), I didn’t write the entire thing (it was sent to me)…I have modified it so that it performs 3 passes (otherwise it can fail on severely troublesome WSUS Servers).

 

# ==============================================================================================
# Name: Clean-WSUS.ps1
# Contact: Tim, www.culham.net
# Date: 16/07/2011

# Comments: Modified the original script so that it now performs the Server Cleanup in 3 passes.
# This is recommended as a single pass may fail.
Yes this works against a remote server.
# ==============================================================================================

Param($servername)
if (!$servername)
{
Write-Host -foregroundcolor:yellow “`nPlease Provide a WSUS Server to Connect to:”
Write-Host “Usage: Clean-WSUS.ps1 Servername`n`n”
exit
}

# WSUS Connection Parameters:
[String]$WSUSupdateServer = $servername
[Boolean]$useSecureConnection = $False
[Int32]$portNumber = 8530 # Change the Port to the Port your WSUS Server is configured to use

# Decline updates that have not been approved for 30 days or more, are not currently needed by any clients, and are superseded by an approved update.
[Boolean]$FirstPassSupersededUpdates = $False
# Decline updates that aren’t approved and have been expired my Microsoft.
[Boolean]$FirstPassExpiredUpdates = $False
# Delete updates that are expired and have not been approved for 30 days or more.
[Boolean]$FirstPassObsoleteUpdates = $True
# Delete older update revisions that have not been approved for 30 days or more.
[Boolean]$FirstPassCompressUpdates = $True
# Delete computers that have not contacted the server in 30 days or more.
[Boolean]$FirstPassObsoleteComputers = $False
# Delete update files that aren’t needed by updates or downstream servers.
[Boolean]$FirstPassUnneededContentFiles = $False

# Decline updates that have not been approved for 30 days or more, are not currently needed by any clients, and are superseded by an aproved update.
[Boolean]$SecondPassSupersededUpdates = $False
# Decline updates that aren’t approved and have been expired my Microsoft.
[Boolean]$SecondPassExpiredUpdates = $False
# Delete updates that are expired and have not been approved for 30 days or more.
[Boolean]$SecondPassObsoleteUpdates = $False
# Delete older update revisions that have not been approved for 30 days or more.
[Boolean]$SecondPassCompressUpdates = $False
# Delete computers that have not contacted the server in 30 days or more.
[Boolean]$SecondPassObsoleteComputers = $False
# Delete update files that aren’t needed by updates or downstream servers.
[Boolean]$SecondPassUnneededContentFiles = $True

# Decline updates that have not been approved for 30 days or more, are not currently needed by any clients, and are superseded by an approved update.
[Boolean]$ThirdPassSupersededUpdates = $True
# Decline updates that aren’t approved and have been expired my Microsoft.
[Boolean]$ThirdPassExpiredUpdates = $True
# Delete updates that are expired and have not been approved for 30 days or more.
[Boolean]$ThirdPassObsoleteUpdates = $False
# Delete older update revisions that have not been approved for 30 days or more.
[Boolean]$ThirdPassCompressUpdates = $False
# Delete computers that have not contacted the server in 30 days or more.
[Boolean]$ThirdPassObsoleteComputers = $True
# Delete update files that aren’t needed by updates or downstream servers.
[Boolean]$ThirdPassUnneededContentFiles = $False

# Load the Required .NET assembly
[void][reflection.assembly]::LoadWithPartialName(“Microsoft.UpdateServices.Administration”)

# Connect to the WSUS Server
$Wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($WSUSupdateServer,$useSecureConnection,$portNumber)

# Perform the Cleanup
$CleanupManager = $Wsus.GetCleanupManager()

#Perform First Pass on “Unused Updates and Update Revisions Only”
“Starting Pass 1 of 3: Unused Updates and Update Revisions Only”
$CleanupScope1 = New-Object Microsoft.UpdateServices.Administration.CleanupScope($FirstPassSupersededUpdates,$FirstPassExpiredUpdates,$FirstPassObsoleteUpdates,$FirstPassCompressUpdates,$FirstPassObsoleteComputers,$FirstPassUnneededContentFiles)
$CleanupManager.PerformCleanup($CleanupScope1)
“Completed Pass 1 of 3`n`n”

#Perform Second Pass on “Unneeded Update Files Only”
“Starting Pass 2 of 3: Unneeded Update Files Only”
$CleanupScope2 = New-Object Microsoft.UpdateServices.Administration.CleanupScope($SecondPassSupersededUpdates,$SecondPassExpiredUpdates,$SecondPassObsoleteUpdates,$SecondPassCompressUpdates,$SecondPassObsoleteComputers,$SecondPassUnneededContentFiles)
$CleanupManager.PerformCleanup($CleanupScope2)
“Completed Pass 2 of 3`n`n”

#Perform Third Pass on “Stale Computers, Expired Updates and Superseded Updates”
“Starting Pass 3 of 3: Stale Computers, Expired Updates and Superseded Updates”
$CleanupScope3 = New-Object Microsoft.UpdateServices.Administration.CleanupScope($ThirdPassSupersededUpdates,$ThirdPassExpiredUpdates,$ThirdPassObsoleteUpdates,$ThirdPassCompressUpdates,$ThirdPassObsoleteComputers,$ThirdPassUnneededContentFiles)
$CleanupManager.PerformCleanup($CleanupScope3)
“Completed Pass 3 of 3`n`n”

 

 

Update: An Alternative (better) Script

One of the negative things about the script shown above is that you need to run it against a server manually. That might be exactly what you want though, in which case go ahead and use it. But since writing that script, I realized that a better option would be to have a single script that runs and cleans “ALL” WSUS Servers. So, this new version might suit you better.

# ==============================================================================================
# NAME: WSUS-ServerCleanup.ps1
# AUTHOR: Tim, www.culham.net
# DATE : 04/10/2011
# VERSION: 1.0
# COMMENTS: This script will connect to the Primary WSUS Server and run the Server Cleanup.
# It will clean in 3 passes (Required as a single pass may crash a problematic WSUS Server).
# It will then get a list of the DownStream WSUS Servers and clean them.
#
# VERSION: 1.1
# COMMENTS: Added some timers so you can see what is taking so long…
# ==============================================================================================

# WSUS Connection Parameters:
[Boolean]$useSecureConnection = $False
[Int32]$portNumber = 8530
$PrimaryWSUSServer = “YOURPARENTWSUSSERVNAMEGOESHERE”
$BeginScriptTime = Get-Date

# First Pass Variables
# Decline updates that have not been approved for 30 days or more, are not currently needed by any clients, and are superseded by an approved update.
[Boolean]$FirstPassSupersededUpdates = $False
# Decline updates that aren’t approved and have been expired my Microsoft.
[Boolean]$FirstPassExpiredUpdates = $False
# Delete updates that are expired and have not been approved for 30 days or more.
[Boolean]$FirstPassObsoleteUpdates = $True
# Delete older update revisions that have not been approved for 30 days or more.
[Boolean]$FirstPassCompressUpdates = $True
# Delete computers that have not contacted the server in 30 days or more.
[Boolean]$FirstPassObsoleteComputers = $False
# Delete update files that aren’t needed by updates or downstream servers.
[Boolean]$FirstPassUnneededContentFiles = $False

# Second Pass Variables
[Boolean]$SecondPassSupersededUpdates = $False
[Boolean]$SecondPassExpiredUpdates = $False
[Boolean]$SecondPassObsoleteUpdates = $False
[Boolean]$SecondPassCompressUpdates = $False
[Boolean]$SecondPassObsoleteComputers = $False
[Boolean]$SecondPassUnneededContentFiles = $True

# Third Pass Variables
[Boolean]$ThirdPassSupersededUpdates = $True
[Boolean]$ThirdPassExpiredUpdates = $True
[Boolean]$ThirdPassObsoleteUpdates = $False
[Boolean]$ThirdPassCompressUpdates = $False
[Boolean]$ThirdPassObsoleteComputers = $True
[Boolean]$ThirdPassUnneededContentFiles = $False

# Clean the Primary WSUS Server
# Load the Required .NET assembly
[void][reflection.assembly]::LoadWithPartialName(“Microsoft.UpdateServices.Administration”)

# Connect to the Primary WSUS Server
$PrimaryWSUS = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($PrimaryWSUSServer,$useSecureConnection,$portNumber)

# Start Cleaning the Primary WSUS Server
$CleanupManager = $PrimaryWSUS.GetCleanupManager()

# Perform First Pass on “Unused Updates and Update Revisions Only”
$StartFirstPass = Get-Date
“`nBeginning WSUS Cleanup on $PrimaryWSUSServer at $StartFirstPass`n”
“Starting Pass 1 of 3: Unused Updates and Update Revisions Only”
$CleanupPrimary1 = New-Object Microsoft.UpdateServices.Administration.CleanupScope($FirstPassSupersededUpdates,$FirstPassExpiredUpdates,$FirstPassObsoleteUpdates,$FirstPassCompressUpdates,$FirstPassObsoleteComputers,$FirstPassUnneededContentFiles)
$CleanupManager.PerformCleanup($CleanupPrimary1)
$EndFirstPass = Get-Date
$TotFirstPass = $EndFirstPass-$StartFirstPass
Write-Host “Completed Pass 1 of 3 in: ” $TotFirstPass.hours “hrs” $TotFirstPass.minutes “min” $TotFirstPass.seconds “sec`n”

# Perform Second Pass on “Unneeded Update Files Only”
$StartSecondPass = Get-Date
“Starting Pass 2 of 3: Unneeded Update Files Only`n”
$CleanupPrimary2 = New-Object Microsoft.UpdateServices.Administration.CleanupScope($SecondPassSupersededUpdates,$SecondPassExpiredUpdates,$SecondPassObsoleteUpdates,$SecondPassCompressUpdates,$SecondPassObsoleteComputers,$SecondPassUnneededContentFiles)
$CleanupManager.PerformCleanup($CleanupPrimary2)
$EndSecondPass = Get-Date
$TotSecondPass = $EndSecondPass-$StartSecondPass
Write-Host “Completed Pass 2 of 3 in: ” $TotSecondPass.hours “hrs” $TotSecondPass.minutes “min” $TotSecondPass.seconds “sec`n”

# Perform Third Pass on “Stale Computers, Expired Updates and Superseded Updates
$StartThirdPass = Get-Date
“Starting Pass 3 of 3: Stale Computers, Expired Updates and Superseded Updates`n”
$CleanupPrimary3 = New-Object Microsoft.UpdateServices.Administration.CleanupScope($ThirdPassSupersededUpdates,$ThirdPassExpiredUpdates,$ThirdPassObsoleteUpdates,$ThirdPassCompressUpdates,$ThirdPassObsoleteComputers,$ThirdPassUnneededContentFiles)
$CleanupManager.PerformCleanup($CleanupPrimary3)
$EndThirdPass=Get-Date
$TotalThirdPass=$EndThirdPass-$StartThirdPass
$TotalRunTime=$EndThirdPass-$StartFirstPass
Write-Host “Completed Pass 3 of 3 in: ” $TotalThirdPass.hours “hrs” $TotalThirdPass.minutes “min” $TotalThirdPass.seconds “sec`n”
Write-Host “Total Run Time: ” $TotalRunTime.hours “hrs” $TotalRunTime.minutes “min” $TotalRunTime.seconds “sec”
“`n—————————————————————————-`n”
#

# Clean the DownStream WSUS Servers
# Get the list of all DownStream WSUS Servers
$servers = [Microsoft.UpdateServices.Administration.AdminProxy]::DownstreamServerCollection;
$servers = $PrimaryWSUS.GetDownstreamServers();
$numberofDSServers = ($servers).count
Write-Host “Identified $numberofDSServers DownStream WSUS Servers”
$servers = $servers | select -expandproperty FullDomainName
$CurrentServer = 1

# Loop through all DownStream Servers
foreach ($server in $servers)
{
# Connect to the WSUS Server
$Wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($server,$useSecureConnection,$portNumber)

# Perform Cleanup
$CleanupManager = $Wsus.GetCleanupManager()

# Provider a counter to show what Server Number we’re currently on
Write-Host “Current Server is $CurrentServer of $numberofDSServers”

# Perform First Pass on “Unused Updates and Update Revisions Only”
$StartFirstPass = Get-Date
“`nBeginning WSUS Cleanup on $server at $StartFirstPass`n”
“Starting Pass 1 of 3: Unused Updates and Update Revisions Only”
$CleanupScope1 = New-Object Microsoft.UpdateServices.Administration.CleanupScope($FirstPassSupersededUpdates,$FirstPassExpiredUpdates,$FirstPassObsoleteUpdates,$FirstPassCompressUpdates,$FirstPassObsoleteComputers,$FirstPassUnneededContentFiles)
$CleanupManager.PerformCleanup($CleanupScope1)
$EndFirstPass = Get-Date
$TotFirstPass = $EndFirstPass-$StartFirstPass
Write-Host “Completed Pass 1 of 3 in: ” $TotFirstPass.hours “hrs” $TotFirstPass.minutes “min” $TotFirstPass.seconds “sec`n”

# Perform Second Pass on “Unneeded Update Files Only”
$StartSecondPass = Get-Date
“Starting Pass 2 of 3: Unneeded Update Files Only”
$CleanupScope2 = New-Object Microsoft.UpdateServices.Administration.CleanupScope($SecondPassSupersededUpdates,$SecondPassExpiredUpdates,$SecondPassObsoleteUpdates,$SecondPassCompressUpdates,$SecondPassObsoleteComputers,$SecondPassUnneededContentFiles)
$CleanupManager.PerformCleanup($CleanupScope2)
$EndSecondPass = Get-Date
$TotSecondPass = $EndSecondPass-$StartSecondPass
Write-Host “Completed Pass 2 of 3 in: ” $TotSecondPass.hours “hrs” $TotSecondPass.minutes “min” $TotSecondPass.seconds “sec`n”

# Perform Third Pass on “Stale Computers, Expired Updates and Superseded Updates
$StartThirdPass = Get-Date
“Starting Pass 3 of 3: Stale Computers, Expired Updates and Superseded Updates”
$CleanupScope3 = New-Object Microsoft.UpdateServices.Administration.CleanupScope($ThirdPassSupersededUpdates,$ThirdPassExpiredUpdates,$ThirdPassObsoleteUpdates,$ThirdPassCompressUpdates,$ThirdPassObsoleteComputers,$ThirdPassUnneededContentFiles)
$CleanupManager.PerformCleanup($CleanupScope3)

# Display Final Runtime
$EndThirdPass=Get-Date
$TotalThirdPass=$EndThirdPass-$StartThirdPass
$TotalRunTime=$EndThirdPass-$StartFirstPass
Write-Host “Completed Pass 3 of 3 in: ” $TotalThirdPass.hours “hrs” $TotalThirdPass.minutes “min” $TotalThirdPass.seconds “sec`n”
Write-Host “Total Run Time: ” $TotalRunTime.hours “hrs” $TotalRunTime.minutes “min” $TotalRunTime.seconds “sec”
“`n—————————————————————————-`n”

# Clear Variables
Clear-Variable CleanupScope1, CleanupScope2, CleanupScope3
Clear-Variable TotFirstPass, EndFirstPass, StartFirstPass
Clear-Variable TotSecondPass, EndSecondPass, StartSecondPass
Clear-Variable TotalThirdPass, EndThirdPass, StartThirdPass
Clear-Variable TotalRunTime
$CurrentServer++
}
“`n—————————————————————————-“
$EndScriptTime = Get-Date
$TotalScriptRunTime=$EndScriptTime-$BeginScriptTime
Write-Host -ForegroundColor:Green “Script is Complete”
Write-Host “Total Run Time: days” $TotalScriptRunTime.days $TotalScriptRunTime.hours “hrs” $TotalScriptRunTime.minutes “min” $TotalScriptRunTime.seconds “sec”
“`n—————————————————————————-“;

 

So, when you run this, you’ll get output like this…

 

Current Server is 27 of 179

Beginning WSUS Cleanup on server01.mydomain.com at 10/11/2011 05:25:34

Starting Pass 1 of 3: Unused Updates and Update Revisions Only
SupersededUpdatesDeclined : 0
ExpiredUpdatesDeclined : 0
ObsoleteUpdatesDeleted : 45
UpdatesCompressed : 455
ObsoleteComputersDeleted : 0
DiskSpaceFreed : 0

Completed Pass 1 of 3 in: 0 hrs 13 min 40 sec

Starting Pass 2 of 3: Unneeded Update Files Only
SupersededUpdatesDeclined : 0
ExpiredUpdatesDeclined : 0
ObsoleteUpdatesDeleted : 0
UpdatesCompressed : 0
ObsoleteComputersDeleted : 0
DiskSpaceFreed : 135169024

Completed Pass 2 of 3 in: 0 hrs 0 min 18 sec

Starting Pass 3 of 3: Stale Computers, Expired Updates and Superseded Updates
SupersededUpdatesDeclined : 0
ExpiredUpdatesDeclined : 0
ObsoleteUpdatesDeleted : 0
UpdatesCompressed : 0
ObsoleteComputersDeleted : 0
DiskSpaceFreed : 0

Completed Pass 3 of 3 in: 0 hrs 0 min 13 sec

Total Run Time: 0 hrs 14 min 12 sec

 

Personally I think it’s a much nicer output and when you have to run this script against a lot of machines, then you’ll appreciate a bit more verbose output.

Have fun and hope it helps you!

 
Comments

Nice work Tim, I look forward to trying this. Thanks for putting all the effort in!

Tim, looks great. I have WSUS 6.3 on Windows 2012 will this work on that version? have you modified the script to send and email with the report in the details? That would be nice. I only have one WSUS server here Thanks again Tom

Hi Tom, I’ve sent you an email with the code to help you do this.

Tim

I tested the changes and have an error I sent you an email with my source code and the errors from when it ran.
I believe the first error caused the others.
The email function worked

Thanks
Tom

Tim

I have a working script now with email being sent. The only issue currently is that the statistics from the cleanup job are not reported in the email only the job time and runtime stats are reported. I sent you an email on this matter.

Thank you

Tom

After we get this fully working we can post on here for others to use?

Tim

The script runs great

Still need to get the stats in the email

Do you need me to email you my running script?

I am sure you are busy

Thanks Tom

Hi Tom,

it sounds like you’ve done some great development work to the script and I’ve been searching a lot for a script to clean-up ‘troublesome’ WSUS servers for a while. I’d really appreciate testing your fork of the script in our environment and obviously crediting you and Tim for the great work for the community!

Very best & keep up the good work!

Reuben

Very nice. Wonder if the emailing report script was ever shared out in another post or somewhere else? Would be useful to have. Thank you.

Leave a Reply