#Requires -Version 5.1 <# .SYNOPSIS Waits until a build VM is fully ready to accept WinRM connections. .DESCRIPTION Performs a three-phase readiness check in a polling loop: 1. vmrun getState returns "running" 2. ICMP ping succeeds 3. Test-WSMan (WinRM) responds without error Throws if the VM does not become ready within the timeout period. .PARAMETER VMPath Full path to the clone VM's .vmx file. .PARAMETER IPAddress IP address of the VM on the build network (e.g. 192.168.11.101). .PARAMETER TimeoutSeconds Maximum seconds to wait for the VM to become ready. Default: 300 (5 minutes) .PARAMETER PollIntervalSeconds Seconds between readiness checks. Default: 5 .PARAMETER VmrunPath Full path to vmrun.exe. Default: C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe .EXAMPLE .\Wait-VMReady.ps1 ` -VMPath "F:\CI\BuildVMs\Clone_run-42_20260508_143022\Clone_run-42_20260508_143022.vmx" ` -IPAddress "192.168.11.101" #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $VMPath, [Parameter(Mandatory)] [ValidatePattern('^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$')] [string] $IPAddress, [ValidateRange(3, 600)] [int] $TimeoutSeconds = 300, [ValidateRange(1, 30)] [int] $PollIntervalSeconds = 5, [string] $VmrunPath = 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe', # Skip ICMP ping phase (phase 2). Useful when the guest firewall blocks ICMP # but WinRM (TCP 5986) is open. Phase 1 (vmrun state) and phase 3 (WinRM) # are still checked. [switch] $SkipPing ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' if (-not (Test-Path $VmrunPath -PathType Leaf)) { throw "vmrun.exe not found at: $VmrunPath" } $deadline = (Get-Date).AddSeconds($TimeoutSeconds) $attempt = 0 $phase = 'vmrun-state' # tracks current phase for informative output $lastPhase = '' Write-Host "[Wait-VMReady] Waiting for VM ready (timeout: ${TimeoutSeconds}s, IP: $IPAddress)..." while ((Get-Date) -lt $deadline) { $attempt++ # ── Phase 1: VM must be running ───────────────────────────────────── # vmrun has no getState; getGuestIPAddress exits non-zero when the VM # is not powered on. Native commands don't throw — check exit code only. & $VmrunPath getGuestIPAddress $VMPath 2>&1 | Out-Null $isRunning = ($LASTEXITCODE -eq 0) if (-not $isRunning) { if ($phase -ne 'vmrun-state') { $phase = 'vmrun-state' } if ($lastPhase -ne $phase) { Write-Host "[Wait-VMReady] Phase 1/3: waiting for VM to enter running state..." $lastPhase = $phase } Start-Sleep -Seconds $PollIntervalSeconds continue } # ── Phase 2: Network ping (optional) ─────────────────────────────── if (-not $SkipPing) { $pingOk = Test-Connection -ComputerName $IPAddress -Count 1 -Quiet -ErrorAction SilentlyContinue if (-not $pingOk) { $phase = 'ping' if ($lastPhase -ne $phase) { Write-Host "[Wait-VMReady] Phase 2/3: VM running, waiting for network (ping $IPAddress)..." $lastPhase = $phase } Start-Sleep -Seconds $PollIntervalSeconds continue } } # ── Phase 3: WinRM port 5986 accepting TCP connections ─────────────── # Test-WSMan has no -SessionOption in PS 5.1 and fails cert validation for # self-signed certs. TCP open on 5986 = HTTPS listener up = VM ready. $winrmUp = Test-NetConnection -ComputerName $IPAddress -Port 5986 ` -InformationLevel Quiet -WarningAction SilentlyContinue ` -ErrorAction SilentlyContinue if ($winrmUp) { Write-Host "[Wait-VMReady] VM ready after ~$([int]($attempt * $PollIntervalSeconds))s (attempt $attempt)." return } else { $phase = 'winrm' if ($lastPhase -ne $phase) { Write-Host "[Wait-VMReady] Phase 3/3: ping OK, waiting for WinRM on port 5986 (HTTPS)..." $lastPhase = $phase } Start-Sleep -Seconds $PollIntervalSeconds continue } } throw "[Wait-VMReady] Timed out after ${TimeoutSeconds}s waiting for VM at $IPAddress to become ready. Last phase: $phase"