Deploying a Windows Update with SCCM without rebooting using a Powershell script detection method

Occasionally when deploying software one of the prerequisites might be to install one or more Windows Updates. These are fairly easy to package up and deploy by simply using the command wusa.exe "KB12345678.msu" /quiet and setting the detection method to check the version of one of the files that the update effects. This information is published by Microsoft for each KB article. Once the computer reboots the file will be the newer version and the detection method will properly detect that update is installed.

But what if you don’t want to reboot immediately? It’s quite feasible that if you’re installing an update as a prerequisite to another software installation you want to install the update with the reboot suppressed and then install the software. You’d probably be using the Dependancies feature of SCCM to ensure the update gets installed whenever someone tries to install the software. In this scenario you can’t use the file version detection method as the file version will not change until after a reboot, but as you’re suppressing that reboot until after the software has been installed, the detection will fail. And that will cause the whole software deployment to fail.

The solution to this is to use a Powershell script as your deployment method. This script will do two things:

  1. Check for a particular event in the Windows Setup log that will exist when the update has been installed.
  2. Check if the file version of one of the files the update effects has been updated.

Why do you need to do both the event log check and the file version check? Because you cannot rely on the Setup log to continue to contain the log entry in the future. The Setup log may be cleared or set to roll over when it reaches a certain size and when that happens the detection will fail and incorrectly show as uninstalled in Software Center. The point of this detection method is to make the installation show as successful in the short time between the update installation and the software installation after which you will presumably reboot the machine, or in the worst case wait for the user to turn off their machine at the end of the day. Once the reboot is complete the second detection method will kick in and continue to correctly detect the installed update going forward.

So let’s take an example update and see what the package would look like in SCCM. For this example I am using KB2921916.

KB2921916 Program Properties

This part is nice and simple. Notice the uninstall program is specified as well, I consider it best practise to always include the uninstall program just in case the update causes a problem and the user needs to remove it quickly. These programs are:

wusa.exe "Windows6.1-KB2921916-x64.msu" /quiet /norestart

wusa.exe /kb:2921916 /uninstall /quiet /norestart

Hop over to the Detection Method tab and change it to Use a custom script to detect the presence of this deployment type.

KB2921916 Detection Method

Click Edit and get writing some Powershell!1

KB2921916 Detection Method Properties

That script is:

$KBToDetect = "KB2921916"
$FileToDetect = "$Env:WinDir\System32\setupapi.dll"
$VersionToDetect = "6.1.7601.18361"

Start-Sleep "5"

$GetFileVersionInfo = (Get-Item $FileToDetect).VersionInfo
$FileVersion = $GetFileVersionInfo.FileMajorPart.ToString() + "." + $GetFileVersionInfo.FileMinorPart.ToString() + "." + $GetFileVersionInfo.FileBuildPart.ToString() + "." + $GetFileVersionInfo.FilePrivatePart.ToString()

if ([Version] $FileVersion -ge [Version] $VersionToDetect) {return $true}

if (Get-WinEvent -ErrorAction "SilentlyContinue" -MaxEvents 3 -FilterHashtable @{logname = 'setup'} | select message | Select-String -SimpleMatch "A reboot is necessary before package $KBToDetect can be changed to the Installed state.") {return $true}

There’s quite a lot going on here so let’s briefly run through it. The first three variables define what we are looking for and you will have to update these based on the KB you are installing. Having another look the KB2921916 article and expanding the File information section you can see that one of the files that updates is Setupapi.dll. This file can be found in %SystemDrive%\System32\Setupapi.dll and will bring its version number to 6.1.7601.18361. These details have been added to the $KBToDetect, $FileToDetect and $VersionToDetect variables.

The next part is a simple 5 second sleep. I added this because in my testing I found that sometimes the detection script runs before the Setup log entry is added which causes the detection to fail. By waiting 5 seconds before running the detection we ensure the log entry is in place when the detection runs.

The next part is the needlessly complicated (in my opinion) code needed to find the version of Setupapi.dll. If you just try (Get-Item $FileToDetect).VersionInfo.ProduectVersion you will not get the current version of the file, you will get the version the file was when it shipped! This weirdness is known and has been blogged about. So this code gathers the individual parts of the file’s true version (the FileMajorPart, FileMinorPart, FileBuildPart and FilePrivatePart) and constructs a full version string out of it.

The next two lines are the only ones that really matter for the detection. The first if statement checks the file version and returns $true if the installed version of Setupapi.dll is either equal to or greater than 6.1.7601.18361. This will ensure that post reboot the detection method will still work and ensures that it will also continue to detect correctly if the file version increases again in the future.

The second if statement checks the last 3 events in the Setup log and searches for the string “A reboot is necessary before package $KBToDetect can be changed to the Installed state.” If this string is found then it returns $true.

Note that nothing at all is returned if neither of these if statements is true. That is by design because when you use Powershell as a detection method practically any output from the script is taken to be success so if it fails it must return nothing.

Only one thing left to do now. Click on the User Experience tab and change the enforced behaviour from the default (Determine behaviour based on return codes) to No specific action.

KB2921916 User Experience Properties

This is important because if you don’t do this the return code will be 3010 (soft reboot) and the user will be prompted to reboot regardless of your /noreboot switch. If this happens any further components of the deployment will wait until the reboot has been completed which is probably what you’re trying to avoid!

1In order to use a Powershell script detection method you will need to ensure that you have the Powershell execution policy set to Bypass on your clients. That is unless you’re doing things very properly and have a code signing script that your clients trust that you can sign your Powershell detection scripts with. You can set the execution policy in the Client settings in SCCM or via GPO.