If you’ve ever deployed Windows Updates to clients on your network, you have probably been asked by your manager(s) what KB’s were deployed, and when if an issue comes up on a workstation or server. Unfortunately, sometimes the built-in WSUS server reporting tool can leave you frustrated and doesn’t have great functionality for generating them outside of the WSUS management GUI. You have an alternative; a PowerShell Windows Update report!
How to Tell When a PowerShell Report is Needed
I was recently asked by a group of managers that were working on validating a security vulnerability scan for some assistance. This vulnerability scan was claiming that a set of systems were missing particular Microsoft KB’s, KB’s that were recently approved, deadlined, and showing as installed in the WSUS management console.
I sent some screenshots of the console status along with my sysadmin reply. I didn’t give it much thought at the time because I was busy with other projects and this was a routine request.
A day or so went by, and another vulnerability scan was run, producing the same results. Management was not convinced that the updates were installed. Having issues with WSUS from time to time, I started to distrust the built in reports and the management console. I didn’t know updating Windows as this hard!
To be cautious, and a little more diligent, I decided to bypass the WSUS management console and go straight to the workstations and servers that were showing up in the security vulnerability scan.
Brainstorming a Windows Update Report
Luckily, the security vulnerability scan only found about 4 workstations and 12 servers with these supposedly missing KB’s. So I created a simple list in a text file using the fully qualified domain name (FQDN) of each host. I also knew for a fact, that the missing KB’s would have been installed in the past 30 days as I just completed a maintenance cycle.
With this knowledge in hand, I jotted down some pseudo code to help me begin. Here’s what I outlined:
- Store my text file that contains the list of hosts.
- For each of the hosts in that file, run a command.
- The command must gather installed KB’s installed in the last 30 days.
- The output only needs to contain the hostname, KB/HotFix ID, and the install date.
- The output needs to be readable, and just needs to be a simple file.
- No fancy coding needed, just comparing visually to what WSUS reporting was displaying.
Based on my notes, I had a good idea of what I was looking for and what cmdlets I might need. The primary focus was on the Get-HotFix
cmdlet. This cmdlet queries all the hotfixes (more commonly referred to as security updates) that have been applied to a Windows host. It can find all or specific updates on Windows machines. You can read more about this cmdlet and how to use it here.
Get-HotFix
does not support implicit remoting so I needed to come up with method to run this cmdlet on the systems I needed to report on. Invoke-Command
does and you can pass multiple values to the ComputerName
parameter.
I already have saved a list of hosts I am targeting, so I’ll save myself some typing and store those hosts as a variable. To do so, I’ll have to assign a variable name and make the value the list of hosts.
Get-Content
will read the content of the text file line by line creating an array of sorts. Let’s call this array $Hosts
. Now I have a command, some data to feed to the next set of commands, but I need to make the resulting data readable and concise.
I want to take a moment here to emphasize “Filter First, Format Last.” . Remembering this will help you when working with these types of scripts. Opening up a PowerShell session and running the Get-Hotfix
cmdlet by itself will typically result in a long list of updates that have been applied to a host.
PS51> Get-HotFix
Source Description HotFixID InstalledBy InstalledOn
------ ----------- -------- ----------- -----------
MACWINVM Update KB2693643 MACWINVM\Administ... 3/14/2019 12:00:00 AM
MACWINVM Update KB4100347 NT AUTHORITY\SYSTEM 2/17/2019 12:00:00 AM
MACWINVM Update KB4230204 NT AUTHORITY\SYSTEM 7/6/2018 12:00:00 AM
MACWINVM Security Update KB4287903 NT AUTHORITY\SYSTEM 7/8/2018 12:00:00 AM
MACWINVM Security Update KB4338832 NT AUTHORITY\SYSTEM 7/21/2018 12:00:00 AM
MACWINVM Update KB4338853 NT AUTHORITY\SYSTEM 7/6/2018 12:00:00 AM
MACWINVM Update KB4343669 NT AUTHORITY\SYSTEM 7/19/2018 12:00:00 AM
MACWINVM Security Update KB4343902 NT AUTHORITY\SYSTEM 8/15/2018 12:00:00 AM
MACWINVM Update KB4346084 NT AUTHORITY\SYSTEM 5/11/2019 12:00:00 AM
MACWINVM Update KB4456655 NT AUTHORITY\SYSTEM 9/12/2018 12:00:00 AM
--snip--
Filtering helps gather just the information you need.
Without filtered data, formatting is useless at this point. Think of filtering as your data type requirements, and formatting as how you want that data displayed. For my purposes, I already had the requirements thought out. I needed to get updates installed in the past 30 days.
To filter, I will need to use the Where-Object
cmdlet and then pass along some member properties and comparison operators with a dash of math. To do this, I will take every object returned ($_
) from Get-HotFix
and pass those to Where-Object
to find all updates installed on a date that is greater than (-gt
) today’s date (or whenever I run the script) minus (-30) days ago. That will get the initial data I’m looking for.
Get-HotFix | Where-Object { $_.InstalledOn -gt ((Get-Date).AddDays(-30)) }
But I want to filter the returned objects and their properties a little more. This is where Select-Object
will help, allowing me to further trim the amount of data to be displayed to just a couple of crucial properties.
Get-HotFix | Where-Object { $_.InstalledOn -gt ((Get-Date).AddDays(-30)) } |
Select-Object -Property PSComputerName, Description, HotFixID, InstalledOn
Now that I have the data properly filtered, now I can move on to formatting the results into a usable format. To do so I’ll pipe ( | ) the results from my previous filtering to Format-Table -Autosize
and output as a file type of my choosing. I’ll need to use Append
and -ErrorAction SilentlyContinue
parameters to ensure that each result is written to the next line in the output file and if an error occurs, it won’t cause the rest of the hosts to not be contacted.
Format-Table -AutoSize |
Out-File -Encoding utf8 -FilePath '.\Recent_OS_Updates.txt' -Append -ErrorAction SilentlyContinue
I chose to go with a text file because I didn’t require anything fancy. You can change the output to meet your needs. My output looked something similar to this:
Here’s the final script came up with and used:
$Hosts = Get-Content -Path '.\hosts.txt'
Invoke-Command -ComputerName $Hosts -ScriptBlock {
Get-HotFix | Where-Object {
$_.InstalledOn -gt ((Get-Date).AddDays(-30))
} | Select-Object -Property PSComputerName, Description, HotFixID, InstalledOn
} | Format-Table -AutoSize |
Out-File -Encoding utf8 -FilePath '.\Recent_OS_Updates.txt' -Append -ErrorAction SilentlyContinue
For me, this was simple, concise, and offered proof that the KB’s were indeed installed. The report was well received by the management team and in a format easily read.