Windows Image Tools | Modify VHDX

3 minute read

Continuing onward

In the last post I talked about Unattend.xml and using WindowsImageTools to create a new VHDX with an Unattend.xml and FirstBoot.ps1 for it to run.

using that as a starting point I’m going to replace the do-nothing FirstBoot.ps1 add have it so something more interesting.


You will need the VHDX created at the end of my post on New-UnattendXml and obviously a copy on WindowsImageTools from the PowerShell gallery.

We are going to have our VHDX install 7Zip on first boot so you will need to download a the MSI if your following along.

Creating a new improved FirstBoot.ps1

I downloaded my MSI into G:\filesToInject\PsTemp and now i’m going to replace firstboot.ps1 so it will silently install it and write a log and shutdown

$BetterFirstBootContent = {
   start-process C:\PsTemp\7z1602-x64.msi -ArgumentList '/q','INSTALLDIR="C:\Program Files\7-Zip"' -wait
 New-Item -Path "G:\filesToInject\PsTemp" -Name FirstBoot.ps1 -ItemType 'file' -Value $BetterFirstBootContent -Force

Placing the files in the VHDX

So now we need to get the two fils into our existing VHD. Now I could just run Convert-Wim2VHD again with the same commands and I would get the desired effect, but I dont want to wait for the that process. I also could just mount the VHDX and drag and drop the files into the VHDX, but that would not let me show off another function in WindowsImageTools


Mount-VhdAndRunBlock is one of my favorite tools. It will mount a VHD and run a script block allowing me to manipulate the files inside. This can be used for copying in files, Editing the registry, checking if a file exists and so on.

$ScriptBlock = {
  copy G:\filesToInject\PsTemp\*.* "$($driveletter):\PsTemp\" -Force
Mount-VhdAndRunBlock -VHD 'G:\vhd\2012r2_eval_Core.vhdx' -block $ScriptBlock

Now that we have the better first boot script we need to create a VM to attach the vhdx to. for that I’m going to use Invoke-CreateVmRunAndWait.

Before we create a VM

Lets look at what we need besides the vhdx to use Invoke-CreateVmRunAndWait

help Invoke-CreateVmRunAndWait


    Create a temp vm with a random name and wait for it to stop

    Invoke-CreateVmRunAndWait [-VhdPath] <String> [-VmGeneration] <Int32> [-VmSwitch] <String> [[-vLan] <Int32>]
    [[-ProcessorCount] <Int32>] [[-MemoryStartupBytess] <Int64>] [<CommonParameters>]

So according to the syntax VhdPath, VmGeneration and VmSwitch are required parameters.

Deciding on a VM Generation

While I know what type of VHDX i created, when creating automation script we may not always know if I need a Gen1 or Gen2 VM. So I’m going use Get-VhdPartitionStyle.

Get-VhdPartitionStyle will return a string of either MBR for Master Boot Record or GPT for GUID Partition Table. Generation 1 VM’s are based on BIOS and windows only works with MBR partitions for BIOS based computers. Generation 2 VM’s are based on uEFI architecture and Windows uses GPT partitions by default with uEFI.

$vmGeneration = 2 #default to gen2 
$PartitionStyle = Get-VhdPartitionStyle -vhd 'G:\vhd\2012r2_eval_Core.vhdx'
if ($PartitionStyle -eq 'GPT') 
    $vmGeneration = 2

Getting the switch

We are not actually going to use the network in this example so I’m just going to grab the first VmSwitch

$VmSwitch = (Get-VMSwitch)[0].Name #First switch

Creating the VM

Invoke-CreateVmRunAndWait -VhdPath 'G:\vhd\2012r2_eval_Core.vhdx' -VmGeneration $vmGeneration -VmSwitch $VmSwitch -verbose

We now have a randomly named vm that will run our script and install z7ip. Because it running this during the specialize phase once it’s done it will reboot and end up at the “Press Ctrl+Alt+Del to login” I”m just going to use Hyper-V manager to shut it down.

VERBOSE: [Invoke-CreateVmRunAndWait] : Creating VM 1utbdwad at 08/30/2016 10:57:29
VERBOSE: [Invoke-CreateVmRunAndWait] : VM 1utbdwad stopped
VERBOSE: [Invoke-CreateVmRunAndWait] : VM 1utbdwad Deleted at 08/30/2016 11:08:47

Checking the results

$ScriptBlock = {
  dir "$($driveletter):\Program Files\"
Mount-VhdAndRunBlock -VHD 'G:\vhd\2012r2_eval_Core.vhdx' -block $ScriptBlock
    Directory: K:\Program Files

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----         9/4/2016   9:42 AM                7-Zip
d-----        8/22/2013  10:39 AM                Common Files
d-----        3/21/2014   1:09 PM                Internet Explorer
d-----        8/22/2013  10:39 AM                WindowsPowerShell


So now we have a VM with an app installed, users created and automated the Out of Box Esperance. However a fresh install of Server 2012 R2 does not have WMF 5 and will have a ton of patches that need to be installed.

I will tackle that next.

Leave a Comment

Your email address will not be published. Required fields are marked *