Skip to main content

How to Delete a Hyper-V VM Snapshot in Batch Script

Hyper-V checkpoints (snapshots) are powerful safety nets, but they accumulate cost over time. Every checkpoint creates differencing disk files (.avhdx) that grow as the VM writes data, degrading I/O performance and consuming significant storage. Deleting old checkpoints that are no longer needed reclaims disk space and improves VM performance by merging the differencing disks back into the base VHDX.

In this guide, we will explore how to delete Hyper-V VM checkpoints from a Batch Script using PowerShell, including single deletion, bulk cleanup, and retention policy automation.

Understanding What Happens When You Delete a Checkpoint

When you delete a checkpoint:

  1. The checkpoint metadata is removed from Hyper-V Manager.
  2. The associated differencing disk (.avhdx) is merged into the parent disk.
  3. The merge happens in the background. For running VMs, there is no downtime.
  4. The merge may take significant time for large differencing disks (minutes to hours for multi-GB files).
  5. The VM continues operating normally during the merge.
info

Deleting a checkpoint does NOT revert the VM to a previous state. It merges the checkpoint's changes into the base disk, making those changes permanent. To revert, use Restore-VMCheckpoint instead.

Method 1: Deleting a Specific Checkpoint by Name

@echo off
setlocal

set "vm_name=WebServer-01"
set "cp_name=Pre-Update [2024-01-15 09:30]"

:: Verify Admin
net session >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Administrator privileges required.
pause
exit /b 1
)

echo Deleting checkpoint "%cp_name%" from "%vm_name%"...

powershell -noprofile -command ^
"$cp = Get-VMCheckpoint -VMName '%vm_name%' -ErrorAction SilentlyContinue | ^
Where-Object { $_.Name -eq '%cp_name%' }; ^
if (-not $cp) { exit 1 }; ^
$cp | Remove-VMCheckpoint -Confirm:$false"

if %errorlevel%==0 (
echo [SUCCESS] Checkpoint deleted. Differencing disk merge is in progress.
) else (
echo [ERROR] Checkpoint not found or deletion failed.
)

pause

Method 2: Deleting All Checkpoints for a VM

To clean up all checkpoints at once:

@echo off
setlocal

set "vm_name=DatabaseVM"

net session >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Admin required.
pause
exit /b 1
)

:: Count existing checkpoints
set "count=0"
for /f "delims=" %%C in ('powershell -noprofile -command ^
"@(Get-VMCheckpoint -VMName '%vm_name%' -ErrorAction SilentlyContinue).Count"') do set "count=%%C"

echo VM: %vm_name%
echo Checkpoints found: %count%

if "%count%"=="0" (
echo [INFO] No checkpoints to delete.
pause
exit /b 0
)

echo.
set /p "confirm=Delete ALL %count% checkpoints? Type YES: "
if /i not "%confirm%"=="YES" (
echo [CANCELLED]
pause
exit /b 0
)

echo Deleting all checkpoints...
powershell -noprofile -command ^
"Get-VMCheckpoint -VMName '%vm_name%' | Remove-VMCheckpoint -Confirm:$false"

echo [OK] All checkpoints deleted. Merging in background.
pause

Method 3: Interactive Checkpoint Deletion

@echo off
title Checkpoint Cleanup Tool
setlocal enabledelayedexpansion

net session >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Run as Administrator.
pause
exit /b 1
)

set /p "vm_name=Enter VM name: "

:list
cls
echo =============================================
echo CHECKPOINT CLEANUP: %vm_name%
echo =============================================
echo.

set "count=0"
for /f "delims=" %%L in ('powershell -noprofile -command ^
"Get-VMCheckpoint -VMName '%vm_name%' -ErrorAction SilentlyContinue | Sort-Object CreationTime | ForEach-Object { Write-Host ($_.Name + ' | Created: ' + $_.CreationTime) }"') do (
set /a count+=1
echo !count!. %%L
)

if "!count!"=="0" (
echo No checkpoints found.
pause
exit /b 0
)

echo.
echo Options:
echo [1-!count!] Delete specific checkpoint
echo [A] Delete ALL checkpoints
echo [Q] Quit
echo.
set /p "choice=Select: "

if /i "!choice!"=="Q" exit /b 0
if /i "!choice!"=="A" (
set /p "c2=Delete ALL? Type YES: "
if /i "!c2!"=="YES" (
powershell -noprofile -command "Get-VMCheckpoint -VMName '%vm_name%' | Remove-VMCheckpoint -Confirm:$false"
echo [OK] All deleted.
pause
)
goto list
)

:: Validate numeric input
set /a "sel=!choice!" 2>nul
if !sel! lss 1 (
echo [ERROR] Invalid selection.
pause
goto list
)
if !sel! gtr !count! (
echo [ERROR] Invalid selection.
pause
goto list
)

