Use PowerShell to Determine if Specific Windows Updates are Installed on Remote Servers

It has been a crazy week to say the least. If you're like me, you wanted to make sure that the specific Windows updates that patch the WannaCry ransomware vulnerability have been installed on all of your servers. I've seen a lot of functions and scripts this week to accomplish that task, but most of them seem too complicated in my opinion.

While it's personal preference, I also always think about whether I should use a PowerShell one-liner, script, or function. Usually one-liners are something I type into the PowerShell console using all the aliases and positional parameters that I want since I'll simply close out of the console when I'm done and the code is gone. Code with aliases and positional parameters shouldn't be saved as scripts or shared with others.

For me, it's a little more difficult to distinguish the difference between whether to use a PowerShell script or function. I write functions as reusable tools that I place into modules which allow me to easily access them. They're generally generic enough to be used in multiple scenarios. Often times, I'll write caller scripts for the functions so the specific data such as server names is not contained within the function itself which makes them easier to share with others outside of my organization.

In the scenario of testing for Windows updates that are installed specifically for WannaCry, I'll use a script since the updates are cumulative and the KB numbers that are valid this month won't be all of the ones that are valid next month that patch this vulnerability. In other words, I chose a script because the shelf life isn't long enough to justify writing a function.

The Get-Hotfix cmdlet is used to check for hotfixes that are installed. It has a ComputerName parameter for targeting remote computers but more than likely it will be blocked by either a network or host firewall since it uses older protocols for communication. Although multiple computer names can be specified with Get-Hotfix, it runs against one computer at a time and it does not continue to the next computer once it tries to connect to one that is unreachable.

The following example demonstrates this problem where Get-Hotfix does not continue to the next computer once it reaches a computer that's unreachable. It also confirms that Get-Hotfix does not run in parallel.

get-hotfix1a.png

Long story short, don't use the ComputerName parameter of Get-Hotfix to query remote computers because there's a better way.

Wrap the Get-Hotfix cmdlet inside Invoke-Command to take advantage of PowerShell remoting. By default, Invoke-Command runs against 32 remote computers at a time in parallel which can be adjusted using the ThrottleLimit parameter. PowerShell remoting is also more firewall friendly and is enabled by default on servers running Windows Server 2012 and higher. It can be enabled on other versions using Enable-PSRemoting as long as PowerShell 2.0 or higher is installed.

The following example scans three servers for the hotfixes listed in Microsoft Security Bulletin MS17-010.

 1Invoke-Command -ComputerName Server01, Server02, Server03 {
 2    $Patches = 'KB4012598', #Windows XP, Vista, Server 2003, 2008
 3               'KB4018466', #Server 2008
 4               'KB4012212', 'KB4012215', 'KB4015549', 'KB4019264', #Windows 7, Server 2008 R2
 5               'KB4012214', 'KB4012217', 'KB4015551', 'KB4019216', #Server 2012
 6               'KB4012213', 'KB4012216', 'KB4015550', 'KB4019215', #Windows 8.1, Server 2012 R2
 7               'KB4012606', 'KB4015221', 'KB4016637', 'KB4019474', #Windows 10
 8               'KB4013198', 'KB4015219', 'KB4016636', 'KB4019473', 'KB4016871', #Windows 10 1511
 9               'KB4013429', 'KB4015217', 'KB4015438', 'KB4016635', 'KB4019472' #Windows 10 1607, Server 2016
10    Get-HotFix -Id $Patches
11} -Credential (Get-Credential) -ErrorAction SilentlyContinue -ErrorVariable Problem
12
13foreach ($p in $Problem) {
14    if ($p.origininfo.pscomputername) {
15        Write-Warning -Message "Patch not found on $($p.origininfo.pscomputername)"
16    }
17    elseif ($p.targetobject) {
18        Write-Warning -Message "Unable to connect to $($p.targetobject)"
19    }
20}

You could just as easily query Active Directory for the computer names or use Get-Content to obtain a list of computer names from a text file.

I placed the Patches variable inside of Invoke-Command to make the script PowerShell 2.0 compatible. If all of the remote servers were running PowerShell 3.0 or higher, that could have been defined at the top and the Using variable scope modifier could have used to use the local variable in the remote sessions.

Some scripts and functions that I've seen make this process more complicated than it needs to be by first checking to see what operating system and architecture the target computer is running to then only check for the specific updates that are applicable to that OS. There's no reason for that since updates that aren't applicable won't be installed anyway and if any of these updates are found, it's been patched. If you decided to write a function, you could simply return a Boolean value letting you know that the computer is good to go if any one of these updates is found.

µ