PowerShell Pipeline
Finding Pending Updates Using PowerShell
Get a clear list of what Windows updates have been downloaded, but haven't been applied in an environment.
We depend on Windows updates to ensure that our systems are keeping up with patches so they are secure. If you are in an enterprise environment, then you are probably using something like Windows Server Update Services (WSUS) to accomplish all of this. While your clients could probably be rebooted nightly to ensure that they are up to date, your servers are probably another story altogether. Ensuring that they meet specific uptime requirements as well as being available to your customers probably means that the time to update and reboot them are a little stricter.
Let's assume that your policy looks like this:
- WSUS updates are automatically approved based on a rule that is configured
- The Windows Update agent settings are configured to download but not install the updates
- This means that you need to manually install the updates and reboot the server
While I am not going to be looking at the installation process in today's article, I will be covering how we can get a better idea on what updates are queued up on each system that will need to be installed.
We can't easily see what updates are currently available on a system unless we go into the Windows Updates settings and view the updates or we can query the event log to see what updates have been downloaded and are waiting to install. I am going with a different approach by directly going into the windows update agent via its API to search for and report on downloaded updates that are waiting to be installed. To do this, I am going to make use of Microsoft.Update.Session COM object to get our foot in the door to begin hunting down pending updates.
$Computername = $env:COMPUTERNAME
$updatesession = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session",$Computername))
In this case, I am just going to look at my local system for updates. Because I want to have something that will work remotely, I am choosing to use the [Activator] with its CreateInstance() method and supplying the type that I am looking to connect to remotely as well as supplying the computer name. By doing this, I will have created my UpdateSearcher object that will be used to look for updates.
Once I have this created, I can then begin a search using the Search() method and supplying a string which contains a specific query. More information about what kinds of queries you can perform can be found at this following MSDN link.
Note that the following code may take a few minutes to run.
$searchresult = $updatesearcher.Search("IsInstalled=0") # 0 = NotInstalled | 1 = Installed
In this case, I am only looking for updates that are not currently installed. Now I cannot query whether an update has been downloaded or not, so I will need to deal with that later on. Once completed, we can get an idea on how many updates are available by looking at the count of the results returned for updates.
$searchresult.Updates.Count
Obviously I have been failing to keep myself updated on this virtual machine. Since I do have updates which are available and not yet installed, it is time to take a look and see what they are.
$Updates = If ($searchresult.Updates.Count -gt 0) {
#Updates are waiting to be installed
$count = $searchresult.Updates.Count
Write-Verbose "Found $Count update\s!"
#Cache the count to make the For loop run faster
For ($i=0; $i -lt $Count; $i++) {
#Create object holding update
$Update = $searchresult.Updates.Item($i)
[pscustomobject]@{
Title = $Update.Title
KB = $($Update.KBArticleIDs)
SecurityBulletin = $($Update.SecurityBulletinIDs)
MsrcSeverity = $Update.MsrcSeverity
IsDownloaded = $Update.IsDownloaded
Url = $Update.MoreInfoUrls
Categories = ($Update.Categories | Select-Object -ExpandProperty Name)
BundledUpdates = @($Update.BundledUpdates)|ForEach{
[pscustomobject]@{
Title = $_.Title
DownloadUrl = @($_.DownloadContents).DownloadUrl
}
}
}
}
}
Here I am beginning a loop by working through each update and then parsing out various pieces of information and creating a custom PowerShell object. This is then being saved so I can work with the object later on to provide some sort of reporting of what is downloaded and what isn't downloaded for some unknown reason.
The first thing I will look at is what is actually downloaded by performing a grouping of the IsDownloaded property.
$IsDownloaded = $Updates|group IsDownloaded
#Index 1 is downloaded updates
$IsDownloaded[1].Group
So far I have 88 updates that have been downloaded to be installed by a system administrator. I can see that they appear to be a lot of OS updates but as I explore I can see .Net and malicious software removal tool updates as well. I can also see details such as its knowledge base number and its bulletin number, if applicable. If you explore more into the object, we can look at the bundled updates that will be installed as well as their respected url that the updates will be downloaded from.
$IsDownloaded[1].Group[1]
$IsDownloaded[1].Group[1].BundledUpdates|Format-List
Now if we wanted to quickly get these updates into a report, we can simply export the data to a CSV file and send that to whoever might need the information.
$IsDownloaded[1].Group |
Export-Csv -NoTypeInformation -Path DownloadedUpdates.csv
Regardless of what you choose to do with this data, it definitely provides some great information in the updates that are downloaded and waiting for installation. Also, if you are interested in a function that does all of this work for you, then check out Get-PendingUpdate which is available to download from this link and if you wish to help in its development, you can head out to GitHub to and fork this function here.
About the Author
Boe Prox is a Microsoft MVP in Windows PowerShell and a Senior Windows System Administrator. He has worked in the IT field since 2003, and he supports a variety of different platforms. He is a contributing author in PowerShell Deep Dives with chapters about WSUS and TCP communication. He is a moderator on the Hey, Scripting Guy! forum, and he has been a judge for the Scripting Games. He has presented talks on the topics of WSUS and PowerShell as well as runspaces to PowerShell user groups. He is an Honorary Scripting Guy, and he has submitted a number of posts as a to Microsoft's Hey, Scripting Guy! He also has a number of open source projects available on Codeplex and GitHub. His personal blog is at http://learn-powershell.net.