Creating a small footprint, base image Part 2 | Patching and Cleanup via PowerShell

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

Patching and Cleanup via PowerShell

Patching The Core Image

Starting with our Core image we are going to Patch and clean up extra files. At the command prompt I’m going to type: start Powershell Now I could use sconfig to patch, but we are doing this via PowerShell. Using James Oniel’s blog as a reference. I have cut the code down to just the needed commands because the paste keystrokes has a limited size. And sometimes opening a fresh VM does not give me the options for an Advanced Session. I’m going to take the code below and “Paste from clipboard” into PowerShell to download the all patches, including optional patches

$Criteria = "IsInstalled=0 and Type='Software'"
$updateSession = New-Object -ComObject 'Microsoft.Update.Session'
$updates = $updateSession.CreateupdateSearcher().Search($Criteria).Updates
$downloader = $updateSession.CreateUpdateDownloader()   
$downloader.Updates = $updates  
"Downloading $($downloader.Updates.count) updates"
$Result = $downloader.Download() 
if (($Result.Hresult -eq 0) -and (($Result.resultCode -eq 2) -or ($Result.resultCode -eq 3)) ) 
    $updatesToInstall = New-Object -ComObject 'Microsoft.Update.UpdateColl'

    $updates |
    Where-Object -FilterScript {
    } |
    ForEach-Object -Process {
        $null = $updatesToInstall.Add($_)
    $installer = $updateSession.CreateUpdateInstaller()
    $installer.Updates = $updatesToInstall

    "Installing $($installer.Updates.count) updates"
    $installationResult = $installer.Install()
    $Global:counter = -1
    $installer.updates | Format-Table -AutoSize -Property Title, EulaAccepted, @{
        label      = 'Result'
        expression = {
            $resultcode[$installationResult.GetUpdateResult($Global:counter++).resultCode ]


This takes a while. Once it’s done I’m going to reboot the VM and paste the script again until there are 0 updates. It can take 2-3 times depending on the age of your ISO.


Once we have updated Core with every patch, it’s time to remove unneeded file. If you going install any features, special software or setup boot time scripts. now is the time to do so. I’m not going to this time. I will start by removing the source file for adding windows features. We will be creating a WIM from our GUI image for that purpose.

Get-WindowsFeature | where-object{$_.Installed -eq 0 -and $_.InstallState -eq 'Available'} | uninstall-windowsfeature -remove
dism /online /cleanup-image /StartComponentCleanup /ResetBase
defrag c: /UVX
  • First we removes the source from SxS
  • Second we remove any “superseded” items in SxS. So any older patches that have been updated with a newer one will be removed.
  • Third, we defrag the drive and consolidate free space (this probably is not necessary)

Preparing and Patching the GUI Image

With that done lets move to the GUI VM. I’m going to first make sure every feature is available

get-windowsfeature | Where InstallState -eq Removed
Display Name                                            Name                       Install State
 ------------                                            ----                       -------------
     [ ] .NET Framework 3.5 (includes .NET 2.0 and 3.0)  NET-Framework-Core               Removed
     [ ] Windows PowerShell 2.0 Engine                   PowerShell-V2                    Removed

So .NET 3.5 and PowerShell 2.0 are removed. If your never going to use them that is ok. I find that .NET 3.5 is frequently needed so to be safe I’m going to add them both to SxS.

get-windowsfeature | Where InstallState -eq Removed | Install-WindowsFeature -Source D:\sources\sxs
Success Restart Needed Exit Code      Feature Result
 ------- -------------- ---------      --------------
 True    No             Success        {.NET Framework 3.5 (includes .NET 2.0 and...
 WARNING: Windows automatic updating is not enabled. To ensure that your newly-installed role or feature is
 automatically updated, turn on Windows Update.

Because we want the WIM to be a source when adding to VM’s created from our Core Image, and we want the feature to be patched from the get-go. They need to be installed and patched on this VM. If they are not all installed, that is OK, it will still work as a source so long as the installed features in the WIM are the same version or newer then the VM your adding the feature to.So if we don’t mind patching the new items after the fact when we can just remove the GUI, patch and move on. I’m going to keep the GUI, and add some features I know I will be using in my lab. (now before you go and think I’ll just run Get-WindowsFeature | Add-WindowsFeature, that does not work because some features are mutually exclusive)

Install-WindowsFeature DNS, DHCP, File-Services, RSAT -IncludeAllSubFeature
Install-WindowsFeature AD-Domain-Services, AD-Certificate, ADCS-Cert-Authority

There will be a few warnings but everything should install Now I’m going to use the same code used on the Core VM to run updates. Once that is patched and shutdown it’s ready for Part 3.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s