Private/Confirm-Win32ToolkitGuestDesktop.ps1

function Confirm-Win32ToolkitGuestDesktop {
    <#
    .SYNOPSIS
        Ensures the guest is at a logged-in interactive desktop; reboots to trigger AutoLogon if not.
    .DESCRIPTION
        The interactive Hyper-V test mode runs the PSADT GUI in the logged-on user's session, so it needs
        a real desktop (explorer.exe). If a checkpoint landed on the login screen instead, this recovers:
        it reboots the guest, and — because AutoLogon is configured (Set-Win32ToolkitGuestAutoLogon) —
        Windows logs straight back into the desktop. Returns $true once explorer is running, $false if it
        could not reach a desktop within the timeout (e.g. AutoLogon isn't configured). HOST-ONLY.
    .PARAMETER VMName
        The test VM.
    .PARAMETER Credential
        The guest credential (PowerShell Direct).
    .PARAMETER TimeoutMinutes
        How long to wait for the desktop after a recovery reboot (default 5).
    .OUTPUTS
        [bool]
    #>

    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$VMName,
        [Parameter(Mandatory)] [ValidateNotNull()]        [pscredential]$Credential,
        [ValidateRange(1, 30)] [int]$TimeoutMinutes = 5
    )

    $hasDesktop = {
        [bool](Invoke-Command -VMName $VMName -Credential $Credential -ScriptBlock {
            [bool](Get-Process -Name explorer -ErrorAction SilentlyContinue)
        } -ErrorAction SilentlyContinue)
    }

    if (& $hasDesktop) { return $true }

    Write-Warning 'No interactive desktop detected (login screen?) — rebooting the guest to trigger AutoLogon...'
    Write-Warning "This recovery costs 1-3 minutes on EVERY interactive run — re-take the checkpoint at a logged-in desktop to fix it durably (Reset-Win32ToolkitTestVM, log in via vmconnect, then re-checkpoint from the TUI's Hyper-V screen)."
    Invoke-Command -VMName $VMName -Credential $Credential -ScriptBlock { Restart-Computer -Force } -ErrorAction SilentlyContinue

    # Wait for the shutdown to actually BEGIN (heartbeat leaves 'OK') before waiting for the guest to come
    # back — polling at 1 s replaces the old blind 20 s sleep; the 20 s CAP keeps the worst case identical
    # (if the heartbeat never dips we proceed exactly as before and the ready-wait below sorts it out).
    $dipDeadline = (Get-Date).AddSeconds(20)
    do {
        $hb = (Get-VMIntegrationService -VMName $VMName -Name 'Heartbeat' -ErrorAction SilentlyContinue).PrimaryStatusDescription
        if ($hb -ne 'OK') { break }
        Start-Sleep -Seconds 1
    } until ((Get-Date) -gt $dipDeadline)
    try { Wait-Win32ToolkitVMReady -VMName $VMName -Credential $Credential -SkipPrep -HeartbeatTimeoutSec 180 -PSDirectTimeoutSec 300 | Out-Null } catch { }

    $deadline = (Get-Date).AddMinutes($TimeoutMinutes)
    while (-not (& $hasDesktop) -and (Get-Date) -lt $deadline) { Start-Sleep -Seconds 10 }
    return [bool](& $hasDesktop)
}