# verify-state.ps1 - VM-side detection runner. Reads the harness matrix.json # from the path given via -MatrixPath, runs the verify block of each app under # the requested -PCType / -PCSubType, prints per-app PASS / FAIL / WARN, and # exits 0 only if every check passes. # # Detection methods supported: # Registry -> Get-ItemProperty $path[$name] -eq $value # File -> Test-Path $path # FileVersion -> (Get-Item $path).VersionInfo.FileVersion -eq $value # Hash -> Get-FileHash SHA256 -eq $value # FileGrep -> Get-Content $path -match $pattern (regex) [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$MatrixPath, [Parameter(Mandatory=$true)] [string]$PCType, [string]$PCSubType ) $ErrorActionPreference = 'Continue' if (-not (Test-Path -LiteralPath $MatrixPath)) { Write-Host "[FAIL] matrix not found at $MatrixPath" exit 1 } $matrix = Get-Content -LiteralPath $MatrixPath -Raw | ConvertFrom-Json $entry = $matrix.pcTypes | Where-Object { $_.PCType -eq $PCType -and ($_.PCSubType -eq $PCSubType -or [string]::IsNullOrEmpty($_.PCSubType)) } | Select-Object -First 1 if (-not $entry) { Write-Host "[FAIL] no matrix entry for PCType=$PCType PCSubType=$PCSubType" exit 1 } function Test-AppState { param($app) $v = $app.verify switch ($v.method) { 'Registry' { if (-not (Test-Path -LiteralPath $v.path)) { return @{ pass=$false; detail="reg path missing: $($v.path)" } } $val = (Get-ItemProperty -LiteralPath $v.path -Name $v.name -ErrorAction SilentlyContinue).$($v.name) if ($null -eq $val) { return @{ pass=$false; detail="reg name $($v.name) not present" } } if ($v.value -and $val -ne $v.value) { return @{ pass=$false; detail="reg $($v.name) = '$val' (expected '$($v.value)')" } } return @{ pass=$true; detail="reg $($v.name) = '$val'" } } 'File' { if (Test-Path -LiteralPath $v.path) { return @{ pass=$true; detail="exists: $($v.path)" } } return @{ pass=$false; detail="missing: $($v.path)" } } 'FileVersion' { if (-not (Test-Path -LiteralPath $v.path)) { return @{ pass=$false; detail="missing: $($v.path)" } } $ver = (Get-Item -LiteralPath $v.path).VersionInfo.FileVersion if ($v.value -and $ver -ne $v.value) { return @{ pass=$false; detail="version $ver (expected $($v.value))" } } return @{ pass=$true; detail="version $ver" } } 'Hash' { if (-not (Test-Path -LiteralPath $v.path)) { return @{ pass=$false; detail="missing: $($v.path)" } } $h = (Get-FileHash -LiteralPath $v.path -Algorithm SHA256).Hash if ($v.value -and $h -ne $v.value) { return @{ pass=$false; detail="hash $h (expected $($v.value))" } } return @{ pass=$true; detail="hash matches" } } 'FileGrep' { if (-not (Test-Path -LiteralPath $v.path)) { return @{ pass=$false; detail="missing: $($v.path)" } } $hit = Get-Content -LiteralPath $v.path | Select-String -Pattern $v.pattern -Quiet if ($hit) { return @{ pass=$true; detail="pattern matched: $($v.pattern)" } } return @{ pass=$false; detail="pattern not found: $($v.pattern)" } } default { return @{ pass=$false; detail="unknown method: $($v.method)" } } } } $total = 0; $passed = 0; $failed = @() foreach ($app in $entry.apps) { $total++ $r = Test-AppState -app $app if ($r.pass) { Write-Host " [PASS] $($app.name) - $($r.detail)" $passed++ } else { Write-Host " [FAIL] $($app.name) - $($r.detail)" $failed += $app.name } } Write-Host "" Write-Host "=== verify summary: $passed/$total passed ===" if ($failed.Count -gt 0) { Write-Host "failed: $($failed -join ', ')" exit 1 } exit 0