509d1fc284
H4: Invoke-VmrunBounded added to _Common.psm1 (Start-Process + Wait-Process + taskkill on
timeout, returns {ExitCode;Output;TimedOut;ElapsedSeconds}). Wired in Invoke-CIJob.ps1
for vmrun start (180s) and Remove-BuildVM.ps1 for stop/deleteVM (60s/90s).
H5: Cleanup-OrphanedBuildVMs.ps1 removes stale vm-start.lock (>30 min). ValidateRange
lowered to 0 to allow emergency -MaxAgeHours 0. Invoke-RetentionPolicy.ps1 same.
H8: _Transport.psm1 SSH hardening: StrictHostKeyChecking=accept-new, F:\CI\State\known_hosts.
H9: Validate-DeployState.ps1 Linux branch added: checks ci-report-ip.service enabled,
ci-report-ip.sh executable via SSH. New params: -GuestOS, -SshKeyPath, -SshUser.
H12: Get-GuestIPAddress extracted into _Common.psm1 (guestinfo.ci-ip primary, getGuestIPAddress
fallback). Invoke-CIJob.ps1 Phase 3b replaced with single call. Measure-CIBenchmark.ps1
updated to match.
Also: _Common.psm1 adds Write-CIRedactedCommand and ConvertTo-SafeShellSingleQuotedString.
Pester suite updated: 30/30 pass.
131 lines
5.2 KiB
PowerShell
131 lines
5.2 KiB
PowerShell
#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(SupportsShouldProcess)]
|
||
param(
|
||
[Parameter(Mandatory)]
|
||
[string] $VMPath,
|
||
|
||
[string] $VmrunPath = 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe',
|
||
|
||
[ValidateRange(1, 60)]
|
||
[int] $GracefulTimeoutSeconds = 15
|
||
)
|
||
|
||
Set-StrictMode -Version Latest
|
||
$ErrorActionPreference = 'Continue' # Don't stop on errors — always try next step
|
||
|
||
Import-Module (Join-Path $PSScriptRoot '_Common.psm1') -Force
|
||
|
||
$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) -and $PSCmdlet.ShouldProcess($cloneDir, 'Remove partial clone directory')) {
|
||
Remove-Item $cloneDir -Recurse -Force -ErrorAction SilentlyContinue
|
||
}
|
||
return
|
||
}
|
||
|
||
if (-not $PSCmdlet.ShouldProcess($VMPath, 'Stop and delete build VM')) { return }
|
||
|
||
# ── Step 2: Graceful stop ─────────────────────────────────────────────────────
|
||
Write-Host "[Remove-BuildVM] Sending soft stop (timeout: ${GracefulTimeoutSeconds}s)..."
|
||
$stopSoft = Invoke-VmrunBounded -VmrunPath $VmrunPath `
|
||
-Operation stop -Arguments @($VMPath, 'soft') -TimeoutSeconds $GracefulTimeoutSeconds
|
||
if ($stopSoft.TimedOut) {
|
||
Write-Warning "[Remove-BuildVM] Soft stop timed out after ${GracefulTimeoutSeconds}s."
|
||
} elseif ($stopSoft.ExitCode -eq 0) {
|
||
Write-Host "[Remove-BuildVM] VM stopped gracefully."
|
||
}
|
||
|
||
# ── Step 3: Force stop if still running ──────────────────────────────────────
|
||
if ($stopSoft.TimedOut -or $stopSoft.ExitCode -ne 0) {
|
||
Write-Host "[Remove-BuildVM] Graceful stop did not succeed — forcing hard stop..."
|
||
$stopHard = Invoke-VmrunBounded -VmrunPath $VmrunPath `
|
||
-Operation stop -Arguments @($VMPath, 'hard') -TimeoutSeconds 60
|
||
if ($stopHard.TimedOut) {
|
||
Write-Warning "[Remove-BuildVM] Hard stop timed out after 60s."
|
||
}
|
||
}
|
||
|
||
# Poll vmrun list until VMX is gone — vmware-vmx.exe may hold file locks briefly
|
||
# after stop. Waiting for the entry to disappear ensures deleteVM won't race the
|
||
# process exit.
|
||
$vmxNorm = $VMPath.Trim().ToLower()
|
||
$releaseDeadline = (Get-Date).AddSeconds(20)
|
||
while ((Get-Date) -lt $releaseDeadline) {
|
||
$listedVMs = & $VmrunPath -T ws list 2>&1 |
|
||
Where-Object { $_ -notmatch '^Total running' } |
|
||
ForEach-Object { "$_".Trim().ToLower() }
|
||
if ($listedVMs -notcontains $vmxNorm) { break }
|
||
Start-Sleep -Seconds 2
|
||
}
|
||
|
||
# ── Step 4: Delete VM via vmrun — retry up to 3× with backoff ────────────────
|
||
Write-Host "[Remove-BuildVM] Deleting VM..."
|
||
$deleteOutput = ''
|
||
$deleteExit = -1
|
||
foreach ($delay in @(0, 3, 6)) {
|
||
if ($delay -gt 0) {
|
||
Write-Host "[Remove-BuildVM] Retrying deleteVM in ${delay}s..."
|
||
Start-Sleep -Seconds $delay
|
||
}
|
||
$delResult = Invoke-VmrunBounded -VmrunPath $VmrunPath `
|
||
-Operation deleteVM -Arguments @($VMPath) -TimeoutSeconds 90
|
||
$deleteOutput = $delResult.Output
|
||
$deleteExit = $delResult.ExitCode
|
||
if ($delResult.TimedOut) {
|
||
Write-Warning "[Remove-BuildVM] deleteVM timed out after 90s."
|
||
$deleteExit = -1
|
||
}
|
||
if ($deleteExit -eq 0) { break }
|
||
}
|
||
|
||
if ($deleteExit -ne 0) {
|
||
Write-Warning "[Remove-BuildVM] vmrun deleteVM failed after retries (exit $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."
|