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.
April 8, 2013
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.
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
You May Also Like