Add Updates to an Offline VHD or WIM File

Various solutions help in the automated updating of a VHD file from Windows Update Services or Configuration Manager, but you can also manually inject updates. Here's what to do.

John Savill

April 8, 2013

7 Min Read
Add Updates to an Offline VHD or WIM File

Q: How can I add updates to an offline VHD or WIM file downloaded from the Microsoft Catalog?

A: Various solutions help in the automated updating of a virtual hard disk (VHD) file from Windows Update Services or Configuration Manager. However, you can also manually inject updates into a VHD using the DISM utility, and it's actually pretty simple.

After you download all the updates to a folder, you will have one folder with lots of subfolders (one for each downloaded update from the catalog). The Windows PowerShell script below mounts your VHD and assumes it has been mounted as drive letter I, then searches for all updates in subfolders of the current folder and applies them all to the VHD file, performs a cleanup of the image (if a Service Pack was installed there are many files that have been replaced) and then dismounts.

I save this as a script but actually run it manually by selecting the various blocks of the code within the Integrated Scripting Editor, so I can check each stage of the progress.

I use this process to keep my master Windows Server 2012 VHD patched with the latest update each month, so when it's deployed as part of a new OS deployment, it's already fully patched.

#block 1mount-vhd -path G:TempWin2012DCRTM.vhdx 

 

#block 2 $updates = get-childitem -Recurse | where {($_.extension -eq ".msu") -or ($_.extension -eq ".cab")} | select fullname foreach($update in $updates) { write-debug $update.fullname $command = "dism /image:i: /add-package /packagepath:'" + $update.fullname + "'" write-debug $command Invoke-Expression $command } 

 

#block 3$command = "dism /image:i: /Cleanup-Image /spsuperseded" Invoke-Expression $command

 

#block 4dismount-vhd -path G:TempWin2012DCRTM.vhdx -confirm:$false

Below is an example execution in action:

PS D:softwareWindows 2012 Updates> mount-vhd -path G:TempWin2012DCRTM.vhdxPS D:softwareWindows 2012 Updates> $updates = get-childitem -Recurse | where {($_.extension -eq ".msu") -or ($_.extension -eq ".cab")} | select fullnameforeach($update in $updates){write-debug $update.fullname$command = "dism /image:i: /add-package /packagepath:'" + $update.fullname + "'"write-debug $commandInvoke-Expression $command}


Here is the output from the example:

Deployment Image Servicing and Management toolVersion: 6.2.9200.16384Image Version: 6.2.9200.16384Processing 1 of 1 - Adding package D:softwareWindows 2012 UpdatesCumulative Security Update for Internet Explorer 10 forWindows Server 2012 (KB2761465)AMD64-all-windows8-rt-kb2761465-x64_71efb0756cf746951571a72c83ddb55775362418.msuThe operation completed successfully.Deployment Image Servicing and Management toolVersion: 6.2.9200.16384Image Version: 6.2.9200.16384Processing 1 of 1 - Adding package D:softwareWindows 2012 UpdatesSecurity Update for Microsoft .NET Framework 4.5 on Windows 8 and Windows Server 2012 for x64-based Systems (KB2737084)AMD64_X86_ARM-all-windows8-rt-kb2737084-x64_1a1b73c30d7bd20b61fc522890a8fd61370ae1bb.msuThe operation completed successfully. ....


Here is the next command, with output:

PS D:softwareWindows 2012 Updates> $command = "dism /image:i: /Cleanup-Image /spsuperseded"Invoke-Expression $commandDeployment Image Servicing and Management toolVersion: 6.2.9200.16384Image Version: 6.2.9200.16384Service Pack Cleanup cannot proceed: No Service Pack backup files were found.The operation completed successfully.


 

And the next command:

PS D:softwareWindows 2012 Updates> dismount-vhd -path G:TempWin2012DCRTM.vhdx -confirm:$false

Notice there was no Service Pack applied, so the step to clean up the image didn't perform any action.

The same set of commands can be used to patch a master WIM file that is used for new deployments or for deployed OSs to get updated or have corrupt features removed.