:: Delete specific checkpoint by index
powershell -noprofile -command ^
"$cps = @(Get-VMCheckpoint -VMName '%vm_name%' -ErrorAction SilentlyContinue | Sort-Object CreationTime); ^
$i = !sel! - 1; ^
if ($i -ge 0 -and $i -lt $cps.Count) { ^
Write-Host ('Deleting: ' + $cps[$i].Name); ^
$cps[$i] | Remove-VMCheckpoint -Confirm:$false; ^
Write-Host '[OK] Deleted.' ^
} else { ^
Write-Host '[ERROR] Checkpoint no longer exists.' ^
}"

pause
goto list

Method 4: Age-Based Cleanup (Retention Policy)

Automatically delete checkpoints older than a specified number of days:

@echo off
setlocal

set "vm_name=AppServer-01"
set "max_age_days=7"

net session >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Admin required.
pause
exit /b 1
)

echo Cleaning up checkpoints older than %max_age_days% days for "%vm_name%"...
echo.

powershell -noprofile -command ^
"$cutoff = (Get-Date).AddDays(-%max_age_days%); ^
$old = @(Get-VMCheckpoint -VMName '%vm_name%' -ErrorAction SilentlyContinue | ^
Where-Object { $_.CreationTime -lt $cutoff }); ^
if ($old.Count -eq 0) { ^
Write-Host 'No checkpoints older than %max_age_days% days found.' ^
} else { ^
Write-Host ('Found ' + $old.Count + ' old checkpoint(s):'); ^
$old | ForEach-Object { ^
Write-Host (' Deleting: ' + $_.Name + ' (Created: ' + $_.CreationTime + ')'); ^
$_ | Remove-VMCheckpoint -Confirm:$false ^
}; ^
Write-Host '[OK] Cleanup complete.' ^
}"

pause

Method 5: Cleanup All VMs on the Host

For server-wide checkpoint management:

@echo off
setlocal

set "max_age_days=14"

net session >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Admin required.
pause
exit /b 1
)

echo =============================================
echo HOST-WIDE CHECKPOINT CLEANUP
echo Removing checkpoints older than %max_age_days% days
echo =============================================
echo.

powershell -noprofile -command ^
"$cutoff = (Get-Date).AddDays(-%max_age_days%); ^
$total = 0; ^
Get-VM | ForEach-Object { ^
$vm = $_.Name; ^
$old = @(Get-VMCheckpoint -VMName $vm -ErrorAction SilentlyContinue | ^
Where-Object { $_.CreationTime -lt $cutoff }); ^
if ($old.Count -gt 0) { ^
Write-Host ('--- ' + $vm + ': ' + $old.Count + ' old checkpoint(s) ---'); ^
$old | ForEach-Object { ^
Write-Host (' Deleting: ' + $_.Name); ^
$_ | Remove-VMCheckpoint -Confirm:$false; ^
$total++ ^
} ^
} ^
}; ^
Write-Host ''; ^
Write-Host ('[TOTAL] Deleted ' + $total + ' checkpoints.')"

pause

Deleting a Checkpoint Subtree

A checkpoint may have child checkpoints beneath it. To delete a checkpoint and all its descendants:

@echo off
set "vm_name=TestVM"
set "cp_name=Root Checkpoint"

powershell -noprofile -command ^
"Get-VMCheckpoint -VMName '%vm_name%' -ErrorAction SilentlyContinue | ^
Where-Object { $_.Name -eq '%cp_name%' } | ^
Remove-VMCheckpoint -IncludeAllChildCheckpoints -Confirm:$false"

echo [OK] Checkpoint and all children deleted.
pause

Common Mistakes

The Wrong Way: Manually Deleting AVHDX Files

:: WRONG - Never delete differencing disk files directly
del "D:\Hyper-V\VHDs\WebServer-01_GUID.avhdx"

Output Concern: Manually deleting .avhdx files bypasses the Hyper-V merge process and corrupts the VM's disk chain. The VM will fail to start with an error about missing parent disks. Always use Remove-VMCheckpoint to properly merge and clean up differencing disks.

The Wrong Way: Confusing Delete with Restore

:: WRONG mental model:
:: "Delete checkpoint" does NOT revert the VM to before the checkpoint
:: It KEEPS the current state and removes the ability to go back

Deleting a checkpoint merges its changes into the base disk, making them permanent. It does not undo those changes. To revert, use Restore-VMCheckpoint before deleting.

Best Practices

  1. Implement a retention policy: Automatically delete checkpoints older than 7-14 days.
  2. Never delete .avhdx files manually: Always use Remove-VMCheckpoint for proper disk merging.
  3. Monitor merge progress: Large merges can take hours. Monitor disk I/O during the merge.
  4. Delete from oldest to newest: While Hyper-V handles the merge order, cleaning from oldest first is logically cleaner.
  5. Schedule cleanup during low activity: The merge process consumes disk I/O. Run cleanup during off-peak hours.

Conclusion

Deleting Hyper-V VM checkpoints from a Batch Script is handled by PowerShell's Remove-VMCheckpoint cmdlet, which removes the checkpoint metadata and triggers a background merge of the differencing disk into its parent. Implementing age-based retention policies ensures checkpoints do not accumulate indefinitely, while interactive tools give administrators granular control over which checkpoints to preserve or remove. The critical takeaway is that deletion means making changes permanent, not reverting them, and that disk files must never be deleted manually.