Adds rows for Standard-Timeclock, CMM, Keyence, Lab, WaxAndTrace, Genspect, Display, Shopfloor alongside the existing Standard-Machine. Per-type apps verified against the corresponding v2 manifest's detection methods (PC-DMIS 2016/2019R2/Protect Viewer/CLM/goCMM for CMM; VR-6000/USB driver for Keyence; kiosk shortcut for Display). Common app list deduped via "$ref": "common.<key>" pattern. Verifier resolves refs into the per-type apps array at runtime so each row stays short and PCTypes-filter-aware (Lab + Display + Shopfloor get fewer common apps because the manifest's PCTypes filter excludes them from FMS hosts pin / Oracle / OpenText respectively). verify-state.ps1 changes: - $ref resolution against the matrix.common namespace - Registry method now permits no DetectionName (key-existence only, e.g. Protect Viewer) - New PnpUtilGrep method for INF-driver checks (Keyence USB driver) Smoke-verified end-to-end on the win11 VM as SYSTEM via qga - 60 checks across 9 PC types. Type-specific failures (5 CMM, 2 Keyence, 1 Display) correctly surface "no payload staged" rather than masking it as pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
133 lines
5.3 KiB
PowerShell
133 lines
5.3 KiB
PowerShell
# 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
|
|
}
|
|
|
|
# Resolve `{ "$ref": "common.<key>" }` entries: matrix.json uses $ref to dedup
|
|
# common app lists (e.g. "common.all", "common.fmsResolver") so per-PC-type
|
|
# rows can compose without copy/paste. Walk the apps list, expand each ref
|
|
# inline, drop the ref placeholder.
|
|
$resolvedApps = @()
|
|
foreach ($a in $entry.apps) {
|
|
$refVal = $a.PSObject.Properties['$ref']
|
|
if ($refVal) {
|
|
$parts = $refVal.Value -split '\.'
|
|
if ($parts.Count -eq 2 -and $parts[0] -eq 'common') {
|
|
$key = $parts[1]
|
|
$list = $matrix.common.$key
|
|
if ($list) {
|
|
foreach ($x in $list) { $resolvedApps += $x }
|
|
continue
|
|
}
|
|
Write-Host "[WARN] unresolved `$ref: $($refVal.Value)"
|
|
continue
|
|
}
|
|
Write-Host "[WARN] unsupported `$ref form: $($refVal.Value)"
|
|
continue
|
|
}
|
|
$resolvedApps += $a
|
|
}
|
|
|
|
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)" } }
|
|
# No DetectionName/value -> key existence is enough (e.g. Protect Viewer entry)
|
|
if (-not $v.name) {
|
|
return @{ pass=$true; detail="reg key exists: $($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)" }
|
|
}
|
|
'PnpUtilGrep' {
|
|
$drivers = & pnputil /enum-drivers 2>&1 | Out-String
|
|
if ($drivers -match $v.pattern) {
|
|
return @{ pass=$true; detail="pnputil match: $($v.pattern)" }
|
|
}
|
|
return @{ pass=$false; detail="pnputil no match for: $($v.pattern)" }
|
|
}
|
|
default { return @{ pass=$false; detail="unknown method: $($v.method)" } }
|
|
}
|
|
}
|
|
|
|
$total = 0; $passed = 0; $failed = @()
|
|
foreach ($app in $resolvedApps) {
|
|
$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
|