Powershell Script : WSUS Cleaner

This script is performing cleanup tasks on a WSUS (Windows Server Update Services) server. It runs the “Invoke-WsusServerCleanup” cmdlet with different parameters to clean up obsolete and unneeded updates, compress updates, and decline expired and superseded updates. The script then retrieves the name of the host and generates a timestamp. Finally, it sends an email report with the results of the cleanup using the Send-MailMessage cmdlet.

A special thank you goes out to Derek Hartman for his exceptional efforts in updating and expanding the features of this script.

<#
.Synopsis
    Uses MS Superseded Updates script to check what the superseded count is an email alert about it.
    Config has all the variables you will want to set
    Current config will email a group if the superseded count is over 250 updates
    MS Original Script: https://learn.microsoft.com/en-us/troubleshoot/mem/configmgr/update-management/decline-superseded-updates

.EXAMPLE
    .\SCCM-CheckWSUSDeclineCount.ps1

.NOTES
    Modified by: Derek Hartman
    Date: 4/6/2023

#>

#Config ------------------------
$SMTP = "TBD"
$From = "[email protected]"
$To = "[email protected]"
$WSUSServer = "TBD"
$AlertCount = "250"
# ------------------------------

Function Decline-SupersededUpdates {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)]
        [string]
        $UpdateServer,

        [Parameter(Mandatory = $true, Position = 2)]
        [int]
        $Port,

        [Parameter()]
        [switch]
        $UseSSL,

        [Parameter()]
        [switch]
        $SkipDecline,

        [Parameter()]
        [switch]
        $DeclineLastLevelOnly,

        [Parameter()]
        [int]
        $ExclusionPeriod = 0
    )

    if (-not (Test-Path -Path "$PSScriptRoot\WsusDeclineLogs"))
    {
        New-Item -Path $PSScriptRoot -Name 'WsusDeclineLogs' -ItemType Directory -Force
    }

    $file = "$PSScriptRoot\WsusDeclineLogs\WSUS_Decline_Superseded_{0:MMddyyyy_HHmm}.log" -f (Get-Date) 

    Start-Transcript -Path $file

    if ($SkipDecline -and $DeclineLastLevelOnly)
    {
        Write-Output -InputObject 'Using SkipDecline and DeclineLastLevelOnly switches together is not allowed.'
        Write-Output -InputObject ''
        return
    }

    $outSupersededList = Join-Path -Path "$PSScriptRoot\WsusDeclineLogs" -ChildPath 'SupersededUpdates.csv'
    $outSupersededListBackup = Join-Path -Path "$PSScriptRoot\WsusDeclineLogs" -ChildPath 'SupersededUpdatesBackup.csv'

    Set-Content -Value 'UpdateID, RevisionNumber, Title, KBArticle, SecurityBulletin, LastLevel' -Path $outSupersededList

    try
    {
        if ($UseSSL)
        {
            Write-Output -InputObject "Connecting to WSUS server $UpdateServer on Port $Port using SSL... "
        }
        else
        {
            Write-Output -InputObject "Connecting to WSUS server $UpdateServer on Port $Port... "
        }
    
        [reflection.assembly]::LoadWithPartialName('Microsoft.UpdateServices.Administration') | Out-Null
        $wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer($UpdateServer, $UseSSL, $Port);
    }
    catch [System.Exception] 
    {
        Write-Output -InputObject 'Failed to connect.'
        Write-Output -InputObject "Error: $($_.Exception.Message)"
        Write-Output -InputObject 'Please make sure that WSUS Admin Console is installed on this machine'
        Write-Output -InputObject ''
        $wsus = $null
    }

    if ($null -eq $wsus)
    {
        return
    }

    Write-Output -InputObject 'Connected.'

    $UpdateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope

    (Get-Date).AddMonths(-6)
    $UpdateScope.FromArrivalDate = (Get-Date).AddMonths(-6)
    $UpdateScope.ToArrivalDate = (Get-Date)

    $countAllUpdates = 0
    $countSupersededAll = 0
    $countSupersededLastLevel = 0
    $countSupersededExclusionPeriod = 0
    $countSupersededLastLevelExclusionPeriod = 0
    $countDeclined = 0

    Write-Output -InputObject 'Getting a list of all updates... '

    try
    {
        $allUpdates = $wsus.GetUpdates($UpdateScope)
    }
    catch [System.Exception]
    {
        Write-Output -InputObject 'Failed to get updates.'
        Write-Output -InputObject "Error: $($_.Exception.Message)"
        Write-Output -InputObject 'If this operation timed out, please decline the superseded updates from the WSUS Console manually.'
        Write-Output -InputObject ''
        return
    }

    Write-Output -InputObject 'Done'

    Write-Output -InputObject 'Parsing the list of updates... '
    foreach ($update in $allUpdates)
    {
        $countAllUpdates++

        if ($update.IsDeclined)
        {
            $countDeclined++
        }

        if (-not $update.IsDeclined -and $update.IsSuperseded)
        {
            $countSupersededAll++
    
            if (-not $update.HasSupersededUpdates)
            {
                $countSupersededLastLevel++
            }

            if ($update.CreationDate -lt (Get-Date).AddDays(-$ExclusionPeriod))
            {
                $countSupersededExclusionPeriod++
                if (-not $update.HasSupersededUpdates)
                {
                    $countSupersededLastLevelExclusionPeriod++
                }
            }

            "$($update.Id.UpdateId.Guid), $($update.Id.RevisionNumber), $($update.Title), $($update.KnowledgeBaseArticles), $($update.SecurityBulletins), $($update.HasSupersededUpdates)" | Out-File $outSupersededList -Append
        }
    }

    Write-Output -InputObject 'Done.'
    Write-Output -InputObject "List of superseded updates: $outSupersededList"

    Write-Output -InputObject ''
    Write-Output -InputObject 'Summary:'
    Write-Output -InputObject '========'

    Write-Output -InputObject "All Updates = $countAllUpdates"
    $AnyExceptDeclined = $countAllUpdates - $countDeclined
    Write-Output -InputObject "Any except Declined = $AnyExceptDeclined"
    Write-Output -InputObject "All Superseded Updates = $countSupersededAll"
    $SuperseededAllOutput = $countSupersededAll - $countSupersededLastLevel
    Write-Output -InputObject "    Superseded Updates (Intermediate) = $SuperseededAllOutput"
    Write-Output -InputObject "    Superseded Updates (Last Level) = $countSupersededLastLevel"
    Write-Output -InputObject "    Superseded Updates (Older than $ExclusionPeriod days) = $countSupersededExclusionPeriod"
    Write-Output -InputObject "    Superseded Updates (Last Level Older than $ExclusionPeriod days) = $countSupersededLastLevelExclusionPeriod"

    $i = 0
    if (-not $SkipDecline)
    {
    
        Write-Output -InputObject "SkipDecline flag is set to $SkipDecline. Continuing with declining updates"
        $updatesDeclined = 0
    
        if ($DeclineLastLevelOnly)
        {
            Write-Output -InputObject '  DeclineLastLevel is set to True. Only declining last level superseded updates.' 
        
            foreach ($update in $allUpdates)
            {
            
                if (-not $update.IsDeclined -and $update.IsSuperseded -and -not $update.HasSupersededUpdates)
                {
                    if ($update.CreationDate -lt (Get-Date).AddDays(-$ExclusionPeriod))
                    {
                        $i++
                        $percentComplete = "{0:N2}" -f (($updatesDeclined / $countSupersededLastLevelExclusionPeriod) * 100)
                        Write-Progress -Activity "Declining Updates" -Status "Declining update #$i/$countSupersededLastLevelExclusionPeriod - $($update.Id.UpdateId.Guid)" -PercentComplete $percentComplete -CurrentOperation "$($percentComplete)% complete"

                        try
                        {
                            $update.Decline()
                            $updatesDeclined++
                        }
                        catch [System.Exception]
                        {
                            Write-Output -InputObject "Failed to decline update $($update.Id.UpdateId.Guid). Error:" $_.Exception.Message
                        }
                    }
                }
            }
        }
        else
        {
            Write-Output -InputObject '  DeclineLastLevel is set to False. Declining all superseded updates.'

            foreach ($update in $allUpdates)
            {
            
                if (-not $update.IsDeclined -and $update.IsSuperseded)
                {
                    if ($update.CreationDate -lt (Get-Date).AddDays(-$ExclusionPeriod))
                    {
                  
                        $i++
                        $percentComplete = "{0:N2}" -f (($updatesDeclined / $countSupersededAll) * 100)
                        Write-Progress -Activity "Declining Updates" -Status "Declining update #$i/$countSupersededAll - $($update.Id.UpdateId.Guid)" -PercentComplete $percentComplete -CurrentOperation "$($percentComplete)% complete"
                        try
                        {
                            $update.Decline()
                            $updatesDeclined++
                        }
                        catch [System.Exception]
                        {
                            Write-Output -InputObject "Failed to decline update $($update.Id.UpdateId.Guid). Error:" $_.Exception.Message
                        }
                    }
                }
            }
        }

        Write-Output -InputObject "  Declined $updatesDeclined updates."

        if ($updatesDeclined -ne 0)
        {
            Copy-Item -Path $outSupersededList -Destination $outSupersededListBackup -Force
            Write-Output -InputObject "  Backed up list of superseded updates to $outSupersededListBackup"
        }
    }
    else
    {
        Write-Output -InputObject "SkipDecline flag is set to $SkipDecline. Skipped declining updates"
    }

    Write-Output -InputObject ''
    Write-Output -InputObject 'Done'
    Write-Output -InputObject ''

    Stop-Transcript

    return $countSupersededAll
}

$Output = Decline-SupersededUpdates -UpdateServer $WSUSServer -UseSSL -Port 8531 -SkipDecline

$Count = $Output | select -last 1

$Emailbody = "<html>"

If ($Count -gt $AlertCount) {
    $Emailbody += '<p style="color:red;">' + "WSUS SupersededUpdates count is getting high. Current Count:$($Count)</p>"
}

Function Send-Email {
$MailParams = @{'To'         = $To;
                'From'       = $From;
                'Subject'    = "WSUS SupersededUpdates Count";
                'SmtpServer' = $SMTP }
            $Body = @"
$emailbody
"@
            
            if ([string]::IsNullOrEmpty($emailbody)) {
            Write-Host Empty
            }
			ElseIf ($emailbody -eq "<html></html>"){
            Write-Host Empty2
            }
            Else {
                Send-MailMessage @MailParams -Body $Body -BodyAsHtml
            }
}
$emailbody += "</html>"
Send-Email

Related Posts