New-WindowsImage -size small | Test-Lab
One of the time consuming steps to deploying new VMs is the time spend managing Images and and applying patches. I’m not big on Golden images. I tend to use a fully patched VHDX or VMDK and let DSC handle the configuration and software. This is not the fastest, and at scale you need to create more then one image based on what saves the most time. (IIS, SQL, Exchange, etc…). In this series, I’m going to go over how I create a a baseline image of 2012r2 with PowerShell. Also because Install-WindowsFeature has issues when the target VM has been patched, we are also going to create a fully patched WIM to use as -source.
Blogs in this Series
- Part 1 VHDX from ISO
- Part 2 Patching and Cleanup via PowerShell
- Part 3 SysPrep and Compacting Images
- Part 4 Bringing it all together with automation
SysPrep and Compacting Images
Sysprep the Core Image
Now I need to sysprep the core image before it’s usable. but I don’t want to repeat this whole process from scratch next month. So I’m going to copy the VHDX and create a new VM that I will sysprep. So back to the host
copy .\CorePatched.vhdx CoreSysprep.vhdx new-vm -Name CoreSysprep -MemoryStartupBytes 512MB -BootDevice VHD -VHDPath G:\temp\CoreSysprep.vhdx -Generation 2 -SwitchName TestLab
Name State CPUUsage(%) MemoryAssigned(M) Uptime Status ---- ----- ----------- ----------------- ------ ------ CoreSysprep Off 0 0 00:00:00 Operating normally
start-vm coreSysprep vmconnect localhost coresysprep
Now on the new VM run
c:\windows\System32\Sysprep\sysprep.exe /quiet /oobe /generalize /shutdown
Once the VM is shutdown it’s time to look at our progress on the host.
Compacting the Images
dir core*
Directory: G:\temp Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 5/14/2015 9:20 PM 5641338880 CoreFromIso.vhdx -a---- 5/15/2015 4:10 PM 9265217536 CorePatched.vhdx -a---- 5/15/2015 4:27 PM 9265217536 CoreSysprep.vhdx
Wow, almost 10 gigs. not exactly small. Well we are not done. We need to convert both our images to WIM and the core one back to VHDX. The transfer from VHDX to WMI and back to VHDX does a better job then using a utility to write all 0’s to the disk and then compact it. This is partly due to the swap file. Back on the host we need to create a folder to mount the VHDX into and then create the WIM.
mkdir g:\mount
Directory: G:\ Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 5/15/2015 8:54 PM mount
Mount-WindowsImage -ImagePath G:\temp\CoreSysprep.vhdx -Path G:\Mount -Index 1
Path : G:\Mount Online : False Restart Needed : False
New-WindowsImage -CapturePath G:\Mount -Name 2012r2_Core -ImagePath G:\Temp\2012r2Core.wim -Description "2012r2 Core Patched May 2015" -Verify
LogPath : C:\Windows\Logs\DISM\dism.log
Dismount-WindowsImage -Path G:\mount -Discard
LogPath : C:\Windows\Logs\DISM\dism.log
Mount-WindowsImage -ImagePath G:\temp\GUIPatched.vhdx -Path G:\Mount -Index 1
Path : G:\Mount Online : False Restart Needed : False
New-WindowsImage -CapturePath G:\Mount -Name 2012r2_Source -ImagePath G:\Temp\2012r2Source.wim -Description "2012r2 Source Patched May 2015" -Verify
LogPath : C:\Windows\Logs\DISM\dism.log
Dismount-WindowsImage -Path G:\mount -Discard
LogPath : C:\Windows\Logs\DISM\dism.log
Converting the Core WIM back to VHDX is going to require that we make a change to Convert-WindowsImage.ps1. The logic of the script looks at the WIM and if there is only one image it uses that one. But it does so using metadata that does not exist when a WIM is created using New-WindowsImage. So down on line 4020 we nee to comment out a few lines
# If there's only one image in the WIM, just selected that. #if ($openWim.Images.Count -eq 1) { # $Edition = $openWim.Images[0].ImageFlags # $openImage = $openWim[$Edition] #} else { if ([String]::IsNullOrEmpty($Edition)) { Write-W2VError "You must specify an Edition or SKU index, since the WIM has more than one image." Write-W2VError "Valid edition names are:" $openWim.Images | %{ Write-W2VError " $($_.ImageFlags)" } throw } if ([Int32]::TryParse($Edition, [ref]$null)) { $openImage = $openWim[[Int32]$Edition] } else { $openImage = $openWim[$Edition] } #}
Now it will work using -edition 1 (There is only one OS inside the WIM)
G:\temp\Convert-WindowsImage.ps1 -SourcePath G:\temp\2012r2Core.wim -VHDPath G:\temp\2012R2Core.vhdx -SizeBytes 40gb -VHDType Dynamic -VHDFormat VHDX -VHDPartitionStyle GPT -Edition 1 -Verbose
Windows(R) Image to Virtual Hard Disk Converter for Windows(R) 8 Copyright (C) Microsoft Corporation. All rights reserved. Version 6.3.9600.7.amd64fre.fbl_core1_hyp_dev(mikekol).140217-3000 Release to Web VERBOSE: isUserAdmin? True VERBOSE: isWindows8? True VERBOSE: Temporary VHDX path is : G:\temp\faf6d4fa-fb64-41d7-95f3-5a1ab3825940.vhdx INFO : Image 1 selected ()... INFO : Creating sparse disk... INFO : Attaching VHDX... INFO : Disk initialized with GPT... INFO : Disk partitioned with two Volumes... INFO : System Volume formatted (with DiskPart)... INFO : Boot Volume formatted (with Format-Volume)... INFO : Access path (L:\) has been assigned to the System Volume... INFO : Access path (M:\) has been assigned to the Boot Volume... INFO : Applying image to VHDX. This could take a while... INFO : Signing disk... INFO : Image applied. Making image bootable... VERBOSE: Running bcdboot.exe M:\Windows /s L:\ /v /f UEFI VERBOSE: Return code was 0. INFO : Drive is bootable. Cleaning up... INFO : Closing VHDX... INFO : Closing Windows image... INFO : Done.
dir *core* | sort -Property LastWriteTime
Directory: G:\temp Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 5/14/2015 9:20 PM 5641338880 CoreFromIso.vhdx -a---- 5/15/2015 4:10 PM 9265217536 CorePatched.vhdx -a---- 5/15/2015 8:00 PM 9265217536 CoreSysprep.vhdx -a---- 5/15/2015 9:39 PM 2376356767 2012r2Core.wim -a---- 5/15/2015 10:15 PM 4936695808 2012R2Core.vhdx
dir *gui*, *source* | sort -Property LastWriteTime
Directory: G:\temp Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 5/14/2015 9:29 PM 9130999808 GUIFromIso.vhdx -a---- 5/15/2015 3:56 PM 16143876096 GuiPatched.vhdx -a---- 5/15/2015 10:03 PM 6153716725 2012r2Source.wim
As you can see our Source.WIM is 6.1Gig and our Core VHDX is only 4.9gig. fully patched it’s smaller then the baseline Core image. We can place the WIM on a share that is accessible to new VM’s and they can use that for adding features. In another blog I will go over how to update the WIM file automatically each month.