ConfigMgr Console

Hyper-V to Automate VHDx Optimization

Topics: ConfigMgr Console

Using Hyper-V to Automate VHDx Optimization

Another Hyper-V Post after I ran out of storage on my Windows 10 Hyper-V Host.

TLDR: Full Script on GitHub

The idea here was to create a script to run that would clean up each of the VMs (but only the ones I wanted), shut them down, compact the Virtual Hard Drive File (VHDx File), the boot up the VM again. I had started doing this manually after finding a blog post from Garth.

High Level Overview

Get all VMs on Host that match criteria that the VM is current On and that the VM Name (In Hyper-V) does not match Server.

$VMs = Get-VM | Where-Object {$_.State -eq "Running" -and $_.Name -notmatch "Server"}

Once we have that infomration, we go a bit deeper by looping through each one using a ForEach

Foreach ($VM in $VMs)
    if (Get-VMSnapshot -VMName $VM.Name){
        Write-Host "$($VM.Name) has SnapShots, remove first then re-run" -ForegroundColor Yellow

        #More Code...

First we check if there is a snapshot, if there is, we move on to the next one. Sure we could automatically delete the snapshots too.. but they might be there for a reason, and I don’t want to willy nilly delete them all.

If they don’t have a snap shot… I get the IP Address assigned to the VM

$IPAddress = (Get-VM -Name "$($VM.Name)" | Select -ExpandProperty networkadapters).IPAddresses | Select-Object -First 1
Write-Host "$($VM.Name) | $IPAddress" -ForegroundColor Magenta

Then as long as your DNS is setup properly, you can get the Machine’s Windows Name from DNS. I then have it run a remote powershell script to do cleanup. The Script is in the $RemoteScript variable.

$VMNetName = [System.Net.Dns]::GetHostByAddress($IPAddress).Hostname
Write-Host "$($VM.Name) = $VMNetName | $IPAddress" -ForegroundColor Magenta
Invoke-Command -ScriptBlock $RemoteScript -ComputerName $VMNetName -ErrorAction Stop

Remote Script Example which deletes Branch Cache & CCM Cache Items.

$RemoteScript = 
Clear-BCCache -Force
$CMObject = New-Object -ComObject 'UIResource.UIResourceMgr' 
$CMCacheObjects = $CMObject.GetCacheInfo() 
# Delete Cache item 
$CMCacheObjects.GetCacheElements() | ForEach-Object { 

Once that is done, we confirm that it has a VHDx and not a passthrough drive then shut down the VM

$VHDXPaths = $VM.HardDrives.path | Where-Object {$VM.HardDrives.DiskNumber -eq $null}
        if ($VHDXPaths){
            Get-VM -Name $VM.Name | Stop-VM -Force

Once the VM is off, we optimize it.

ForEach ($VHDXPath in $VHDXPaths)
  $SizeBefore = (Get-Item -Path $VHDXPath).length
  Write-Host " Size of $((Get-Item -Path $VHDXPath).Name) = $($SizeBefore/1GB) GB" -ForegroundColor Green
  Write-Host " Optimzing VHD $VHDXPath on $($VM.Name)" -ForegroundColor Green
  Optimize-VHD -Path $VHDXPath -Mode Full
  $SizeAfter = (Get-Item -Path $VHDXPath).length
  $Diff = $SizeBefore - $SizeAfter
  Write-Host " Size After: $($SizeAfter/1GB) GB | Saving $($Diff /1GB) GB" -ForegroundColor Green
Get-VM -Name $VM.Name | Start-VM

When running the script, you’ll see output like, I’ve boxed the space savings. In this example I’ve only compacted the VHDx files, I didn’t do any cleanup first.

HyperV Optimize 01

Full Script on GitHub, grab it and edit it for own needs.

To watch it in motion, check out the script running on YouTube.

HyperV Optimize 01

Additional Hyper-V Posts

About Recast Software

1 in 3 organizations using Microsoft Configuration Manager rely on Right Click Tools to surface vulnerabilities and remediate quicker than ever before.
Download Free Tools
Request Pricing

Back to Top