Weekend Scripter: Use PowerShell to Patch VHD and WIM Image Files

Weekend Scripter: Use PowerShell to Patch VHD and WIM Image Files

  • Comments 5
  • Likes

 

 

Summary: Microsoft PFE Ben Pearce shows how to use Windows PowerShell to perform offline servicing of Virtual Disks (VHD) and Windows Imaging Format (WIM) files.

 

Microsoft Scripting Guy Ed Wilson here. We continue with guest blogger Weekend Scripter today by having a post written by Ben Pearce.

Ben Pearce is a UK based Premier Field Engineer at Microsoft who spends his time helping customers understand how to deploy and maintain healthy IT systems.  Ben specialises in helping customers with Windows PowerShell and Virtualisation technologies and can be found helping run 2 blogs: http://blogs.technet.com/mspremuk and http://blogs.technet.com/benp.  Ben co-authored this script with deployment expert Jason Stiff http://blogs.technet.com/jasons. Here is Ben.

 

This post discusses the ServiceImage.ps1 script that is located on the Scripting Guys Script Repository.  This script injects patches to an offline .vhd or .wim file. It is a really useful script and also shows nicely how to wrap regular command line tools in Windows PowerShell. Hopefully this post will help you do the following tasks.

  1. Understand the high level flow of the script
  2. Use Deployment Image Servicing and Management (DISM) and DiskPart command lines
  3. Generate command lines at run time
  4. Run command lines within Windows PowerShell

 

1. High Level Script Flow

Hopefully you will not find the flow of this script too tricky to follow. It really starts just below the section that says Common Processing. However, if you are still coming to grips with scripting this is roughly how the script works:

  1. Prompt the user for the location of the .vhd/.wim and the patches.
  2. Check that there is an image and patches in specified folders.
  3. If the image is a .vhd file:
    1. Generate cmdlines to mount and unmount the vhd with DiskPart.
    2. Generate cmdlines to inject the patches with DISM.
    3. Mount the vhd with Diskpart.
    4. Inject the patches with DISM.
    5. Unmount the vhd with Diskpart.
  4. If the image is a .wim file.
    1. Specify which part of the .wim file to patch.
    2. Mount the .wim file in a directory with DISM.
    3. Generate cmdlines to inject the patches with DISM.
    4. Inject the Patches with DISM.
    5. Unmount the .wim file.

 

2. DISM and DiskPart

DISM is a really powerful command line tool that enumerates, installs, uninstalls, configures, and updates features and packages in Windows images.  More details about how to use DISM can be found in the Microsoft TechNet library or by typing dism /? at the CMD prompt or inside the Windows PowerShell console. Remember that to run dism (which includes obtaining help information about the program) Administrator rights are required.

DiskPart is a very powerful command line tool that can be used to make changes to disks and volumesand mount .vhd files as volume. The problem with using DiskPart is that you cannot give it a simple command line to execute a command.  DiskPart uses the concept of script files that can be used to automate a sequence of tasks. Therefore, to automate the use of DiskPart you must first generate a text file that contains the commands and then call DiskPart with reference to the script file. More details about how to use DiskPart can be found in support article KB300415.

 

3. Generating Command Lines at Runtime

The main challenge in creating this script is generating the command lines that can be invoked. To resolve this, a number of functions have been created:

  • Gendismcmd - used to generate DISM command lines
  • Gendpmountscript - used to generate a script file to be called by DiskPart
  • GendpUnmountscript - used to generate a script file to be called by DiskPart

 

We will not examine all these functions because lots of the ideas are repeated, but let's examine gendismcmd and gendpmountscript.

 

Gendismcmd

This function takes 2 parameters: an array of .cab files (patches) and the path of the image to be patched. The function generates the beginning of the command line and then loops through each of the patches in the array and appends (+=) that patch to the end of the command line. Finally, the function returns the entire command line.

function gendismcmd($cabs,$img)

{

 

          $cmdline = "DISM /image:" + $img + " /Add-Package"

          foreach($cab in $cabs)

          {

                   $cmdline += " /PackagePath:" + "'" + $cab.fullname + "'"

          }

          return $cmdline

 

}

 

