When preparing virtual hard disks (VHDX) for optimization in Windows Server 2012, many administrators want to use the built-in Disk Cleanup utility (cleanmgr.exe
) to remove unnecessary files before running Optimize-VHD
. However, Microsoft bundles this tool with the Desktop Experience feature, which brings unwanted dependencies like Media Foundation and Ink and Handwriting components.
You can manually extract and register the Disk Cleanup components without installing the full Desktop Experience:
# Copy required files from Windows installation media
robocopy "D:\sources\install.wim\Windows\WinSxS" "C:\Windows\WinSxS" /E /COPYALL /R:0 /W:0 /NP /XO /XD amd64_microsoft-windows-a..ence-mitigations-c1_* amd64_microsoft-windows-a..ence-mitigations-c2_*
# Register the DLL
regsvr32 /s C:\Windows\WinSxS\amd64_microsoft-windows-cleanmgr_*\cleanmgr.exe
# Create the scheduled task XML (required for proper execution)
$cleanmgrXML = @'
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Description>Disk Cleanup</Description>
</RegistrationInfo>
<Principals>
<Principal id="Author">
<UserId>S-1-5-18</UserId>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>P3D</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>C:\Windows\System32\cleanmgr.exe</Command>
</Exec>
</Actions>
</Task>
'@
$cleanmgrXML | Out-File "C:\Windows\System32\cleanmgr.xml"
For those preferring pure PowerShell, here's a function that mimics Disk Cleanup's functionality:
function Invoke-VHDXCleanup {
param (
[Parameter(Mandatory=$true)]
[string]$DriveLetter
)
# Clear Windows Update cache
if (Test-Path "$DriveLetter\Windows\SoftwareDistribution\Download") {
Remove-Item "$DriveLetter\Windows\SoftwareDistribution\Download\*" -Recurse -Force
}
# Clear temporary files
if (Test-Path "$DriveLetter\Windows\Temp") {
Remove-Item "$DriveLetter\Windows\Temp\*" -Recurse -Force
}
# Clear user temp files (for all users)
Get-ChildItem "$DriveLetter\Users" | ForEach-Object {
$userTemp = "$($_.FullName)\AppData\Local\Temp"
if (Test-Path $userTemp) {
Remove-Item "$userTemp\*" -Recurse -Force
}
}
# Clear IIS logs if present
if (Test-Path "$DriveLetter\inetpub\logs") {
Remove-Item "$DriveLetter\inetpub\logs\*" -Recurse -Force
}
# Clear .NET temporary files
if (Test-Path "$DriveLetter\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files") {
Remove-Item "$DriveLetter\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\*" -Recurse -Force
}
}
After cleaning the disk, run the VHDX optimization:
# First check fragmentation level
Get-VHD -Path "C:\VMs\server2012.vhdx" | Select-Object Path,FileSize,Size,@{Name="Fragmentation";Expression={[math]::Round(($_.FileSize/$_.Size)*100,2)}}
# Then optimize
Optimize-VHD -Path "C:\VMs\server2012.vhdx" -Mode Full
To confirm the cleanup was effective:
# Check disk space before and after
$before = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='C:'" | Select-Object Size,FreeSpace
# Run cleanup...
$after = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='C:'" | Select-Object Size,FreeSpace
$spaceRecovered = ($after.FreeSpace - $before.FreeSpace)/1GB
Write-Host "Recovered $([math]::Round($spaceRecovered,2)) GB of disk space"
When preparing virtual machine VHDX files for optimization with Optimize-VHD
, admins often need to reclaim space by removing temporary files. While Microsoft's Disk Cleanup
(cleanmgr.exe) is ideal for this, it's frustratingly tied to the bloated Desktop Experience feature on Windows Server 2012.
Installing Desktop Experience introduces unnecessary components like:
- Media Foundation (increases attack surface) - Ink and Handwriting Services (irrelevant for servers) - Additional GUI components (wastes resources)
You can extract just the Disk Cleanup functionality by manually registering its components:
# From elevated PowerShell: Copy-Item "$env:windir\winsxs\amd64_microsoft-windows-cleanmgr_*" "C:\Temp\CleanMgr" Copy-Item "$env:windir\winsxs\amd64_microsoft-windows-cleanmgr.resources_*" "C:\Temp\CleanMgr\en-US" regsvr32 /s "C:\Temp\CleanMgr\cleanmgr.exe" regsvr32 /s "C:\Temp\CleanMgr\cleanmgr.exe.mui"
Combine this with your optimization workflow:
# PowerShell script example function Optimize-VHDX { param([string]$VHDPath) # Mount VHDX $disk = Mount-VHD -Path $VHDPath -Passthru $partition = Get-Partition -DiskNumber $disk.DiskNumber | Where-Object {$_.Type -eq 'Basic'} # Run Disk Cleanup Start-Process "cleanmgr" -ArgumentList "/sagerun:1" -Wait # Dismount and optimize Dismount-VHD -Path $VHDPath Optimize-VHD -Path $VHDPath -Mode Full }
Create custom cleanup profiles by first running:
cleanmgr /sageset:1
Then reference this config in scripts using /sagerun:1
. This avoids interactive prompts during automation.
For completely headless environments, consider these PowerShell alternatives:
# Delete temporary files Remove-Item "$env:windir\Temp\*" -Recurse -Force Remove-Item "$env:TEMP\*" -Recurse -Force # Clear Windows Update cache Stop-Service wuauserv Remove-Item "$env:windir\SoftwareDistribution\Download\*" -Recurse -Force Start-Service wuauserv
After cleanup, verify reclaimed space with:
Get-VHD -Path "C:\VMs\server.vhdx" | Select-Object Path,FileSize,Size