[{"data":1,"prerenderedAt":2415},["ShallowReactive",2],{"post:\u002F2026\u002F07\u002F22\u002Finfrastructure-what-a-real-disaster-recovery-test-actually-reveals\u002F":3},{"post":4,"newer":2362,"older":2372,"related":2383,"series":2413},{"id":5,"title":6,"body":7,"canonical":2343,"categories":2344,"date":2347,"description":2348,"extension":2349,"featured":2350,"hero":2351,"image":2351,"meta":2352,"navigation":521,"path":2353,"readingTime":308,"seo":2354,"series":2351,"seriesOrder":2351,"sites":2355,"source":2351,"stem":2356,"tags":2357,"updated":2351,"url":2360,"__hash__":2361},"blog\u002Fblog\u002F2026\u002F07\u002F22\u002Finfrastructure-what-a-real-disaster-recovery-test-actually-reveals.md","Infrastructure: What a Real Disaster Recovery Test Actually Reveals",{"type":8,"value":9,"toc":2333},"minimark",[10,14,19,22,25,29,32,35,39,42,54,58,61,64,68,83,86,312,315,2099,2106,2114,2131,2135,2138,2144,2192,2197,2224,2229,2253,2257,2260,2264,2329],[11,12,13],"p",{},"A disaster recovery plan that has only ever been reviewed on paper is a hypothesis, not a capability. I've sat through plenty of tabletop exercises where everyone agreed the runbook made sense, the RTOs looked achievable, and the plan got signed off for another year — and then watched a real, full failover to the DR site find a dozen things the tabletop never could, because a tabletop only tests whether the plan reads well, not whether the systems actually do what the plan assumes.",[15,16,18],"h2",{"id":17},"tabletop-exercises-dont-find-these","Tabletop Exercises Don't Find These",[11,20,21],{},"Walking through a runbook out loud surfaces logic gaps — a missing step, an unclear owner, a sequencing question nobody had thought about. What it can't surface is anything that only breaks when bits actually move: a backup job that's been silently failing for a service nobody checks, a replication link that's been running behind for months without alerting because the alert threshold was set once and never revisited, or a script that references a path that stopped existing after a server rename. None of that shows up until someone actually tries to bring the environment up somewhere else.",[11,23,24],{},"The good news is that the tooling most of us already own was built for exactly this. Hyper-V Replica has a test failover that creates a temporary copy of the replica VM on the DR host, isolated from the production network, while the primary keeps running and replication carries on untouched. Azure Site Recovery has the same idea at recovery-plan scale: you pick a recovery point (latest processed, latest app-consistent, and so on), pick an isolated virtual network, and clean up the test VMs when you're done. Neither costs you downtime, which removes the usual excuse for only ever doing the tabletop.",[15,26,28],{"id":27},"the-dependencies-nobody-documented","The Dependencies Nobody Documented",[11,30,31],{},"Every DR plan I've reviewed has a failover group — the list of systems that get included in the test or the real event. Every real test I've run has found at least one application that depends on something outside that group. An app server fails over cleanly and then can't reach the license server, the internal API it calls for lookups, or a file share that was never in scope because nobody thought of it as \"infrastructure.\" These dependencies are invisible in an architecture diagram that only shows what a team explicitly built, because most of them accumulated informally after the diagram was drawn. A real failover is the only reliable way to find them, because it's the only exercise that actually tries to use the dependency and fails when it isn't there.",[11,33,34],{},"That's why the test script below doesn't just check that VMs boot. It reads a plan file that lists, for each VM, the services that must be running and the TCP endpoints it must reach, and it tests those from inside the recovered guest. The plan file becomes the dependency documentation, and every failed endpoint check is either a missing VM in the failover group or a hard-coded address that doesn't exist at the DR site.",[15,36,38],{"id":37},"licensing-and-identity-break-first","Licensing and Identity Break First",[11,40,41],{},"In the DR tests I've run, two categories of failure have come up more often than anything else, and neither is a capacity problem. Licensing tied to a hardware ID, a MAC address, or a specific IP will refuse to activate on DR hardware that doesn't match, and the fix is rarely fast if it requires a vendor support call mid-test. Identity is the other one: service accounts, Kerberos delegation, and certificate trust chains are full of assumptions about which domain controller, which site, or which network path is available, and a DR site that's a slower or differently-configured replica of production identity infrastructure surfaces authentication failures that never show up in normal operation.",[11,43,44,45,49,50,53],{},"Microsoft's Site Recovery guidance for Active Directory is a useful model even outside Azure. Test failover happens in a network isolated from production; before any application fails over, a domain controller and DNS server have to exist in that isolated network, most easily by test-failing-over a replicated DC first. That DC should be a global catalog and hold the FSMO roles the test needs, or those roles have to be seized afterwards. The isolated network should reuse the production address range and must not be connected back to production. On Hyper-V Replica, a test VM (named after the original with ",[46,47,48],"code",{}," - Test"," appended) isn't connected to any network by default, which is safe but means no dependency can be tested. The equivalent of Site Recovery's isolated network is an internal or private virtual switch on the Replica server, assigned to each replicated adapter with ",[46,51,52],{},"Set-VMNetworkAdapter -TestReplicaSwitchName",", plus a DC first in the plan file so it is up before anything that authenticates against it.",[15,55,57],{"id":56},"recovery-time-vs-recovery-confidence","Recovery Time vs Recovery Confidence",[11,59,60],{},"The number everyone tracks is recovery time — how long the failover took against the target RTO. The number that actually matters more is recovery confidence: how much of the failover required a human improvising a fix in real time versus following the documented steps. A recovery that hit its RTO because three people quietly worked around three undocumented problems isn't a passing test; it's a test that got lucky with who happened to be in the room.",[11,62,63],{},"Automation doesn't create confidence by itself, but it measures it honestly. A script records the same timings and checks every run, so the only way the result improves is if the environment did. And the recovery point matters as much as the recovery time: Hyper-V Replica sends changes every 30 seconds, 5 minutes or 15 minutes, keeps up to 24 hourly recovery points if you configure them, and only produces application-consistent points when you schedule VSS snapshots. A test that fails over to a crash-consistent point on a database server is testing something different from what the business thinks it has.",[15,65,67],{"id":66},"the-test-failover-script","The Test Failover Script",[11,69,70,71,74,75,78,79,82],{},"This runs on the Hyper-V Replica server that holds the replica VMs (PowerShell Direct only reaches VMs running on the local host). For each VM in the plan, in order, it checks replication health with ",[46,72,73],{},"Get-VMReplication",", creates a test VM with ",[46,76,77],{},"Start-VMFailover -AsTest",", starts it, waits until the guest answers over PowerShell Direct, gives services time to settle, then checks the listed services and endpoints from inside the guest. Test VMs stay running until every VM has been checked, because later VMs depend on earlier ones, and then ",[46,80,81],{},"Stop-VMFailover"," deletes them all, including when the run stops on an error. The results go to a CSV you can attach to the DR test record.",[11,84,85],{},"The plan file is a PowerShell data file, ordered the way the runbook boots things:",[87,88,93],"pre",{"className":89,"code":90,"language":91,"meta":92,"style":92},"language-powershell shiki shiki-themes github-dark","@{\n    VMs = @(\n        @{ Name = 'dc-dr01'; Services = @('NTDS', 'DNS', 'Netlogon'); Endpoints = @() }\n        @{ Name = 'sql01'; Services = @('MSSQLSERVER', 'SQLSERVERAGENT'); Endpoints = @('dc-dr01:389', 'dc-dr01:88') }\n        @{ Name = 'lic01'; Services = @('\u003Clicense-service-name>'); Endpoints = @('dc-dr01:389') }\n        @{ Name = 'app01'; Services = @('W3SVC'); Endpoints = @('sql01:1433', 'lic01:27000', 'files01:445') }\n    )\n}\n","powershell","",[46,94,95,108,123,172,219,254,300,306],{"__ignoreMap":92},[96,97,100,104],"span",{"class":98,"line":99},"line",1,[96,101,103],{"class":102},"snl16","@",[96,105,107],{"class":106},"s95oV","{\n",[96,109,111,114,117,120],{"class":98,"line":110},2,[96,112,113],{"class":106},"    VMs ",[96,115,116],{"class":102},"=",[96,118,119],{"class":102}," @",[96,121,122],{"class":106},"(\n",[96,124,126,129,132,134,138,141,143,145,148,151,154,157,159,162,165,167,169],{"class":98,"line":125},3,[96,127,128],{"class":102},"        @",[96,130,131],{"class":106},"{ Name ",[96,133,116],{"class":102},[96,135,137],{"class":136},"sU2Wk"," 'dc-dr01'",[96,139,140],{"class":106},"; Services ",[96,142,116],{"class":102},[96,144,119],{"class":102},[96,146,147],{"class":106},"(",[96,149,150],{"class":136},"'NTDS'",[96,152,153],{"class":102},",",[96,155,156],{"class":136}," 'DNS'",[96,158,153],{"class":102},[96,160,161],{"class":136}," 'Netlogon'",[96,163,164],{"class":106},"); Endpoints ",[96,166,116],{"class":102},[96,168,119],{"class":102},[96,170,171],{"class":106},"() }\n",[96,173,175,177,179,181,184,186,188,190,192,195,197,200,202,204,206,208,211,213,216],{"class":98,"line":174},4,[96,176,128],{"class":102},[96,178,131],{"class":106},[96,180,116],{"class":102},[96,182,183],{"class":136}," 'sql01'",[96,185,140],{"class":106},[96,187,116],{"class":102},[96,189,119],{"class":102},[96,191,147],{"class":106},[96,193,194],{"class":136},"'MSSQLSERVER'",[96,196,153],{"class":102},[96,198,199],{"class":136}," 'SQLSERVERAGENT'",[96,201,164],{"class":106},[96,203,116],{"class":102},[96,205,119],{"class":102},[96,207,147],{"class":106},[96,209,210],{"class":136},"'dc-dr01:389'",[96,212,153],{"class":102},[96,214,215],{"class":136}," 'dc-dr01:88'",[96,217,218],{"class":106},") }\n",[96,220,222,224,226,228,231,233,235,237,239,242,244,246,248,250,252],{"class":98,"line":221},5,[96,223,128],{"class":102},[96,225,131],{"class":106},[96,227,116],{"class":102},[96,229,230],{"class":136}," 'lic01'",[96,232,140],{"class":106},[96,234,116],{"class":102},[96,236,119],{"class":102},[96,238,147],{"class":106},[96,240,241],{"class":136},"'\u003Clicense-service-name>'",[96,243,164],{"class":106},[96,245,116],{"class":102},[96,247,119],{"class":102},[96,249,147],{"class":106},[96,251,210],{"class":136},[96,253,218],{"class":106},[96,255,257,259,261,263,266,268,270,272,274,277,279,281,283,285,288,290,293,295,298],{"class":98,"line":256},6,[96,258,128],{"class":102},[96,260,131],{"class":106},[96,262,116],{"class":102},[96,264,265],{"class":136}," 'app01'",[96,267,140],{"class":106},[96,269,116],{"class":102},[96,271,119],{"class":102},[96,273,147],{"class":106},[96,275,276],{"class":136},"'W3SVC'",[96,278,164],{"class":106},[96,280,116],{"class":102},[96,282,119],{"class":102},[96,284,147],{"class":106},[96,286,287],{"class":136},"'sql01:1433'",[96,289,153],{"class":102},[96,291,292],{"class":136}," 'lic01:27000'",[96,294,153],{"class":102},[96,296,297],{"class":136}," 'files01:445'",[96,299,218],{"class":106},[96,301,303],{"class":98,"line":302},7,[96,304,305],{"class":106},"    )\n",[96,307,309],{"class":98,"line":308},8,[96,310,311],{"class":106},"}\n",[11,313,314],{},"The script needs the Hyper-V module on the replica host, Windows Server 2016 or later guests (a PowerShell Direct requirement), a guest credential valid in the isolated environment, and replicas that have finished initial replication.",[87,316,318],{"className":89,"code":317,"language":91,"meta":92,"style":92},"\u003C#\n.SYNOPSIS\n    Runs a Hyper-V Replica test failover for a list of VMs and verifies services and dependencies inside each guest.\n.DESCRIPTION\n    Reads a plan file (.psd1) listing VMs in boot order with the services each must run and the host:port\n    endpoints each must reach. For every VM it checks replication health, runs Start-VMFailover -AsTest,\n    starts the test VM, waits for PowerShell Direct to respond, then checks services with Get-Service and\n    endpoints with Test-NetConnection from inside the guest. When all VMs are done it removes every test VM\n    with Stop-VMFailover (unless -KeepTestVMs is set) and writes the results to a CSV file.\n.PARAMETER PlanPath\n    Path to the .psd1 plan file.\n.PARAMETER GuestCredential\n    Credential for the guests, valid inside the isolated test network.\n.PARAMETER BootTimeoutMinutes\n    How long to wait for each test VM to answer PowerShell Direct.\n.PARAMETER SettleSeconds\n    How long to wait after the guest answers before checking services, for delayed-start services.\n.PARAMETER EvidencePath\n    CSV file for the results.\n.PARAMETER KeepTestVMs\n    Leave the test VMs running for manual testing; clean up later with Stop-VMFailover.\n.EXAMPLE\n    .\\Invoke-DRTestFailover.ps1 -PlanPath .\\dr-plan.psd1 -GuestCredential (Get-Credential)\n.NOTES\n    Author  : Thomas Lasswell (https:\u002F\u002Fwww.techcolumnist.com)\n    Version : 1.0 (2026-07-22)\n    Requires: Hyper-V module on the Replica server, Windows Server 2016+ guests\n#>\n\n[CmdletBinding()]\nparam (\n    [Parameter(Mandatory = $true)]\n    [string]$PlanPath,\n\n    [Parameter(Mandatory = $true)]\n    [pscredential]$GuestCredential,\n\n    [Parameter(Mandatory = $false)]\n    [int]$BootTimeoutMinutes = 15,\n\n    [Parameter(Mandatory = $false)]\n    [int]$SettleSeconds = 120,\n\n    [Parameter(Mandatory = $false)]\n    [string]$EvidencePath = (Join-Path -Path (Get-Location).Path -ChildPath ('DRTest-{0:yyyyMMdd-HHmm}.csv' -f (Get-Date))),\n\n    [switch]$KeepTestVMs\n)\n\n$ErrorActionPreference = 'Stop'\n$plan = Import-PowerShellDataFile -Path $PlanPath\n$results = [System.Collections.Generic.List[object]]::new()\n$testedVMs = [System.Collections.Generic.List[string]]::new()\n$runClock = [System.Diagnostics.Stopwatch]::StartNew()\n\ntry {\n    foreach ($entry in $plan.VMs) {\n        $row = [ordered]@{\n            VM           = $entry.Name\n            Replication  = 'Normal'\n            TestVM       = ''\n            ReadySeconds = $null\n            FailedChecks = ''\n            Result       = 'Pass'\n        }\n\n        # Replication health first: a Critical replica is a finding before any test runs.\n        foreach ($health in 'Warning', 'Critical') {\n            if (Get-VMReplication -VMName $entry.Name -ReplicationHealth $health -ErrorAction SilentlyContinue) {\n                $row.Replication = $health\n            }\n        }\n\n        if ($row.Replication -eq 'Critical') {\n            $row.Result = 'Fail'\n            $row.FailedChecks = 'Replication health is Critical; not tested'\n            $results.Add([PSCustomObject]$row)\n            continue\n        }\n\n        # Create the test VM; Hyper-V names it after the replica with ' - Test' appended.\n        Start-VMFailover -VMName $entry.Name -AsTest -Confirm:$false\n        $testVM = Get-VM -Name \"$($entry.Name) - Test\" -ErrorAction SilentlyContinue | Select-Object -First 1\n\n        if (-not $testVM) {\n            $row.Result = 'Fail'\n            $row.FailedChecks = 'Test VM was not created'\n            $results.Add([PSCustomObject]$row)\n            continue\n        }\n\n        $testedVMs.Add($entry.Name)\n        $row.TestVM = $testVM.Name\n        $clock = [System.Diagnostics.Stopwatch]::StartNew()\n        Start-VM -VM $testVM\n\n        # Wait until the guest answers over PowerShell Direct.\n        $ready = $false\n\n        do {\n            try {\n                Invoke-Command -VMId $testVM.VMId -Credential $GuestCredential -ScriptBlock { $true } -ErrorAction Stop | Out-Null\n                $ready = $true\n            } catch {\n                Start-Sleep -Seconds 15\n            }\n        } while (-not $ready -and $clock.Elapsed.TotalMinutes -lt $BootTimeoutMinutes)\n\n        if (-not $ready) {\n            $row.Result = 'Fail'\n            $row.FailedChecks = \"Guest did not answer PowerShell Direct within $BootTimeoutMinutes minutes\"\n            $results.Add([PSCustomObject]$row)\n            continue\n        }\n\n        $row.ReadySeconds = [int]$clock.Elapsed.TotalSeconds\n        Start-Sleep -Seconds $SettleSeconds\n\n        # Check services and dependencies from inside the recovered guest.\n        $checks = Invoke-Command -VMId $testVM.VMId -Credential $GuestCredential -ArgumentList $entry.Services, $entry.Endpoints -ScriptBlock {\n            param ($services, $endpoints)\n\n            foreach ($serviceName in $services) {\n                $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue\n                [PSCustomObject]@{ Check = \"service $serviceName\"; Passed = [bool]($service -and $service.Status -eq 'Running') }\n            }\n\n            foreach ($endpoint in $endpoints) {\n                $target, $port = $endpoint -split ':'\n                $reachable = Test-NetConnection -ComputerName $target -Port $port -InformationLevel Quiet -WarningAction SilentlyContinue\n                [PSCustomObject]@{ Check = \"tcp $endpoint\"; Passed = [bool]$reachable }\n            }\n        }\n\n        $failed = @($checks | Where-Object { -not $_.Passed })\n\n        if ($failed.Count -gt 0) {\n            $row.Result = 'Fail'\n            $row.FailedChecks = ($failed.Check) -join '; '\n        }\n\n        $results.Add([PSCustomObject]$row)\n    }\n} finally {\n    # Remove the test VMs only after every dependency has been exercised, or when the run stops on an error.\n    if (-not $KeepTestVMs) {\n        foreach ($name in $testedVMs) {\n            Stop-VMFailover -VMName $name -Confirm:$false\n        }\n    }\n}\n\n$results | Export-Csv -Path $EvidencePath -NoTypeInformation\n$results | Format-Table -AutoSize\nWrite-Output \"Test run took $([int]$runClock.Elapsed.TotalMinutes) minutes. Evidence: $EvidencePath\"\n",[46,319,320,326,335,340,347,352,357,362,367,373,384,390,400,406,416,422,432,438,448,454,464,470,478,484,492,498,504,510,516,523,535,544,568,582,587,604,617,622,640,658,663,680,697,702,719,771,776,787,793,798,809,825,847,865,881,886,895,910,930,941,952,963,974,984,995,1001,1006,1012,1034,1059,1070,1076,1081,1086,1102,1113,1124,1136,1142,1147,1152,1158,1181,1233,1238,1251,1260,1270,1279,1284,1289,1294,1300,1311,1325,1336,1341,1347,1358,1363,1371,1379,1416,1427,1438,1452,1457,1485,1490,1502,1511,1527,1536,1541,1546,1551,1566,1577,1582,1588,1622,1636,1641,1655,1676,1726,1731,1736,1749,1771,1802,1836,1841,1846,1851,1880,1885,1901,1910,1926,1931,1936,1946,1952,1963,1969,1982,1995,2012,2017,2022,2027,2032,2052,2067],{"__ignoreMap":92},[96,321,322],{"class":98,"line":99},[96,323,325],{"class":324},"sAwPA","\u003C#\n",[96,327,328,332],{"class":98,"line":110},[96,329,331],{"class":330},"sDLfK",".",[96,333,334],{"class":102},"SYNOPSIS\n",[96,336,337],{"class":98,"line":125},[96,338,339],{"class":324},"    Runs a Hyper-V Replica test failover for a list of VMs and verifies services and dependencies inside each guest.\n",[96,341,342,344],{"class":98,"line":174},[96,343,331],{"class":330},[96,345,346],{"class":102},"DESCRIPTION\n",[96,348,349],{"class":98,"line":221},[96,350,351],{"class":324},"    Reads a plan file (.psd1) listing VMs in boot order with the services each must run and the host:port\n",[96,353,354],{"class":98,"line":256},[96,355,356],{"class":324},"    endpoints each must reach. For every VM it checks replication health, runs Start-VMFailover -AsTest,\n",[96,358,359],{"class":98,"line":302},[96,360,361],{"class":324},"    starts the test VM, waits for PowerShell Direct to respond, then checks services with Get-Service and\n",[96,363,364],{"class":98,"line":308},[96,365,366],{"class":324},"    endpoints with Test-NetConnection from inside the guest. When all VMs are done it removes every test VM\n",[96,368,370],{"class":98,"line":369},9,[96,371,372],{"class":324},"    with Stop-VMFailover (unless -KeepTestVMs is set) and writes the results to a CSV file.\n",[96,374,376,378,381],{"class":98,"line":375},10,[96,377,331],{"class":330},[96,379,380],{"class":102},"PARAMETER",[96,382,383],{"class":102}," PlanPath\n",[96,385,387],{"class":98,"line":386},11,[96,388,389],{"class":324},"    Path to the .psd1 plan file.\n",[96,391,393,395,397],{"class":98,"line":392},12,[96,394,331],{"class":330},[96,396,380],{"class":102},[96,398,399],{"class":102}," GuestCredential\n",[96,401,403],{"class":98,"line":402},13,[96,404,405],{"class":324},"    Credential for the guests, valid inside the isolated test network.\n",[96,407,409,411,413],{"class":98,"line":408},14,[96,410,331],{"class":330},[96,412,380],{"class":102},[96,414,415],{"class":102}," BootTimeoutMinutes\n",[96,417,419],{"class":98,"line":418},15,[96,420,421],{"class":324},"    How long to wait for each test VM to answer PowerShell Direct.\n",[96,423,425,427,429],{"class":98,"line":424},16,[96,426,331],{"class":330},[96,428,380],{"class":102},[96,430,431],{"class":102}," SettleSeconds\n",[96,433,435],{"class":98,"line":434},17,[96,436,437],{"class":324},"    How long to wait after the guest answers before checking services, for delayed-start services.\n",[96,439,441,443,445],{"class":98,"line":440},18,[96,442,331],{"class":330},[96,444,380],{"class":102},[96,446,447],{"class":102}," EvidencePath\n",[96,449,451],{"class":98,"line":450},19,[96,452,453],{"class":324},"    CSV file for the results.\n",[96,455,457,459,461],{"class":98,"line":456},20,[96,458,331],{"class":330},[96,460,380],{"class":102},[96,462,463],{"class":102}," KeepTestVMs\n",[96,465,467],{"class":98,"line":466},21,[96,468,469],{"class":324},"    Leave the test VMs running for manual testing; clean up later with Stop-VMFailover.\n",[96,471,473,475],{"class":98,"line":472},22,[96,474,331],{"class":330},[96,476,477],{"class":102},"EXAMPLE\n",[96,479,481],{"class":98,"line":480},23,[96,482,483],{"class":324},"    .\\Invoke-DRTestFailover.ps1 -PlanPath .\\dr-plan.psd1 -GuestCredential (Get-Credential)\n",[96,485,487,489],{"class":98,"line":486},24,[96,488,331],{"class":330},[96,490,491],{"class":102},"NOTES\n",[96,493,495],{"class":98,"line":494},25,[96,496,497],{"class":324},"    Author  : Thomas Lasswell (https:\u002F\u002Fwww.techcolumnist.com)\n",[96,499,501],{"class":98,"line":500},26,[96,502,503],{"class":324},"    Version : 1.0 (2026-07-22)\n",[96,505,507],{"class":98,"line":506},27,[96,508,509],{"class":324},"    Requires: Hyper-V module on the Replica server, Windows Server 2016+ guests\n",[96,511,513],{"class":98,"line":512},28,[96,514,515],{"class":324},"#>\n",[96,517,519],{"class":98,"line":518},29,[96,520,522],{"emptyLinePlaceholder":521},true,"\n",[96,524,526,529,532],{"class":98,"line":525},30,[96,527,528],{"class":106},"[",[96,530,531],{"class":330},"CmdletBinding",[96,533,534],{"class":106},"()]\n",[96,536,538,541],{"class":98,"line":537},31,[96,539,540],{"class":102},"param",[96,542,543],{"class":106}," (\n",[96,545,547,550,553,555,559,562,565],{"class":98,"line":546},32,[96,548,549],{"class":106},"    [",[96,551,552],{"class":330},"Parameter",[96,554,147],{"class":106},[96,556,558],{"class":557},"s9osk","Mandatory",[96,560,561],{"class":102}," =",[96,563,564],{"class":330}," $true",[96,566,567],{"class":106},")]\n",[96,569,571,573,576,579],{"class":98,"line":570},33,[96,572,549],{"class":106},[96,574,575],{"class":102},"string",[96,577,578],{"class":106},"]$PlanPath",[96,580,581],{"class":102},",\n",[96,583,585],{"class":98,"line":584},34,[96,586,522],{"emptyLinePlaceholder":521},[96,588,590,592,594,596,598,600,602],{"class":98,"line":589},35,[96,591,549],{"class":106},[96,593,552],{"class":330},[96,595,147],{"class":106},[96,597,558],{"class":557},[96,599,561],{"class":102},[96,601,564],{"class":330},[96,603,567],{"class":106},[96,605,607,609,612,615],{"class":98,"line":606},36,[96,608,549],{"class":106},[96,610,611],{"class":102},"pscredential",[96,613,614],{"class":106},"]$GuestCredential",[96,616,581],{"class":102},[96,618,620],{"class":98,"line":619},37,[96,621,522],{"emptyLinePlaceholder":521},[96,623,625,627,629,631,633,635,638],{"class":98,"line":624},38,[96,626,549],{"class":106},[96,628,552],{"class":330},[96,630,147],{"class":106},[96,632,558],{"class":557},[96,634,561],{"class":102},[96,636,637],{"class":330}," $false",[96,639,567],{"class":106},[96,641,643,645,648,651,653,656],{"class":98,"line":642},39,[96,644,549],{"class":106},[96,646,647],{"class":102},"int",[96,649,650],{"class":106},"]$BootTimeoutMinutes ",[96,652,116],{"class":102},[96,654,655],{"class":330}," 15",[96,657,581],{"class":102},[96,659,661],{"class":98,"line":660},40,[96,662,522],{"emptyLinePlaceholder":521},[96,664,666,668,670,672,674,676,678],{"class":98,"line":665},41,[96,667,549],{"class":106},[96,669,552],{"class":330},[96,671,147],{"class":106},[96,673,558],{"class":557},[96,675,561],{"class":102},[96,677,637],{"class":330},[96,679,567],{"class":106},[96,681,683,685,687,690,692,695],{"class":98,"line":682},42,[96,684,549],{"class":106},[96,686,647],{"class":102},[96,688,689],{"class":106},"]$SettleSeconds ",[96,691,116],{"class":102},[96,693,694],{"class":330}," 120",[96,696,581],{"class":102},[96,698,700],{"class":98,"line":699},43,[96,701,522],{"emptyLinePlaceholder":521},[96,703,705,707,709,711,713,715,717],{"class":98,"line":704},44,[96,706,549],{"class":106},[96,708,552],{"class":330},[96,710,147],{"class":106},[96,712,558],{"class":557},[96,714,561],{"class":102},[96,716,637],{"class":330},[96,718,567],{"class":106},[96,720,722,724,726,729,731,734,737,740,743,746,749,752,755,758,761,763,766,769],{"class":98,"line":721},45,[96,723,549],{"class":106},[96,725,575],{"class":102},[96,727,728],{"class":106},"]$EvidencePath ",[96,730,116],{"class":102},[96,732,733],{"class":106}," (",[96,735,736],{"class":330},"Join-Path",[96,738,739],{"class":102}," -",[96,741,742],{"class":106},"Path (",[96,744,745],{"class":330},"Get-Location",[96,747,748],{"class":106},").Path ",[96,750,751],{"class":102},"-",[96,753,754],{"class":106},"ChildPath (",[96,756,757],{"class":136},"'DRTest-{0:yyyyMMdd-HHmm}.csv'",[96,759,760],{"class":102}," -f",[96,762,733],{"class":106},[96,764,765],{"class":330},"Get-Date",[96,767,768],{"class":106},")))",[96,770,581],{"class":102},[96,772,774],{"class":98,"line":773},46,[96,775,522],{"emptyLinePlaceholder":521},[96,777,779,781,784],{"class":98,"line":778},47,[96,780,549],{"class":106},[96,782,783],{"class":102},"switch",[96,785,786],{"class":106},"]$KeepTestVMs\n",[96,788,790],{"class":98,"line":789},48,[96,791,792],{"class":106},")\n",[96,794,796],{"class":98,"line":795},49,[96,797,522],{"emptyLinePlaceholder":521},[96,799,801,804,806],{"class":98,"line":800},50,[96,802,803],{"class":330},"$ErrorActionPreference",[96,805,561],{"class":102},[96,807,808],{"class":136}," 'Stop'\n",[96,810,812,815,817,820,822],{"class":98,"line":811},51,[96,813,814],{"class":106},"$plan ",[96,816,116],{"class":102},[96,818,819],{"class":330}," Import-PowerShellDataFile",[96,821,739],{"class":102},[96,823,824],{"class":106},"Path $PlanPath\n",[96,826,828,831,833,836,839,841,844],{"class":98,"line":827},52,[96,829,830],{"class":106},"$results ",[96,832,116],{"class":102},[96,834,835],{"class":106}," [",[96,837,838],{"class":102},"System.Collections.Generic.List",[96,840,528],{"class":106},[96,842,843],{"class":102},"object",[96,845,846],{"class":106},"]]::new()\n",[96,848,850,853,855,857,859,861,863],{"class":98,"line":849},53,[96,851,852],{"class":106},"$testedVMs ",[96,854,116],{"class":102},[96,856,835],{"class":106},[96,858,838],{"class":102},[96,860,528],{"class":106},[96,862,575],{"class":102},[96,864,846],{"class":106},[96,866,868,871,873,875,878],{"class":98,"line":867},54,[96,869,870],{"class":106},"$runClock ",[96,872,116],{"class":102},[96,874,835],{"class":106},[96,876,877],{"class":102},"System.Diagnostics.Stopwatch",[96,879,880],{"class":106},"]::StartNew()\n",[96,882,884],{"class":98,"line":883},55,[96,885,522],{"emptyLinePlaceholder":521},[96,887,889,892],{"class":98,"line":888},56,[96,890,891],{"class":102},"try",[96,893,894],{"class":106}," {\n",[96,896,898,901,904,907],{"class":98,"line":897},57,[96,899,900],{"class":102},"    foreach",[96,902,903],{"class":106}," ($entry ",[96,905,906],{"class":102},"in",[96,908,909],{"class":106}," $plan.VMs) {\n",[96,911,913,916,918,920,923,926,928],{"class":98,"line":912},58,[96,914,915],{"class":106},"        $row ",[96,917,116],{"class":102},[96,919,835],{"class":106},[96,921,922],{"class":102},"ordered",[96,924,925],{"class":106},"]",[96,927,103],{"class":102},[96,929,107],{"class":106},[96,931,933,936,938],{"class":98,"line":932},59,[96,934,935],{"class":106},"            VM           ",[96,937,116],{"class":102},[96,939,940],{"class":106}," $entry.Name\n",[96,942,944,947,949],{"class":98,"line":943},60,[96,945,946],{"class":106},"            Replication  ",[96,948,116],{"class":102},[96,950,951],{"class":136}," 'Normal'\n",[96,953,955,958,960],{"class":98,"line":954},61,[96,956,957],{"class":106},"            TestVM       ",[96,959,116],{"class":102},[96,961,962],{"class":136}," ''\n",[96,964,966,969,971],{"class":98,"line":965},62,[96,967,968],{"class":106},"            ReadySeconds ",[96,970,116],{"class":102},[96,972,973],{"class":330}," $null\n",[96,975,977,980,982],{"class":98,"line":976},63,[96,978,979],{"class":106},"            FailedChecks ",[96,981,116],{"class":102},[96,983,962],{"class":136},[96,985,987,990,992],{"class":98,"line":986},64,[96,988,989],{"class":106},"            Result       ",[96,991,116],{"class":102},[96,993,994],{"class":136}," 'Pass'\n",[96,996,998],{"class":98,"line":997},65,[96,999,1000],{"class":106},"        }\n",[96,1002,1004],{"class":98,"line":1003},66,[96,1005,522],{"emptyLinePlaceholder":521},[96,1007,1009],{"class":98,"line":1008},67,[96,1010,1011],{"class":324},"        # Replication health first: a Critical replica is a finding before any test runs.\n",[96,1013,1015,1018,1021,1023,1026,1028,1031],{"class":98,"line":1014},68,[96,1016,1017],{"class":102},"        foreach",[96,1019,1020],{"class":106}," ($health ",[96,1022,906],{"class":102},[96,1024,1025],{"class":136}," 'Warning'",[96,1027,153],{"class":102},[96,1029,1030],{"class":136}," 'Critical'",[96,1032,1033],{"class":106},") {\n",[96,1035,1037,1040,1042,1044,1046,1049,1051,1054,1056],{"class":98,"line":1036},69,[96,1038,1039],{"class":102},"            if",[96,1041,733],{"class":106},[96,1043,73],{"class":330},[96,1045,739],{"class":102},[96,1047,1048],{"class":106},"VMName $entry.Name ",[96,1050,751],{"class":102},[96,1052,1053],{"class":106},"ReplicationHealth $health ",[96,1055,751],{"class":102},[96,1057,1058],{"class":106},"ErrorAction SilentlyContinue) {\n",[96,1060,1062,1065,1067],{"class":98,"line":1061},70,[96,1063,1064],{"class":106},"                $row.Replication ",[96,1066,116],{"class":102},[96,1068,1069],{"class":106}," $health\n",[96,1071,1073],{"class":98,"line":1072},71,[96,1074,1075],{"class":106},"            }\n",[96,1077,1079],{"class":98,"line":1078},72,[96,1080,1000],{"class":106},[96,1082,1084],{"class":98,"line":1083},73,[96,1085,522],{"emptyLinePlaceholder":521},[96,1087,1089,1092,1095,1098,1100],{"class":98,"line":1088},74,[96,1090,1091],{"class":102},"        if",[96,1093,1094],{"class":106}," ($row.Replication ",[96,1096,1097],{"class":102},"-eq",[96,1099,1030],{"class":136},[96,1101,1033],{"class":106},[96,1103,1105,1108,1110],{"class":98,"line":1104},75,[96,1106,1107],{"class":106},"            $row.Result ",[96,1109,116],{"class":102},[96,1111,1112],{"class":136}," 'Fail'\n",[96,1114,1116,1119,1121],{"class":98,"line":1115},76,[96,1117,1118],{"class":106},"            $row.FailedChecks ",[96,1120,116],{"class":102},[96,1122,1123],{"class":136}," 'Replication health is Critical; not tested'\n",[96,1125,1127,1130,1133],{"class":98,"line":1126},77,[96,1128,1129],{"class":106},"            $results.Add([",[96,1131,1132],{"class":102},"PSCustomObject",[96,1134,1135],{"class":106},"]$row)\n",[96,1137,1139],{"class":98,"line":1138},78,[96,1140,1141],{"class":102},"            continue\n",[96,1143,1145],{"class":98,"line":1144},79,[96,1146,1000],{"class":106},[96,1148,1150],{"class":98,"line":1149},80,[96,1151,522],{"emptyLinePlaceholder":521},[96,1153,1155],{"class":98,"line":1154},81,[96,1156,1157],{"class":324},"        # Create the test VM; Hyper-V names it after the replica with ' - Test' appended.\n",[96,1159,1161,1164,1166,1168,1170,1173,1175,1178],{"class":98,"line":1160},82,[96,1162,1163],{"class":330},"        Start-VMFailover",[96,1165,739],{"class":102},[96,1167,1048],{"class":106},[96,1169,751],{"class":102},[96,1171,1172],{"class":106},"AsTest ",[96,1174,751],{"class":102},[96,1176,1177],{"class":106},"Confirm:",[96,1179,1180],{"class":330},"$false\n",[96,1182,1184,1187,1189,1192,1194,1197,1200,1203,1205,1208,1211,1214,1216,1219,1222,1225,1227,1230],{"class":98,"line":1183},83,[96,1185,1186],{"class":106},"        $testVM ",[96,1188,116],{"class":102},[96,1190,1191],{"class":330}," Get-VM",[96,1193,739],{"class":102},[96,1195,1196],{"class":106},"Name ",[96,1198,1199],{"class":136},"\"",[96,1201,1202],{"class":102},"$",[96,1204,147],{"class":136},[96,1206,1207],{"class":106},"$entry.Name",[96,1209,1210],{"class":136},")",[96,1212,1213],{"class":136}," - Test\"",[96,1215,739],{"class":102},[96,1217,1218],{"class":106},"ErrorAction SilentlyContinue ",[96,1220,1221],{"class":102},"|",[96,1223,1224],{"class":330}," Select-Object",[96,1226,739],{"class":102},[96,1228,1229],{"class":106},"First ",[96,1231,1232],{"class":330},"1\n",[96,1234,1236],{"class":98,"line":1235},84,[96,1237,522],{"emptyLinePlaceholder":521},[96,1239,1241,1243,1245,1248],{"class":98,"line":1240},85,[96,1242,1091],{"class":102},[96,1244,733],{"class":106},[96,1246,1247],{"class":102},"-not",[96,1249,1250],{"class":106}," $testVM) {\n",[96,1252,1254,1256,1258],{"class":98,"line":1253},86,[96,1255,1107],{"class":106},[96,1257,116],{"class":102},[96,1259,1112],{"class":136},[96,1261,1263,1265,1267],{"class":98,"line":1262},87,[96,1264,1118],{"class":106},[96,1266,116],{"class":102},[96,1268,1269],{"class":136}," 'Test VM was not created'\n",[96,1271,1273,1275,1277],{"class":98,"line":1272},88,[96,1274,1129],{"class":106},[96,1276,1132],{"class":102},[96,1278,1135],{"class":106},[96,1280,1282],{"class":98,"line":1281},89,[96,1283,1141],{"class":102},[96,1285,1287],{"class":98,"line":1286},90,[96,1288,1000],{"class":106},[96,1290,1292],{"class":98,"line":1291},91,[96,1293,522],{"emptyLinePlaceholder":521},[96,1295,1297],{"class":98,"line":1296},92,[96,1298,1299],{"class":106},"        $testedVMs.Add($entry.Name)\n",[96,1301,1303,1306,1308],{"class":98,"line":1302},93,[96,1304,1305],{"class":106},"        $row.TestVM ",[96,1307,116],{"class":102},[96,1309,1310],{"class":106}," $testVM.Name\n",[96,1312,1314,1317,1319,1321,1323],{"class":98,"line":1313},94,[96,1315,1316],{"class":106},"        $clock ",[96,1318,116],{"class":102},[96,1320,835],{"class":106},[96,1322,877],{"class":102},[96,1324,880],{"class":106},[96,1326,1328,1331,1333],{"class":98,"line":1327},95,[96,1329,1330],{"class":330},"        Start-VM",[96,1332,739],{"class":102},[96,1334,1335],{"class":106},"VM $testVM\n",[96,1337,1339],{"class":98,"line":1338},96,[96,1340,522],{"emptyLinePlaceholder":521},[96,1342,1344],{"class":98,"line":1343},97,[96,1345,1346],{"class":324},"        # Wait until the guest answers over PowerShell Direct.\n",[96,1348,1350,1353,1355],{"class":98,"line":1349},98,[96,1351,1352],{"class":106},"        $ready ",[96,1354,116],{"class":102},[96,1356,1357],{"class":330}," $false\n",[96,1359,1361],{"class":98,"line":1360},99,[96,1362,522],{"emptyLinePlaceholder":521},[96,1364,1366,1369],{"class":98,"line":1365},100,[96,1367,1368],{"class":102},"        do",[96,1370,894],{"class":106},[96,1372,1374,1377],{"class":98,"line":1373},101,[96,1375,1376],{"class":102},"            try",[96,1378,894],{"class":106},[96,1380,1382,1385,1387,1390,1392,1395,1397,1400,1403,1406,1408,1411,1413],{"class":98,"line":1381},102,[96,1383,1384],{"class":330},"                Invoke-Command",[96,1386,739],{"class":102},[96,1388,1389],{"class":106},"VMId $testVM.VMId ",[96,1391,751],{"class":102},[96,1393,1394],{"class":106},"Credential $GuestCredential ",[96,1396,751],{"class":102},[96,1398,1399],{"class":106},"ScriptBlock { ",[96,1401,1402],{"class":330},"$true",[96,1404,1405],{"class":106}," } ",[96,1407,751],{"class":102},[96,1409,1410],{"class":106},"ErrorAction Stop ",[96,1412,1221],{"class":102},[96,1414,1415],{"class":330}," Out-Null\n",[96,1417,1419,1422,1424],{"class":98,"line":1418},103,[96,1420,1421],{"class":106},"                $ready ",[96,1423,116],{"class":102},[96,1425,1426],{"class":330}," $true\n",[96,1428,1430,1433,1436],{"class":98,"line":1429},104,[96,1431,1432],{"class":106},"            } ",[96,1434,1435],{"class":102},"catch",[96,1437,894],{"class":106},[96,1439,1441,1444,1446,1449],{"class":98,"line":1440},105,[96,1442,1443],{"class":330},"                Start-Sleep",[96,1445,739],{"class":102},[96,1447,1448],{"class":106},"Seconds ",[96,1450,1451],{"class":330},"15\n",[96,1453,1455],{"class":98,"line":1454},106,[96,1456,1075],{"class":106},[96,1458,1460,1463,1466,1468,1470,1473,1476,1479,1482],{"class":98,"line":1459},107,[96,1461,1462],{"class":106},"        } ",[96,1464,1465],{"class":102},"while",[96,1467,733],{"class":106},[96,1469,1247],{"class":102},[96,1471,1472],{"class":106}," $ready ",[96,1474,1475],{"class":102},"-and",[96,1477,1478],{"class":106}," $clock.Elapsed.TotalMinutes ",[96,1480,1481],{"class":102},"-lt",[96,1483,1484],{"class":106}," $BootTimeoutMinutes)\n",[96,1486,1488],{"class":98,"line":1487},108,[96,1489,522],{"emptyLinePlaceholder":521},[96,1491,1493,1495,1497,1499],{"class":98,"line":1492},109,[96,1494,1091],{"class":102},[96,1496,733],{"class":106},[96,1498,1247],{"class":102},[96,1500,1501],{"class":106}," $ready) {\n",[96,1503,1505,1507,1509],{"class":98,"line":1504},110,[96,1506,1107],{"class":106},[96,1508,116],{"class":102},[96,1510,1112],{"class":136},[96,1512,1514,1516,1518,1521,1524],{"class":98,"line":1513},111,[96,1515,1118],{"class":106},[96,1517,116],{"class":102},[96,1519,1520],{"class":136}," \"Guest did not answer PowerShell Direct within ",[96,1522,1523],{"class":106},"$BootTimeoutMinutes",[96,1525,1526],{"class":136}," minutes\"\n",[96,1528,1530,1532,1534],{"class":98,"line":1529},112,[96,1531,1129],{"class":106},[96,1533,1132],{"class":102},[96,1535,1135],{"class":106},[96,1537,1539],{"class":98,"line":1538},113,[96,1540,1141],{"class":102},[96,1542,1544],{"class":98,"line":1543},114,[96,1545,1000],{"class":106},[96,1547,1549],{"class":98,"line":1548},115,[96,1550,522],{"emptyLinePlaceholder":521},[96,1552,1554,1557,1559,1561,1563],{"class":98,"line":1553},116,[96,1555,1556],{"class":106},"        $row.ReadySeconds ",[96,1558,116],{"class":102},[96,1560,835],{"class":106},[96,1562,647],{"class":102},[96,1564,1565],{"class":106},"]$clock.Elapsed.TotalSeconds\n",[96,1567,1569,1572,1574],{"class":98,"line":1568},117,[96,1570,1571],{"class":330},"        Start-Sleep",[96,1573,739],{"class":102},[96,1575,1576],{"class":106},"Seconds $SettleSeconds\n",[96,1578,1580],{"class":98,"line":1579},118,[96,1581,522],{"emptyLinePlaceholder":521},[96,1583,1585],{"class":98,"line":1584},119,[96,1586,1587],{"class":324},"        # Check services and dependencies from inside the recovered guest.\n",[96,1589,1591,1594,1596,1599,1601,1603,1605,1607,1609,1612,1614,1617,1619],{"class":98,"line":1590},120,[96,1592,1593],{"class":106},"        $checks ",[96,1595,116],{"class":102},[96,1597,1598],{"class":330}," Invoke-Command",[96,1600,739],{"class":102},[96,1602,1389],{"class":106},[96,1604,751],{"class":102},[96,1606,1394],{"class":106},[96,1608,751],{"class":102},[96,1610,1611],{"class":106},"ArgumentList $entry.Services",[96,1613,153],{"class":102},[96,1615,1616],{"class":106}," $entry.Endpoints ",[96,1618,751],{"class":102},[96,1620,1621],{"class":106},"ScriptBlock {\n",[96,1623,1625,1628,1631,1633],{"class":98,"line":1624},121,[96,1626,1627],{"class":102},"            param",[96,1629,1630],{"class":106}," ($services",[96,1632,153],{"class":102},[96,1634,1635],{"class":106}," $endpoints)\n",[96,1637,1639],{"class":98,"line":1638},122,[96,1640,522],{"emptyLinePlaceholder":521},[96,1642,1644,1647,1650,1652],{"class":98,"line":1643},123,[96,1645,1646],{"class":102},"            foreach",[96,1648,1649],{"class":106}," ($serviceName ",[96,1651,906],{"class":102},[96,1653,1654],{"class":106}," $services) {\n",[96,1656,1658,1661,1663,1666,1668,1671,1673],{"class":98,"line":1657},124,[96,1659,1660],{"class":106},"                $service ",[96,1662,116],{"class":102},[96,1664,1665],{"class":330}," Get-Service",[96,1667,739],{"class":102},[96,1669,1670],{"class":106},"Name $serviceName ",[96,1672,751],{"class":102},[96,1674,1675],{"class":106},"ErrorAction SilentlyContinue\n",[96,1677,1679,1682,1684,1686,1688,1691,1693,1696,1699,1701,1704,1706,1708,1711,1714,1716,1719,1721,1724],{"class":98,"line":1678},125,[96,1680,1681],{"class":106},"                [",[96,1683,1132],{"class":102},[96,1685,925],{"class":106},[96,1687,103],{"class":102},[96,1689,1690],{"class":106},"{ Check ",[96,1692,116],{"class":102},[96,1694,1695],{"class":136}," \"service ",[96,1697,1698],{"class":106},"$serviceName",[96,1700,1199],{"class":136},[96,1702,1703],{"class":106},"; Passed ",[96,1705,116],{"class":102},[96,1707,835],{"class":106},[96,1709,1710],{"class":102},"bool",[96,1712,1713],{"class":106},"]($service ",[96,1715,1475],{"class":102},[96,1717,1718],{"class":106}," $service.Status ",[96,1720,1097],{"class":102},[96,1722,1723],{"class":136}," 'Running'",[96,1725,218],{"class":106},[96,1727,1729],{"class":98,"line":1728},126,[96,1730,1075],{"class":106},[96,1732,1734],{"class":98,"line":1733},127,[96,1735,522],{"emptyLinePlaceholder":521},[96,1737,1739,1741,1744,1746],{"class":98,"line":1738},128,[96,1740,1646],{"class":102},[96,1742,1743],{"class":106}," ($endpoint ",[96,1745,906],{"class":102},[96,1747,1748],{"class":106}," $endpoints) {\n",[96,1750,1752,1755,1757,1760,1762,1765,1768],{"class":98,"line":1751},129,[96,1753,1754],{"class":106},"                $target",[96,1756,153],{"class":102},[96,1758,1759],{"class":106}," $port ",[96,1761,116],{"class":102},[96,1763,1764],{"class":106}," $endpoint ",[96,1766,1767],{"class":102},"-split",[96,1769,1770],{"class":136}," ':'\n",[96,1772,1774,1777,1779,1782,1784,1787,1789,1792,1794,1797,1799],{"class":98,"line":1773},130,[96,1775,1776],{"class":106},"                $reachable ",[96,1778,116],{"class":102},[96,1780,1781],{"class":330}," Test-NetConnection",[96,1783,739],{"class":102},[96,1785,1786],{"class":106},"ComputerName $target ",[96,1788,751],{"class":102},[96,1790,1791],{"class":106},"Port $port ",[96,1793,751],{"class":102},[96,1795,1796],{"class":106},"InformationLevel Quiet ",[96,1798,751],{"class":102},[96,1800,1801],{"class":106},"WarningAction SilentlyContinue\n",[96,1803,1805,1807,1809,1811,1813,1815,1817,1820,1823,1825,1827,1829,1831,1833],{"class":98,"line":1804},131,[96,1806,1681],{"class":106},[96,1808,1132],{"class":102},[96,1810,925],{"class":106},[96,1812,103],{"class":102},[96,1814,1690],{"class":106},[96,1816,116],{"class":102},[96,1818,1819],{"class":136}," \"tcp ",[96,1821,1822],{"class":106},"$endpoint",[96,1824,1199],{"class":136},[96,1826,1703],{"class":106},[96,1828,116],{"class":102},[96,1830,835],{"class":106},[96,1832,1710],{"class":102},[96,1834,1835],{"class":106},"]$reachable }\n",[96,1837,1839],{"class":98,"line":1838},132,[96,1840,1075],{"class":106},[96,1842,1844],{"class":98,"line":1843},133,[96,1845,1000],{"class":106},[96,1847,1849],{"class":98,"line":1848},134,[96,1850,522],{"emptyLinePlaceholder":521},[96,1852,1854,1857,1859,1861,1864,1866,1869,1872,1874,1877],{"class":98,"line":1853},135,[96,1855,1856],{"class":106},"        $failed ",[96,1858,116],{"class":102},[96,1860,119],{"class":102},[96,1862,1863],{"class":106},"($checks ",[96,1865,1221],{"class":102},[96,1867,1868],{"class":330}," Where-Object",[96,1870,1871],{"class":106}," { ",[96,1873,1247],{"class":102},[96,1875,1876],{"class":330}," $_",[96,1878,1879],{"class":106},".Passed })\n",[96,1881,1883],{"class":98,"line":1882},136,[96,1884,522],{"emptyLinePlaceholder":521},[96,1886,1888,1890,1893,1896,1899],{"class":98,"line":1887},137,[96,1889,1091],{"class":102},[96,1891,1892],{"class":106}," ($failed.Count ",[96,1894,1895],{"class":102},"-gt",[96,1897,1898],{"class":330}," 0",[96,1900,1033],{"class":106},[96,1902,1904,1906,1908],{"class":98,"line":1903},138,[96,1905,1107],{"class":106},[96,1907,116],{"class":102},[96,1909,1112],{"class":136},[96,1911,1913,1915,1917,1920,1923],{"class":98,"line":1912},139,[96,1914,1118],{"class":106},[96,1916,116],{"class":102},[96,1918,1919],{"class":106}," ($failed.Check) ",[96,1921,1922],{"class":102},"-join",[96,1924,1925],{"class":136}," '; '\n",[96,1927,1929],{"class":98,"line":1928},140,[96,1930,1000],{"class":106},[96,1932,1934],{"class":98,"line":1933},141,[96,1935,522],{"emptyLinePlaceholder":521},[96,1937,1939,1942,1944],{"class":98,"line":1938},142,[96,1940,1941],{"class":106},"        $results.Add([",[96,1943,1132],{"class":102},[96,1945,1135],{"class":106},[96,1947,1949],{"class":98,"line":1948},143,[96,1950,1951],{"class":106},"    }\n",[96,1953,1955,1958,1961],{"class":98,"line":1954},144,[96,1956,1957],{"class":106},"} ",[96,1959,1960],{"class":102},"finally",[96,1962,894],{"class":106},[96,1964,1966],{"class":98,"line":1965},145,[96,1967,1968],{"class":324},"    # Remove the test VMs only after every dependency has been exercised, or when the run stops on an error.\n",[96,1970,1972,1975,1977,1979],{"class":98,"line":1971},146,[96,1973,1974],{"class":102},"    if",[96,1976,733],{"class":106},[96,1978,1247],{"class":102},[96,1980,1981],{"class":106}," $KeepTestVMs) {\n",[96,1983,1985,1987,1990,1992],{"class":98,"line":1984},147,[96,1986,1017],{"class":102},[96,1988,1989],{"class":106}," ($name ",[96,1991,906],{"class":102},[96,1993,1994],{"class":106}," $testedVMs) {\n",[96,1996,1998,2001,2003,2006,2008,2010],{"class":98,"line":1997},148,[96,1999,2000],{"class":330},"            Stop-VMFailover",[96,2002,739],{"class":102},[96,2004,2005],{"class":106},"VMName $name ",[96,2007,751],{"class":102},[96,2009,1177],{"class":106},[96,2011,1180],{"class":330},[96,2013,2015],{"class":98,"line":2014},149,[96,2016,1000],{"class":106},[96,2018,2020],{"class":98,"line":2019},150,[96,2021,1951],{"class":106},[96,2023,2025],{"class":98,"line":2024},151,[96,2026,311],{"class":106},[96,2028,2030],{"class":98,"line":2029},152,[96,2031,522],{"emptyLinePlaceholder":521},[96,2033,2035,2037,2039,2042,2044,2047,2049],{"class":98,"line":2034},153,[96,2036,830],{"class":106},[96,2038,1221],{"class":102},[96,2040,2041],{"class":330}," Export-Csv",[96,2043,739],{"class":102},[96,2045,2046],{"class":106},"Path $EvidencePath ",[96,2048,751],{"class":102},[96,2050,2051],{"class":106},"NoTypeInformation\n",[96,2053,2055,2057,2059,2062,2064],{"class":98,"line":2054},154,[96,2056,830],{"class":106},[96,2058,1221],{"class":102},[96,2060,2061],{"class":330}," Format-Table",[96,2063,739],{"class":102},[96,2065,2066],{"class":106},"AutoSize\n",[96,2068,2070,2073,2076,2078,2081,2083,2085,2088,2090,2093,2096],{"class":98,"line":2069},155,[96,2071,2072],{"class":330},"Write-Output",[96,2074,2075],{"class":136}," \"Test run took ",[96,2077,1202],{"class":102},[96,2079,2080],{"class":136},"([",[96,2082,647],{"class":102},[96,2084,925],{"class":136},[96,2086,2087],{"class":106},"$runClock.Elapsed.TotalMinutes",[96,2089,1210],{"class":136},[96,2091,2092],{"class":136}," minutes. Evidence: ",[96,2094,2095],{"class":106},"$EvidencePath",[96,2097,2098],{"class":136},"\"\n",[11,2100,2101,2102,2105],{},"A first run against a plan like the one above tends to look something like this, and every ",[46,2103,2104],{},"Fail"," row is a finding for the report:",[87,2107,2112],{"className":2108,"code":2110,"language":2111,"meta":92},[2109],"language-text","VM      Replication TestVM          ReadySeconds FailedChecks                      Result\n--      ----------- ------          ------------ ------------                      ------\ndc-dr01 Normal      dc-dr01 - Test           212                                   Pass\nsql01   Warning     sql01 - Test             348                                   Pass\nlic01   Normal      lic01 - Test             190 service \u003Clicense-service-name>    Fail\napp01   Normal      app01 - Test             241 tcp files01:445                   Fail\n","text",[46,2113,2110],{"__ignoreMap":92},[11,2115,2116,2118,2119,2122,2123,2126,2127,2130],{},[46,2117,81],{}," on a test failover turns the test VM off and deletes it; it does not touch the replica or the primary. For Azure Site Recovery the equivalent pair is ",[46,2120,2121],{},"Start-AzRecoveryServicesAsrTestFailoverJob"," (against a recovery plan, with ",[46,2124,2125],{},"-AzureVMNetworkId"," pointing at the isolated network) and ",[46,2128,2129],{},"Start-AzRecoveryServicesAsrTestFailoverCleanupJob",", and the in-guest checks can run the same way over whatever remote access you have to the test network.",[15,2132,2134],{"id":2133},"the-runbook-checklist","The Runbook Checklist",[11,2136,2137],{},"The script covers the mechanical part. The rest is a checklist I keep with the DR plan and fill in during every test:",[11,2139,2140],{},[2141,2142,2143],"strong",{},"Before the test",[2145,2146,2149,2162,2168,2174,2180,2186],"ul",{"className":2147},[2148],"contains-task-list",[2150,2151,2154,2158,2159,2161],"li",{"className":2152},[2153],"task-list-item",[2155,2156],"input",{"disabled":521,"type":2157},"checkbox"," Replication health is Normal for every VM in scope (",[46,2160,73],{},", or Replicated items in the vault), and you know how far behind the last replication is.",[2150,2163,2165,2167],{"className":2164},[2153],[2155,2166],{"disabled":521,"type":2157}," Recovery point chosen deliberately: latest, or the latest application-consistent point for databases.",[2150,2169,2171,2173],{"className":2170},[2153],[2155,2172],{"disabled":521,"type":2157}," Isolated test network in place, using the production address range and with no route back to production. Test switch set on every replicated adapter.",[2150,2175,2177,2179],{"className":2176},[2153],[2155,2178],{"disabled":521,"type":2157}," DC and DNS first in the boot order; the DR DC is a global catalog and holds (or can seize) the FSMO roles the test needs.",[2150,2181,2183,2185],{"className":2182},[2153],[2155,2184],{"disabled":521,"type":2157}," Plan file reviewed against the application owners' list of dependencies, including licence servers, file shares and external APIs.",[2150,2187,2189,2191],{"className":2188},[2153],[2155,2190],{"disabled":521,"type":2157}," Guest credentials that work without production DCs are available, and the break-glass account has been tested.",[11,2193,2194],{},[2141,2195,2196],{},"During the test",[2145,2198,2200,2206,2212,2218],{"className":2199},[2148],[2150,2201,2203,2205],{"className":2202},[2153],[2155,2204],{"disabled":521,"type":2157}," Start time, each VM's ready time and each failed check recorded (the script's CSV).",[2150,2207,2209,2211],{"className":2208},[2153],[2155,2210],{"disabled":521,"type":2157}," Every manual intervention written down with who did it and why. Each one counts against recovery confidence, however quickly it was done.",[2150,2213,2215,2217],{"className":2214},[2153],[2155,2216],{"disabled":521,"type":2157}," Application owners sign in and run a real transaction, not just a ping.",[2150,2219,2221,2223],{"className":2220},[2153],[2155,2222],{"disabled":521,"type":2157}," Licence activation and certificate validation checked on every application that has either.",[11,2225,2226],{},[2141,2227,2228],{},"After the test",[2145,2230,2232,2241,2247],{"className":2231},[2148],[2150,2233,2235,2237,2238,2240],{"className":2234},[2153],[2155,2236],{"disabled":521,"type":2157}," Test VMs removed (",[46,2239,81],{},", or Cleanup test failover in Site Recovery) and replication confirmed healthy again.",[2150,2242,2244,2246],{"className":2243},[2153],[2155,2245],{"disabled":521,"type":2157}," Each finding logged as a fix with an owner and a date, and the plan file updated with any dependency the test discovered.",[2150,2248,2250,2252],{"className":2249},[2153],[2155,2251],{"disabled":521,"type":2157}," A retest of each fixed item scheduled as soon as the fix is in, not at next year's test.",[15,2254,2256],{"id":2255},"running-it-again-on-purpose","Running It Again, On Purpose",[11,2258,2259],{},"Every finding from a real test becomes a fix, and every fix needs to be verified by running the test again — not next year, but as soon as the fix is in, on the specific piece that broke. A test failover that costs no downtime and is driven by a script makes that cheap enough to actually do. A DR plan that's only ever been fully exercised once, however well it went, is still mostly a hypothesis about everything the last test didn't happen to touch.",[15,2261,2263],{"id":2262},"references","References",[2145,2265,2266,2276,2284,2302,2309,2316,2323],{},[2150,2267,2268,2275],{},[2269,2270,2274],"a",{"href":2271,"rel":2272},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fwindows-server\u002Fvirtualization\u002Fhyper-v\u002Freplication-failover",[2273],"nofollow","Fail over a replicated virtual machine with Hyper-V Replica"," (test VM naming, network default, one test failover at a time)",[2150,2277,2278,2283],{},[2269,2279,2282],{"href":2280,"rel":2281},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fwindows-server\u002Fvirtualization\u002Fhyper-v\u002Fmanage\u002Fset-up-hyper-v-replica",[2273],"Hyper-V Replica overview and setup"," (test, planned and unplanned failover, replication intervals, recovery points)",[2150,2285,2286,2291,2292,2296,2297,2301],{},[2269,2287,2290],{"href":2288,"rel":2289},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fpowershell\u002Fmodule\u002Fhyper-v\u002Fstart-vmfailover",[2273],"Start-VMFailover",", ",[2269,2293,81],{"href":2294,"rel":2295},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fpowershell\u002Fmodule\u002Fhyper-v\u002Fstop-vmfailover",[2273]," and ",[2269,2298,73],{"href":2299,"rel":2300},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fpowershell\u002Fmodule\u002Fhyper-v\u002Fget-vmreplication",[2273]," cmdlet references",[2150,2303,2304],{},[2269,2305,2308],{"href":2306,"rel":2307},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fwindows-server\u002Fvirtualization\u002Fhyper-v\u002Fpowershell-direct",[2273],"Manage Windows virtual machines with PowerShell Direct",[2150,2310,2311],{},[2269,2312,2315],{"href":2313,"rel":2314},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fsite-recovery\u002Fsite-recovery-test-failover-to-azure",[2273],"Run a test failover (disaster recovery drill) to Azure",[2150,2317,2318],{},[2269,2319,2322],{"href":2320,"rel":2321},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fsite-recovery\u002Fsite-recovery-active-directory",[2273],"Set up disaster recovery for Active Directory and DNS",[2150,2324,2325],{},[2269,2326,2121],{"href":2327,"rel":2328},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fpowershell\u002Fmodule\u002Faz.recoveryservices\u002Fstart-azrecoveryservicesasrtestfailoverjob",[2273],[2330,2331,2332],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":92,"searchDepth":110,"depth":110,"links":2334},[2335,2336,2337,2338,2339,2340,2341,2342],{"id":17,"depth":110,"text":18},{"id":27,"depth":110,"text":28},{"id":37,"depth":110,"text":38},{"id":56,"depth":110,"text":57},{"id":66,"depth":110,"text":67},{"id":2133,"depth":110,"text":2134},{"id":2255,"depth":110,"text":2256},{"id":2262,"depth":110,"text":2263},"techcolumnist",[2345,2346],"engineering","strategy","2026-07-22T14:00:00Z","What a real DR failover exposes that a tabletop never catches, with a Hyper-V Replica test failover script that checks each recovered VM and a runbook checklist.","md",false,null,{},"\u002Fblog\u002F2026\u002F07\u002F22\u002Finfrastructure-what-a-real-disaster-recovery-test-actually-reveals",{"title":6,"description":2348},[2343],"blog\u002F2026\u002F07\u002F22\u002Finfrastructure-what-a-real-disaster-recovery-test-actually-reveals",[2358,2359],"backup","hyper-v","\u002F2026\u002F07\u002F22\u002Finfrastructure-what-a-real-disaster-recovery-test-actually-reveals\u002F","4z_mrduRPMQcaaEBgAA9RzwdrCjsxPLD75GqT8_OtCU",{"title":2363,"description":2364,"date":2365,"url":2366,"categories":2367,"tags":2368,"image":2351,"readingTime":402,"canonical":2343,"sites":2371,"series":2351,"seriesOrder":2351},"Intune: Conditional Access Policies That Don't Lock Out Your Help Desk","Seven concrete Entra ID Conditional Access policies, the Graph JSON and PowerShell to create them in report-only, and the roles and queries your help desk needs.","2026-08-05T14:00:00Z","\u002F2026\u002F08\u002F05\u002Fintune-conditional-access-policies-that-don-t-lock-out-your-help-desk\u002F",[2345],[2369,2370],"intune","entra-id",[2343],{"title":2373,"description":2374,"date":2375,"url":2376,"categories":2377,"tags":2378,"image":2351,"readingTime":308,"canonical":2343,"sites":2382,"series":2351,"seriesOrder":2351},"Infrastructure: Documenting an Undocumented Network, Discovery First","Document an undocumented network from what it already knows: DHCP leases, AD, ARP and switch MAC tables, then a scoped nmap pass. Scripts included.","2026-07-15T14:00:00Z","\u002F2026\u002F07\u002F15\u002Finfrastructure-a-discovery-first-approach-to-documenting-an-undocumented-network\u002F",[2345,2346],[2379,2380,2381],"discovery","network","python",[2343],[2384,2394,2404],{"title":2385,"description":2386,"date":2387,"url":2388,"categories":2389,"tags":2390,"image":2351,"readingTime":221,"canonical":2343,"sites":2393,"series":2351,"seriesOrder":2351},"Hyper-V to Talos: Rethinking the Virtualization Stack","Why moving workloads off Hyper-V VMs onto a Talos Kubernetes cluster changed the whole stack, with an inventory script and a VM-to-manifest example.","2026-09-16T14:00:00Z","\u002F2026\u002F09\u002F16\u002Fhyper-v-to-talos-rethinking-the-virtualization-stack\u002F",[2345,2346],[2359,2391,2392],"talos","kubernetes",[2343],{"title":2395,"description":2396,"date":2397,"url":2398,"categories":2399,"tags":2400,"image":2351,"readingTime":221,"canonical":2343,"sites":2403,"series":2351,"seriesOrder":2351},"Windows Management: A Discovery Inventory Nobody Maintains by Hand","Why a self-refreshing Windows inventory built from AD and CIM discovery outlasts a spreadsheet, with the sweep script, staleness report and schedule.","2026-09-02T14:00:00Z","\u002F2026\u002F09\u002F02\u002Fwindows-management-building-a-discovery-inventory-nobody-has-to-maintain-by-hand\u002F",[2345,2346],[2379,2401,2402],"windows","active-directory",[2343],{"title":2405,"description":2406,"date":2407,"url":2408,"categories":2409,"tags":2410,"image":2351,"readingTime":302,"canonical":2343,"sites":2412,"series":2351,"seriesOrder":2351},"Azure: Cost Governance Without Killing Developer Velocity","Azure cost governance that catches waste without approval gates: built-in policy guardrails, budget alerts as code, and scheduled cleanup, with the scripts.","2026-08-12T14:00:00Z","\u002F2026\u002F08\u002F12\u002Fazure-cost-governance-without-killing-developer-velocity\u002F",[2345,2346],[2411],"azure",[2343],{"doc":2351,"posts":2414},[],1790052514071]