#Requires -Version 5.1 <# .SYNOPSIS Stops and permanently deletes an ephemeral build VM. .DESCRIPTION Performs a graceful shutdown (soft stop) with a timeout fallback to a hard stop, then uses vmrun deleteVM to remove all VM files. Finally, removes the clone directory from disk. This script is designed to be called from a try/finally block so it always runs, even on build failure. .PARAMETER VMPath Full path to the clone VM's .vmx file. .PARAMETER VmrunPath Full path to vmrun.exe. Default: C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe .PARAMETER GracefulTimeoutSeconds Seconds to wait for graceful shutdown before forcing power-off. Default: 15 .EXAMPLE .\Remove-BuildVM.ps1 -VMPath "F:\CI\BuildVMs\Clone_run-42_20260508_143022\Clone_run-42_20260508_143022.vmx" #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $VMPath, [string] $VmrunPath = 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe', [ValidateRange(5, 60)] [int] $GracefulTimeoutSeconds = 15 ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Continue' # Don't stop on errors — always try next step $cloneDir = Split-Path $VMPath -Parent Write-Host "[Remove-BuildVM] Destroying VM: $VMPath" # ── Step 1: Check if VM exists at all ──────────────────────────────────────── if (-not (Test-Path $VMPath -PathType Leaf)) { Write-Warning "[Remove-BuildVM] VMX file not found — nothing to remove: $VMPath" # Still attempt to remove the directory in case of partial clone if (Test-Path $cloneDir) { Remove-Item $cloneDir -Recurse -Force -ErrorAction SilentlyContinue } return } # ── Step 2: Graceful stop ───────────────────────────────────────────────────── Write-Host "[Remove-BuildVM] Sending soft stop..." & $VmrunPath -T ws stop $VMPath soft 2>&1 | Out-Null $deadline = (Get-Date).AddSeconds($GracefulTimeoutSeconds) while ((Get-Date) -lt $deadline) { Start-Sleep -Seconds 2 $stateOutput = & $VmrunPath -T ws getState $VMPath 2>&1 if ($stateOutput -notmatch 'running') { Write-Host "[Remove-BuildVM] VM stopped gracefully." break } } # ── Step 3: Force stop if still running ────────────────────────────────────── $stateOutput = & $VmrunPath -T ws getState $VMPath 2>&1 if ($stateOutput -match 'running') { Write-Host "[Remove-BuildVM] Graceful stop timed out — forcing hard stop..." & $VmrunPath -T ws stop $VMPath hard 2>&1 | Out-Null Start-Sleep -Seconds 3 } # ── Step 4: Delete VM via vmrun (removes VMX + delta VMDK) ─────────────────── Write-Host "[Remove-BuildVM] Deleting VM..." $deleteOutput = & $VmrunPath -T ws deleteVM $VMPath 2>&1 $deleteExit = $LASTEXITCODE if ($deleteExit -ne 0) { Write-Warning "[Remove-BuildVM] vmrun deleteVM returned exit code $deleteExit : $deleteOutput" Write-Warning "[Remove-BuildVM] Will attempt manual directory removal." } # ── Step 5: Remove clone directory (catches any leftover files) ─────────────── if (Test-Path $cloneDir) { Write-Host "[Remove-BuildVM] Removing clone directory: $cloneDir" Remove-Item $cloneDir -Recurse -Force -ErrorAction SilentlyContinue if (Test-Path $cloneDir) { Write-Warning "[Remove-BuildVM] Clone directory could not be fully removed: $cloneDir" Write-Warning " Manual cleanup required." } else { Write-Host "[Remove-BuildVM] Clone directory removed." } } Write-Host "[Remove-BuildVM] VM destruction complete."