Gendpmountscript

This function takes 3 parameters: a temporary drive to mount the .vhd, the path of where you want to create the DiskPart script and the path to the .vhd file. We first create an empty array and then append (+=) command lines to it. After we have finished creating the array full of command lines we use set-content to dump the array in a text file. You will notice we have to create 2 scripts because we have to use DiskPart in 2 stages.

function gendpmountscript($drive, $scriptpath, $vhd)

{

          $dpscript = @()

          $dpscript += ("select vdisk file=" + "'" + $vhd +"'")

          $dpscript += "attach vdisk"

          $dpscript += "exit"

          set-content -path "$scriptpath\dpmount1.txt" -value $dpscript

   

          $dpscript = @()

          $dpscript += ("select vdisk file=" + "'" + $vhd +"'")

          $dpscript += "Select partition 2"

          $dpscript += ("assign letter=" + $drive)

          $dpscript += "exit"

          set-content -path "$scriptpath\dpmount2.txt" -value $dpscript

}

 

4. Running Command Lines from Windows PowerShell

Running command lines from Windows PowerShell is really easy if you use the Windows PowerShell cmdlet Invoke-Expression. I've seen people (including me) struggle using the ampersand (&) operator, get stuck using double quotation marks (") and single quotation marks (') and get frustrated calling other command line tools. If you are not sure, use Invoke-Expression. After you have generated the command line, running the command line is as easy as:

Invoke-Expression $dismcmdline

 

However, you might feel it can be overkill.  If you have a really simple command line, you might not want to use it.  For example, when calling DiskPart in the script we do the following:

diskpart /s "$dpscriptpath\dpmount2.txt"

 

My philosophy is that as soon as the command line is becoming tricky I use the Invoke-Expression cmdlet.

I hope that helped and if you have any questions, get in touch.

 

That wraps up another weekend. Thanks Ben for taking the time to write this blog post for everyone to learn a new tactic.

I invite you to follow me on Twitter or Facebook. If you have any questions, send email to me at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • is there a way to get the script to talk to the local wsus server and get the msu files then mount the vhd/wim and inject the updates?

    thanks

  • imagepatcher.codeplex.com

  • Scripting Guy,

    I'm pulling my hair out trying to patch my Windows 7 WIM with the latest Office 2007 patch.  It keeps getting an error, even if I add to this PS script the .MSP extension at line 115 or leave it as a .CAB.  

    Is this even possible?  And if it isn't, why didn't MS make it possible when they devised DISM?  Do you know if Office 2010 will allow this?  Thanks!

    Here is my line 115 now:

    $cabs = get-childitem -path $sourcepack| where{($_.extension -eq ".msu") -or($_.extension -eq ".cab") -or($_.extension -eq ".msp")} | select fullname

  • Josue - In short, no it is not possible. DISM only supports servicing the OS and not applications, even Office. It was only designed and intended to service OS images and it only recognises .cab and .msu files so the change you made to the script is irrelevant. The only way to ‘service’ Office 2007 or 2010 in a .wim image is to precache the Office local install source (LIS) to the target machine in the form of the MSOCache (usually in C:\MSOCache, hidden), generate the image and then run the Office setup from the LIS when the image is deployed. This way IT admins can “service” the Office precache by dropping the .msp files into the Updates directory under MSOCache. By precaching Office and not installing it enables this servicing capability. This can’t be done however to an image where Office has been completely installed.

    More info on Precaching and installing Office from the LIS can be found at;

    technet.microsoft.com/.../cc179231.aspx - Precache the local installation source for Office 2010

    technet.microsoft.com/.../cc179070.aspx - Install Office 2010 from local installation source

    technet.microsoft.com/.../cc179231(office.12).aspx - Precache the local installation source for the 2007 Office system

    technet.microsoft.com/.../cc179070(office.12).aspx - Run Setup from the local installation source to install the 2007 Office system

    Hope this helps.

  • Hello,

    is there any way to execute commands after calling

    diskpart /s "$dpscriptpath\dpmount2.txt"

    In my script, all commands after that call do not get executed.