Hyper-V virtual machines on Windows 11 create .vhdx files that grow over time as you install software, update the OS, or copy files. Even after you delete data inside the guest VM, the host-side .vhdx file does not automatically shrink back to reclaim that free space. This leads to wasted disk space on your host computer, sometimes tens of gigabytes per VM. This article explains how to set up automatic compaction of .vhdx files after each VM shutdown, using a PowerShell script triggered by a scheduled task.
Key Takeaways: Auto-Compacting Hyper-V VHDX Files After Use
- Optimize-VHD -Path C:\VMs\VMName.vhdx -Mode Full: The PowerShell cmdlet that compacts a dynamically expanding .vhdx file by reclaiming unused space.
- Task Scheduler > Create Task > Trigger > On disconnect from user session: The trigger that runs the compaction script each time you shut down or disconnect from a VM.
- Mount-VHD and Dismount-VHD: Cmdlets needed to attach the .vhdx for optimization and detach it after the operation completes.
How Hyper-V VHDX Dynamic Expansion and Compaction Work
Hyper-V offers two main disk formats: fixed-size and dynamically expanding. A dynamically expanding .vhdx file starts at a small size, typically a few megabytes, and grows as the guest OS writes data. When you delete files inside the VM, the .vhdx file does not shrink automatically. The space is marked as free inside the guest, but the host-side file remains at its largest size.
The compaction process uses the Optimize-VHD PowerShell cmdlet. This cmdlet reads the .vhdx file and rewrites it, omitting the sectors that the guest OS has marked as free. The result is a smaller host-side file. The compaction requires the .vhdx to be mounted on the host during the operation. After compaction, the .vhdx must be dismounted so the VM can access it normally.
For this automation, you need a VM that is shut down or saved. A running VM cannot have its disk compacted. The script will mount the .vhdx, run Optimize-VHD, then dismount it. The scheduled task triggers after the VM stops, which happens when you close the VM connection or shut down the guest.
Steps to Set Up Automatic VHDX Compaction on Windows 11
This process uses a PowerShell script and a Task Scheduler trigger. The script runs after you disconnect from a VM session, which typically coincides with VM shutdown. The steps below assume you have Hyper-V enabled and at least one VM with a dynamically expanding .vhdx file.
- Open PowerShell as Administrator
Press the Windows key, type PowerShell, right-click Windows PowerShell in the results, and select Run as administrator. Click Yes in the User Account Control prompt. - Create the compaction script
In the PowerShell window, run the following command to create a new script file in a folder of your choice. Replace C:\Scripts with your preferred path.New-Item -ItemType Directory -Path "C:\Scripts" -ForceThen create the script file with this content. Copy and paste the entire block into PowerShell:
@"
param([string]\$VmName)# Get the VM object
\$VM = Get-VM -Name \$VmName -ErrorAction SilentlyContinue
if (-not \$VM) { exit }# Get all hard disk drives attached to this VM
\$Disks = \$VM.HardDrives | Where-Object { \$_.Path -like "vhdx" }
if (-not \$Disks) { exit }foreach (\$Disk in \$Disks) {
\$VhdPath = \$Disk.Path
try {
Mount-VHD -Path \$VhdPath -ReadOnly
Optimize-VHD -Path \$VhdPath -Mode Full
Dismount-VHD -Path \$VhdPath
} catch {
# Log error if needed
}
}
"@ | Out-File -FilePath "C:\Scripts\Compact-VHD.ps1" -Encoding utf8This script accepts a VM name as a parameter, finds all .vhdx disks attached to that VM, mounts each one as read-only, runs Optimize-VHD, and dismounts it.
- Open Task Scheduler
Press Windows + R, type taskschd.msc, and press Enter. This opens the Task Scheduler console. - Create a new task for the compaction script
In the right-hand Actions pane, click Create Task. The Create Task dialog opens. - Name the task and configure security options
On the General tab, enter a name like Compact Hyper-V VHDX on Disconnect. Under Security options, select Run whether user is logged on or not. Check Run with highest privileges. This gives the script permission to mount and optimize VHD files. - Set the trigger to fire on user session disconnect
Go to the Triggers tab and click New. In the Begin the task dropdown, select On disconnect from user session. Under Settings, leave the default Any user. Click OK. This trigger fires each time you close the VM connection window, which usually happens after you shut down or save the VM. - Configure the action to run the PowerShell script
Go to the Actions tab and click New. In the Action dropdown, ensure Start a program is selected. In the Program/script box, type powershell.exe. In the Add arguments box, paste the following:-ExecutionPolicy Bypass -File "C:\Scripts\Compact-VHD.ps1" -VmName "YourVMName"Replace YourVMName with the exact name of your Hyper-V VM. If you have multiple VMs, create separate tasks for each VM. Click OK.
- Set conditions and settings
Go to the Conditions tab. Uncheck Start the task only if the computer is on AC power if you want it to run on battery. On the Settings tab, check Allow task to be run on demand. Set If the task fails, restart every minute to 1 and Attempt to restart up to to 3. This helps if the .vhdx is temporarily locked. Click OK to save the task. Enter your password if prompted. - Test the automation
Close the VM connection window for the VM you configured. Wait 30 to 60 seconds. Open File Explorer and navigate to the .vhdx file location. Right-click the .vhdx file, select Properties, and note the Size on disk. Compare it with the previous size. It should be smaller if the VM had free space inside.
Common Issues and Limitations of Auto-Compaction
The .vhdx file is locked and cannot be mounted
If the VM is still running or in a saved state with the disk attached, mounting the .vhdx as read-only will fail. The script includes a try-catch block that will exit silently if Mount-VHD fails. Ensure the VM is fully shut down or saved before the task runs. The disconnect trigger usually works because closing the VM connection window triggers the event after the VM has stopped.
Optimize-VHD takes too long and the next VM start is delayed
Compacting a large .vhdx file, such as one over 100 GB, can take several minutes. If you start the VM before the script finishes, the compaction will fail because the .vhdx is still mounted. To avoid this, set the task to run with a delay. In the Trigger properties, set a delay of 5 to 10 minutes. This gives the script time to complete before you might try to start the VM again.
The script does not run after disconnecting from a VM
The disconnect trigger fires for any user session disconnect, not just VM connection windows. If you disconnect from a Remote Desktop session or lock your workstation, the task will also run. This is harmless because the script checks for a VM name and will exit if the VM does not exist. To prevent unnecessary runs, create a separate task for each VM and use a condition that checks if the VM is off before running the script.
Compaction does not shrink the file as much as expected
Optimize-VHD only reclaims space that the guest OS has marked as free. If the guest file system is fragmented or if there are large files that were deleted but the space is not yet marked as free, compaction will not recover that space. Run Optimize-Volume -DriveLetter C -ReTrim -Verbose inside the guest VM before shutting it down. This command tells the guest to trim unused blocks, which makes them available for the host to reclaim.
Manual Compaction vs Automatic Script: Key Differences
| Item | Manual Compaction | Automatic Script |
|---|---|---|
| Trigger | You run Optimize-VHD manually when needed | Runs automatically after VM disconnect |
| VM state required | VM must be off or saved | VM must be off (triggered by disconnect) |
| Effort per VM | Remember to run compaction periodically | Set up once per VM |
| Risk of data loss | Low if you follow proper steps | Low, script mounts as read-only |
| Recovery of space | Immediate after command completes | Delayed until after VM shutdown |
| Best for | One-time cleanup or infrequent use | Frequent VM users who need consistent space recovery |
Manual compaction gives you full control and is simpler to perform on an occasional basis. The automatic script is better for users who run VMs daily and want to reclaim space without remembering to run the command. Both methods use the same Optimize-VHD cmdlet and produce identical results.
The automatic script mounts the .vhdx as read-only, so there is no risk of data corruption. The manual method typically requires you to shut down the VM and run the command directly on the host. The script also logs any errors to the PowerShell console, which you can redirect to a file if you need a record of compaction activity.
After setting up the automatic script, you can monitor its execution by checking the Task Scheduler history. Right-click the task and select History to see when it last ran and whether it succeeded. If the task fails, the restart setting will attempt it up to three times. For persistent failures, check the script path and ensure the VM name matches exactly.
You now have a fully automated solution that shrinks your Hyper-V .vhdx files after each VM use. This keeps your host drive from filling up with unused space. For advanced control, modify the script to compact only VHDX files that exceed a certain size, or add logging to a text file. Combine this with periodic in-guest trimming using Optimize-Volume -ReTrim for maximum space recovery.