Nigel Boulton's Blog
20Jan/1111

Installing a Windows Hotfix on Multiple Machines using a PowerShell Script

A little while ago I was given a hotfix by Microsoft PSS for an issue we had been experiencing with the WMI repository intermittently becoming corrupted on Windows 2008 servers. As you will know from my previous posts we have quite a few servers, so after testing the hotfix carefully I was looking for a way to deploy this across the server estate with minimum effort. We do have a third-party deployment product, but this is geared around deploying regular Microsoft security patches as opposed to hotfixes intended to address specific issues. I wanted a way to do this semi-interactively so that I could monitor progress and deal with any issues arising during the deployment process.

My good friend and colleague Jonathan Medd kindly did the initial research on this for me (I figured if anybody could find a way to do it then he could!). We suspected that we might have some trouble getting PowerShell remoting to do this (see my previous post here), and after some searching and testing it soon became clear that this was in fact the case.

The approach we settled on was still based around PowerShell (of course) but we ended up having to make use of the trusty old utility PsExec, originally written by Mark Russinovich of Sysinternals, who now come under the Microsoft umbrella.

In this solution, PsExec.exe calls WUSA.exe, which is the Windows Update Stand-alone Installer. This is a means of installing update packages programmatically. Update packages have an .msu file extension. I can’t get out of the habit of calling them hotfixes though, sorry... WUSA is a simple and effective utility that surprisingly, I hadn’t encountered before. The final script code is shown below:

$Servers = $(1..176 | foreach {"SERVER$_"})

$HotfixPath = '\\Fileserver\Hotfixes\KB2464876\Windows6.0-KB2464876-x86.msu'

foreach ($Server in $Servers){
	if (Test-Path "\\$Server\c$\Temp"){
		Write-Host "Processing $Server..."
		# Copy update package to local folder on server
		Copy-Item $Hotfixpath "\\$Server\c$\Temp"
		# Run command as SYSTEM via PsExec (-s switch)
		& E:\SysinternalsSuite\PsExec -s \\$Server wusa C:\Temp\Windows6.0-KB2464876-x86.msu /quiet /norestart
		if ($LastExitCode -eq 3010) {
			$ConfirmReboot = $False
		} else {
			$ConfirmReboot = $True
		}
		# Delete local copy of update package
		Remove-Item "\\$Server\c$\Temp\Windows6.0-KB2464876-x86.msu"
		Write-Host "Restarting $Server..."
		Restart-Computer -ComputerName $Server -Force -Confirm:$ConfirmReboot
		Write-Host
	} else {
		Write-Host "Folder C:\Temp does not exist on the target server"
	}
}

To avoid authentication issues, we have PsExec run WUSA as SYSTEM (-s switch), which means that the update package needs to be available locally, so the script copies it to C:\Temp on the machine in question first. During testing, we were using the -i (interactive) switch, but doing this caused error 1008 "ERROR_NO_TOKEN" when I tried to run it for real – this appears to happen if you are not logged on to the server being processed.

The hotfix (sorry, "update package") in question required a restart after installation. I wanted the process to be as automated as possible, but still interactive, as I mentioned earlier. I wanted the restart to be performed automatically and for the script to proceed to the next server unprompted if the package installed as expected, but to prompt me if not so that I could troubleshoot.

To achieve this, WUSA installs the package with the /norestart switch. Because of this, the error code returned from WUSA (via PsExec) is 3010. In fact this isn’t an error. By testing PowerShell’s built in $LastExitCode variable it's possible to have the script proceed with the removal of the local file and the subsequent restart of the machine automatically if this is the result.

Of course, you can build the list of servers in a number of ways in the code above. I used a numbered range, but by simply substituting the line which sets the $Servers variable, you can easily read a list of machine names from a text file:

$Servers = Get-Content '\\Fileserver\Hotfixes\ServerList.txt'

I ran this against 176 servers in groups of about 50 in a few hours and it worked faultlessly. After all the servers had restarted I needed a quick way of verifying that the hotfix had been successfully installed on all of them. Shortly I will publish a further post which outlines how I did that. It’s neat and simple and potentially useful to be able to check for the presence of any hotfix on a number of machines.

Filed under: PowerShell 11 Comments