Finding outdated WSUS clients with PowerShell
One of the aspects of managing a Windows environment I try to be vigilant on is ensuring my clients are installing patches. With the recent exploits that occurred this year, I am certain this is an important aspect of my job. If I have Windows clients that are not installing recent patches, I want to know about it.
I have always found PowerShell to be a great tool for monitoring my environment and reporting to me things I want to know. Sure you use other tools for this, but I prefer the agility that PowerShell provides, so I created a function called Get-OutdatedWSUSClients. This small function basically remotes into all my workstations via PowerShell, and uses Get-WUHistory (from the PSWindowsUpdate module) and reports back if a client has installed ANY update in recent days. In addition, if PSWindowsUpdate is not installed for some reason on the client, it does will install that.
So a simple example of using this function would be running it on a single computer to see if it has installed any updates within the last 3 days:
C:\> Get-OutdatedWSUSClients -ComputerName Win10-Test -Days 3
A much cooler example is to run it on multiple computers based on Active Directory. Here I query all Windows 10 computers in my Win10 OU to retrieve any that has not installed updates in the last 3 days:
C:\ Get-OutdatedWSUSClients -ComputerName (Get-ADComputer -Filter 'Operatingsystem -like "*Windows 10*"' -SearchBase "OU=Win10,DC=DOMAIN,DC=COM" | Select-Object -ExpandProperty Name) -Days 3
If no results are returned, the client has installed an update in the last three days. If the client is in fact outdated, it will print the hostname. You will see in the function I use Chocolatey to install PSWindowsUpdate, which I prefer but you can also just use a PSGallery or your own repository. I also use $LASTEXITCODE to check for errors in running Chocolatey.
function Get-OutdatedWSUSClients { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [String[]]$ComputerName, [Parameter(Mandatory=$true)] [String]$Days ) Invoke-Command -ComputerName $ComputerName -ThrottleLimit 150 -ScriptBlock { $CheckModule = Get-Module -ListAvailable PSWindowsUpdate if (!$CheckModule) { choco install PSWindowsUpdate -y | Out-Null Import-Module PSWindowsUpdate | Out-Null if ($LASTEXITCODE -ne '0') { Write-Warning -Message "$Env:COMPUTERNAME could not install PSWindowsUpdate" Break } } try { $LastUpdate = Get-WUHistory -ErrorAction Stop | Where-Object {$_.Date -gt (Get-Date).AddDays(-$Using:Days)} | Sort-Object -Property Date -Descending | Select-Object -First 1 -Property Date,Title if (!$LastUpdate) { Write-Output "$Env:COMPUTERNAME has not installed updated in last $Days days" Break } } catch { Write-Warning -Message "$Env:COMPUTERNAME could not run Get-WUHistory" } } -ArgumentList {$Days} }