I got carried away creating this and created a full PowerShell function that basically does everything for you for a WIM or VHD building on those manually executed commands I showed above. Just pass the file to update and the patch folder location.

Note the script assumes it can use c:wimmount as its mount and will create this folder if it does not exist. It also assumes if using a VHD that no other VHD file is attached on the system.

Click the link to my website here to download the psm1 file and save to the DocumentsWindowsPowerShellModulesInstall-Patch folder as Install-Patch.psm1, which will allow the module to be available automatically and usable. Below is the actual code from the PSM1 file.

function Install-Patch{    <#    .SYNOPSIS    Patches a WIM or VHD file    .DESCRIPTION    Applies downloaded patches to a VHD or WIM file    .NOTES    File Name: Install-Patch.psm1    Author: John Savill    Requires: Tests on PowerShell 3 on Windows Server 2012    Copyright (c) 2013 John Savill    .LINK    http://www.savilltech.com/    .PARAMETER updateTargetPassed    File (WIM, VHD or VHDX) to be patched    .PARAMETER patchpath    Path containing the updates    .EXAMPLE    Install-Patch d:filestest.vhd d:updateswin2012    Install-Patch d:filesinstall.wim:4 d:updateswin2012    #>    [cmdletbinding()]    Param(    [Parameter(ValuefromPipeline=$false,Mandatory=$true)][string]$updateTargetPassed,    [Parameter(ValuefromPipeline=$false,Mandatory=$true)][string]$patchpath)    #$updateTargetPassed = "G:TempWin2012DatacenterRTM.vhdx"    #or    #$updateTargetPassed = "d:sourcesinstall.wim:4"    #$patchpath = "D:softwareWindows 2012 Updates"    if(($updateTargetPassed.ToLower().Contains(".vhd")) -eq $true) # if its VHD or VHDX. Contains is case sensitive so have to convert to lower when comparing    {    $isVHD = $true    }    else    {    $isVHD = $false    }    if($isVHD)    {    $updateTarget=$updateTargetPassed    if ((Test-Path $updateTarget) -eq $false) #if not found    {        write-output "Source not found ($updateTarget)"        break    }    else    {        mount-vhd -path $updateTarget        $disks = Get-CimInstance -ClassName Win32_DiskDrive | where Caption -eq "Microsoft Virtual Disk"           foreach ($disk in $disks)        {               $vols = Get-CimAssociatedInstance -CimInstance $disk -ResultClassName Win32_DiskPartition                 foreach ($vol in $vols)        {               $updatedrive = Get-CimAssociatedInstance -CimInstance $vol -ResultClassName Win32_LogicalDisk |               where VolumeName -ne 'System Reserved'             }           }        $updatepath = $updatedrive.DeviceID + ""    }    }    if(!$isVHD)  #its a WIM file    {    #Need to extract the WIM part and the index    #extract file name and the index number    $updateTargetPassedSplit = $updateTargetPassed.Split(":")    if($updateTargetPassedSplit.Count -eq 3) #one for drive letter, one for folder and one for image number so would have been two colons in it c:tempinstall.wim:4    {        $updateTarget = $updateTargetPassedSplit[0] + ":" + $updateTargetPassedSplit[1]   #There are two colons. The first is drive letter then the folder!        $updateTargetIndex = $updateTargetPassedSplit[2]        $updatepath = "c:wimmount"        #check if exists and if not create it        if ((Test-Path $updatepath) -eq $false) #if not found        {        Write-Host "Creating folder " + $updatepath        New-Item -Path $updatepath -ItemType directory        #could have also used [system.io.directory]::CreateDirectory($updatepath)        }        # Mount it as folder        #dism /get-wiminfo /wimfile:install.wim        dism /Mount-Wim /wimfile:$updateTarget /index:$updateTargetIndex /mountdir:$updatepath    }    else    {        write-output "Missing index number for WIM file. Example: c:tempinstall.wim:4"        break    }    }    # For WIM or VHD    $updates = get-childitem -path $patchpath -Recurse | where {($_.extension -eq ".msu") -or ($_.extension -eq ".cab")} | select fullname    foreach($update in $updates)    {    write-debug $update.fullname    $command = "dism /image:" + $updatepath + " /add-package /packagepath:'" + $update.fullname + "'"    write-debug $command    Invoke-Expression $command    }    $command = "dism /image:" + $updatepath + " /Cleanup-Image /spsuperseded"    Invoke-Expression $command    if($isVHD)    {    dismount-vhd -path $updateTarget -confirm:$false    }    else    {    dism /Unmount-Wim /mountdir:$updatepath /commit    #dism /Unmount-Wim /mountdir:$updatepath /discard    }}

Below are some example usages of the module in action for both a VHDX and a WIM file.

 

PS C:> Install-Patch G:tempWin2012DatacenterRTM.vhdx 'D:softwareWindows 2012 Updates'Deployment Image Servicing and Management toolVersion: 6.2.9200.16384Image Version: 6.2.9200.16384Processing 1 of 1 - Adding package D:softwareWindows 2012 UpdatesSecurity Update for Windows Server 2012 (KB2753842)AMD64-all-windows8-rt-kb2753842-v2-x64_1287425c7410d86b10874e8d666dbb32deb45e42.msuThe operation completed successfully.Deployment Image Servicing and Management toolVersion: 6.2.9200.16384Image Version: 6.2.9200.16384Processing 1 of 1 - Adding package D:softwareWindows 2012 UpdatesSecurity Update for Windows Server 2012 (KB2765809)AMD64-all-windows8-rt-kb2765809-x64_2b2b24ddf3b884815275a9103b84a5e38ba4ad2b.msuThe operation completed successfully.Deployment Image Servicing and Management toolVersion: 6.2.9200.16384Image Version: 6.2.9200.16384Processing 1 of 1 - Adding package D:softwareWindows 2012 UpdatesSecurity Update for Windows Server 2012 (KB2770660)AMD64-all-windows8-rt-kb2770660-x64_6a0d84e5053592949f2ca469a056568d45b5ec9c.msuThe operation completed successfully.Deployment Image Servicing and Management toolVersion: 6.2.9200.16384Image Version: 6.2.9200.16384Service Pack Cleanup cannot proceed: No Service Pack backup files were found.The operation completed successfully.PS C:> Install-Patch d:sourcesinstall.wim:4 'D:softwareWindows 2012 Updates'Deployment Image Servicing and Management toolVersion: 6.2.9200.16384Mounting imageThe operation completed successfully.Deployment Image Servicing and Management toolVersion: 6.2.9200.16384Image Version: 6.2.9200.16384Processing 1 of 1 - Adding package D:softwareWindows 2012 UpdatesSecurity Update for Windows Server 2012 (KB2753842)AMD64-all-windows8-rt-kb2753842-v2-x64_1287425c7410d86b10874e8d666dbb32deb45e42.msuThe operation completed successfully.Deployment Image Servicing and Management toolVersion: 6.2.9200.16384Image Version: 6.2.9200.16384Processing 1 of 1 - Adding package D:softwareWindows 2012 UpdatesSecurity Update for Windows Server 2012 (KB2765809)AMD64-all-windows8-rt-kb2765809-x64_2b2b24ddf3b884815275a9103b84a5e38ba4ad2b.msuThe operation completed successfully.Deployment Image Servicing and Management toolVersion: 6.2.9200.16384Image Version: 6.2.9200.16384Processing 1 of 1 - Adding package D:softwareWindows 2012 UpdatesSecurity Update for Windows Server 2012 (KB2770660)AMD64-all-windows8-rt-kb2770660-x64_6a0d84e5053592949f2ca469a056568d45b5ec9c.msuThe operation completed successfully.Deployment Image Servicing and Management toolVersion: 6.2.9200.16384Image Version: 6.2.9200.16384Service Pack Cleanup cannot proceed: No Service Pack backup files were found.The operation completed successfully.Deployment Image Servicing and Management toolVersion: 6.2.9200.16384Image File : d:sourcesinstall.wimImage Index : 4Saving imageUnmounting imageThe operation completed successfully.

I created a small video that walks through the script and its usage to help, which is at my website or you can find it on YouTube.

About the Author

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like