Every month we are hit by Microsoft's patchday. And every month the same workflow fires: learn about updates, test them, approve necessary updates, check compliance of machines and manually fix machines with failed updates . But don't think you are done. You still need to update VM templates so that new VMs have the latest updates. I'd like to provide you with a useful script to automate this important task.

The script itself contains many comment to illustrate the approach taken to apply the updates. In addition, it also lists references to the API documentation of the .NET classes used. In case you decide to reuse excerpts in another script you will easily find the appropriate documentation.

For the script to work it requires several parameters like which VHD to update, which WSUS server to contact and where to find updates on the hard disk.

param (
    [string]$VhdPath = $(throw "Parameter VhdPath is required."),
    [string]$MountDir = $(throw "Parameter MountDir is required."),
    [string]$WsusServerName = $(throw "Parameter WsusServerName is required."),
    [Int32]$WsusServerPort = $(throw "Parameter WsusServerPort is required."),
    [string]$WsusTargetGroupName = $(throw "Parameter WsusTargetGroupName is required."),
    [string]$WsusContentPath = $(throw "Parameter WsusContentPath is required.")
)

Microsoft has done a brilliant job in exposing WSUS in .NET and therefore in PowerShell. The classes relevant to this script are located in the namespace Microsoft.UpdateServices.Administration which needs to be loaded before it can be used.

[reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration") | Out-Null

The connection with a WSUS server is established using the static method getUpdateServer of the class AdminProxy and requires the name and port of the WSUS server.

$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($WsusServerName, $False, $WsusServerPort)

Before the WSUS server is asked for a list of update, an update scope is created to narrow down the relevant updates. There are several criteria like a computer target group and a timestamp.

$UpdateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope

The updates will be applied using the tool dism (Deployment Image Servicing and Management tool) which requires the image to be mounted. This is also done using dism.

dism /Mount-Image /ImageFile:"$VhdPath" /Index:1 /MountDir:"$MountDir"

The WSUS server is asked for a list using the update scope. The list is then processed to apply all updates.

$wsus.GetUpdates($UpdateScope) | ForEach {

The files associated with an update are retrieved by the method GetInstallableItems and returns a list of files with a virtual path inside the WSUS content directory. Therefore, the path needs to be translated to the actual location on the hard disk.

$_.GetInstallableItems().Files {

Each of the files (installable items) is then applied to the mounted VHD file using dism. BY the way, dism uses the term "package" for updates to the image because there are other types of packages next to updates.

dism /Image:"$MountDir" /Add-Package /PackagePath:"$FileName"

After all files have been applied to the mounted VHD file, it is important to unmount it using the Commit switch to write the changes to the file. Otherwise all the changes are lost.

dism /Unmount-Image /MountDir:"$MountDir" /Commit

Usage

.\Apply-WindowsUpdate.ps1 -VhdPath C:\PATH\TO\YOUR.VHD -MountDir C:\TEMP\MOUNT -WsusServerName WSUS.YOURDOMAIN.LOCAL -WsusServerPort 8530 -WsusTargetGroupName "Windows Server 2012 R2" -WsusContentPath \\WSUS\WsusContent