How to Get the Replication Status of a Hyper-V VM in Batch Script
Hyper-V Replica is a disaster recovery feature that continuously replicates virtual machines from a primary host to a replica host. Monitoring replication status is critical to ensure that your disaster recovery plan is functional and that replicated VMs are up to date. A replication failure that goes unnoticed means your backup copy is stale, and a failover would result in significant data loss.
In this guide, we will explore how to query and monitor Hyper-V replication status from a Batch Script using PowerShell cmdlets.
Understanding Replication States
| State | Meaning |
|---|---|
Ready to begin | Replication is configured but has not started initial replication |
Replicating | Active replication is in progress (normal operating state) |
Resynchronization required | Replicas are out of sync; a full resync is needed |
Suspended | Replication is paused (manually or due to error) |
Critical | Replication has failed and requires immediate attention |
Not applicable | VM is not configured for replication |
Method 1: Checking Replication Status of a Specific VM
@echo off
setlocal
set "vm_name=ProductionDB"
:: Verify Admin
net session >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Administrator privileges required.
pause
exit /b 1
)
echo Replication status for "%vm_name%":
echo ====================================
echo.
powershell -NoProfile -Command ^
"$rep = Get-VMReplication -VMName '%vm_name%' -ErrorAction SilentlyContinue;" ^
"if (-not $rep) {" ^
" Write-Host '[INFO] VM is not configured for replication.'; exit 0" ^
"};" ^
"Write-Host (' State: {0}' -f $rep.State);" ^
"Write-Host (' Health: {0}' -f $rep.Health);" ^
"Write-Host (' Mode: {0}' -f $rep.Mode);" ^
"Write-Host (' Primary Server: {0}' -f $rep.PrimaryServer);" ^
"Write-Host (' Replica Server: {0}' -f $rep.ReplicaServer);" ^
"Write-Host (' Frequency (sec): {0}' -f $rep.FrequencySec);" ^
"Write-Host (' Last Replication: {0}' -f $rep.LastReplicationTime);" ^
"if ($rep.LastReplicationTime) {" ^
" $age = (Get-Date) - $rep.LastReplicationTime;" ^
" Write-Host (' Replication Age: {0}d {1}h {2}m ago' -f $age.Days, $age.Hours, $age.Minutes)" ^
"}"
pause
Method 2: Listing All Replicated VMs
@echo off
setlocal
net session >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Admin required.
pause
exit /b 1
)
echo =============================================
echo HYPER-V REPLICATION STATUS
echo %date% %time:~0,8%
echo =============================================
echo.
powershell -NoProfile -Command ^
"$reps = @(Get-VMReplication -ErrorAction SilentlyContinue);" ^
"if ($reps.Count -eq 0) {" ^
" Write-Host 'No replicated VMs found on this host.'; exit 0" ^
"};" ^
"foreach ($r in ($reps | Sort-Object Health, VMName)) {" ^
" $icon = switch ($r.Health) {" ^
" 'Normal' {'[OK]'}" ^
" 'Warning' {'[!!]'}" ^
" 'Critical' {'[XX]'}" ^
" default {'[??]'}" ^
" };" ^
" Write-Host ('{0} {1,-25} State: {2,-15} Last: {3}' -f" ^
" $icon, $r.VMName, $r.State, $r.LastReplicationTime)" ^
"};" ^
"Write-Host '';" ^
"$normal = @($reps | Where-Object Health -eq 'Normal').Count;" ^
"$warning = @($reps | Where-Object Health -eq 'Warning').Count;" ^
"$critical = @($reps | Where-Object Health -eq 'Critical').Count;" ^
"Write-Host ('Summary: Normal: {0} Warning: {1} Critical: {2}' -f $normal, $warning, $critical)"
pause
Method 3: Detailed Replication Health Report
@echo off
setlocal
net session >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Admin required.
pause
exit /b 1
)
echo Generating replication health report...
echo.
powershell -NoProfile -Command ^
"$reps = @(Get-VMReplication -ErrorAction SilentlyContinue);" ^
"if ($reps.Count -eq 0) {" ^
" Write-Host 'No replicated VMs found.'; exit 0" ^
"};" ^
"foreach ($r in $reps) {" ^
" Write-Host ('=== ' + $r.VMName + ' ===');" ^
" Write-Host (' Mode: {0}' -f $r.Mode);" ^
" Write-Host (' State: {0}' -f $r.State);" ^
" Write-Host (' Health: {0}' -f $r.Health);" ^
" Write-Host (' Frequency: Every {0} seconds' -f $r.FrequencySec);" ^
" Write-Host (' Primary: {0}' -f $r.PrimaryServer);" ^
" Write-Host (' Replica: {0}' -f $r.ReplicaServer);" ^
" Write-Host (' Last Sync: {0}' -f $r.LastReplicationTime);" ^
" $stats = Measure-VMReplication -VMName $r.VMName -ErrorAction SilentlyContinue;" ^
" if ($stats) {" ^
" $avgMB = if ($stats.AverageReplicationSize) { [math]::Round($stats.AverageReplicationSize / 1MB, 2) } else { 'N/A' };" ^
" $pendMB = if ($stats.PendingReplicationSize) { [math]::Round($stats.PendingReplicationSize / 1MB, 2) } else { 'N/A' };" ^
" Write-Host (' Avg Size: {0} MB' -f $avgMB);" ^
" Write-Host (' Pending Size: {0} MB' -f $pendMB);" ^
" Write-Host (' Missed Cycles: {0}' -f $stats.MissedReplicationCount);" ^
" Write-Host (' Successful: {0}' -f $stats.SuccessfulReplicationCount)" ^
" } else {" ^
" Write-Host ' Statistics: Not available'" ^
" };" ^
" Write-Host ''" ^
"}"
pause
Method 4: Replication Health Monitor (Auto-Refresh)
@echo off
title Replication Health Monitor
:loop
cls
echo =============================================
echo REPLICATION MONITOR
echo %date% %time:~0,8%
echo =============================================
echo.
powershell -NoProfile -Command ^
"$reps = @(Get-VMReplication -ErrorAction SilentlyContinue);" ^
"if ($reps.Count -eq 0) { Write-Host 'No replications configured.'; exit 0 };" ^
"foreach ($r in ($reps | Sort-Object Health, VMName)) {" ^
" $color = switch ($r.Health) {" ^
" 'Normal' {'Green'}" ^
" 'Warning' {'Yellow'}" ^
" 'Critical' {'Red'}" ^
" default {'White'}" ^
" };" ^
" $age = if ($r.LastReplicationTime) {" ^
" ((Get-Date) - $r.LastReplicationTime).ToString('hh\:mm\:ss')" ^
" } else { 'Never' };" ^
" Write-Host ('{0,-25} {1,-12} {2,-15} Age: {3}' -f" ^
" $r.VMName, $r.Health, $r.State, $age) -ForegroundColor $color" ^
"};" ^
"Write-Host '';" ^
"$issues = @($reps | Where-Object { $_.Health -ne 'Normal' });" ^
"if ($issues.Count -gt 0) {" ^
" Write-Host ('ALERT: {0} replication(s) need attention!' -f $issues.Count) -ForegroundColor Red" ^
"} else {" ^
" Write-Host 'All replications healthy.' -ForegroundColor Green" ^
"}"
echo.
echo Refreshing in 30 seconds. Press Ctrl+C to stop.
timeout /t 30 /nobreak >nul
goto loop
Method 5: CSV Export for Compliance Reporting
@echo off
setlocal
for /f "tokens=2 delims==" %%T in ('wmic os get LocalDateTime /value') do set "dt=%%T"
set "stamp=%dt:~0,4%%dt:~4,2%%dt:~6,2%"
set "output=%~dp0replication_report_%COMPUTERNAME%_%stamp%.csv"
net session >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Admin required.
pause
exit /b 1
)
echo Exporting replication status to CSV...
powershell -NoProfile -Command ^
"@(Get-VMReplication -ErrorAction SilentlyContinue) |" ^
"Select-Object VMName, State, Health, Mode," ^
" PrimaryServer, ReplicaServer," ^
" FrequencySec, LastReplicationTime," ^
" @{Name='AgeMinutes'; Expression={" ^
" if ($_.LastReplicationTime) {" ^
" [math]::Round(((Get-Date) - $_.LastReplicationTime).TotalMinutes, 1)" ^
" } else { 'N/A' }" ^
" }} |" ^
"Export-Csv -Path '%output%' -NoTypeInformation"
if %errorlevel%==0 (
echo [SUCCESS] Exported to: %output%
echo.
type "%output%"
) else (
echo [ERROR] Export failed.
)
echo.
pause
Resuming Failed or Suspended Replication
@echo off
setlocal
set "vm_name=ProductionDB"
net session >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Admin required.
pause
exit /b 1
)
:: Check current state
set "state="
for /f "delims=" %%S in ('powershell -NoProfile -Command ^
"$r = Get-VMReplication -VMName '%vm_name%' -ErrorAction SilentlyContinue; if ($r) { $r.State }"') do set "state=%%S"
if not defined state (
echo [ERROR] VM "%vm_name%" has no replication configured or was not found.
pause
exit /b 1
)
echo Current replication state: %state%
echo.
if /i "%state%"=="Suspended" (
echo Resuming replication...
powershell -NoProfile -Command ^
"try { Resume-VMReplication -VMName '%vm_name%' -ErrorAction Stop; exit 0 }" ^
"catch { Write-Host ('[ERROR] ' + $_.Exception.Message); exit 1 }"
if !errorlevel!==0 (
echo [OK] Replication resumed.
)
) else if /i "%state%"=="ResynchronizationRequired" (
echo Starting resynchronization...
powershell -NoProfile -Command ^
"try { Resume-VMReplication -VMName '%vm_name%' -Resynchronize -ErrorAction Stop; exit 0 }" ^
"catch { Write-Host ('[ERROR] ' + $_.Exception.Message); exit 1 }"
if !errorlevel!==0 (
echo [OK] Resynchronization started.
)
) else (
echo [INFO] No action needed. State is: %state%
)
pause
Common Mistakes
The Wrong Way: Not Monitoring Replication Age
:: WRONG - Only checking state, not age
powershell -Command "(Get-VMReplication -VMName 'MyVM').State"
:: Returns "Replicating" even if the last sync was 24 hours ago
Output Concern:
The replication state may show "Replicating" even when the last successful sync was hours ago. Always check LastReplicationTime and calculate the age to detect stale replicas.
The Wrong Way: Assuming All VMs Are Replicated
:: WRONG - Fails silently if the VM has no replication configured
powershell -Command "Measure-VMReplication -VMName 'NonReplicatedVM'"
Not all VMs on a host may be configured for replication. Always handle the case where Get-VMReplication returns null or no results.
Best Practices
- Monitor replication age: Check the time since the last successful replication, not just the state.
- Alert on non-Normal health: Any replication in
WarningorCriticalhealth requires investigation. - Use
Measure-VMReplicationfor statistics: It provides detailed metrics on replication sizes, missed cycles, and success rates. - Schedule regular reports: Export replication status to CSV daily for compliance and audit trails.
- Resume suspended replications promptly: Suspended replications accumulate changes that make resynchronization slower.
Conclusion
Getting the replication status of Hyper-V VMs from a Batch Script is powered by PowerShell's Get-VMReplication and Measure-VMReplication cmdlets. Regular monitoring of replication state, health, and age ensures that disaster recovery copies remain current and functional. By building automated dashboards, alerting on degraded replications, and exporting status reports for compliance, administrators maintain confidence that their disaster recovery infrastructure will perform when needed.