test harness: Path A (imaging chain) for Standard-Machine

Smokes end-to-end on the win11 VM in ~14s for Standard/Machine: 11/11
stage scripts exit 0 (6 Shopfloor baseline + 5 Standard per-PC-type),
transcripts land in C:\Logs\SFLD\ as expected.

Pieces:

- stage-image.ps1 - VM-side: clean prior state, robocopy shopfloor-setup
  tree from samba share to C:\Enrollment\shopfloor-setup, drop pc-type +
  pc-subtype + site-config, walk numbered stage scripts (^[0-9]{2}-) in
  Shopfloor/ then <PCType>/, run each, collect rc + summary. Skips PPKG /
  sync_intune / reboot - real machine identity is not touched.
- A-imaging/run.sh - host orchestrator: revert, stage repo tree to
  /home/camp/pxe-images/test-stage-A, mount Z: in VM as SYSTEM, invoke
  stage-image.ps1 with PCType/PCSubType params, collect transcripts.
  Optional PREINSTALL_PATH env if you have the binary installer payload
  available; default skips it (00-PreInstall logs "installer not found"
  for every entry, expected for orchestration-only test - per-app installs
  are covered by Path B).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-05-02 17:50:02 -04:00
parent eaf2dbf167
commit b4e5152471
2 changed files with 211 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env bash
# A-imaging/run.sh - exercise the shopfloor-setup imaging chain on the win11
# VM (Shopfloor baseline + per-PC-type stage scripts), stopping before
# PPKG / Intune handoff. Skips actual binary installs by default; orchestration-
# only test. Path B exercises the per-app installs.
#
# Usage:
# ./run.sh # Standard / Machine
# ./run.sh CMM # CMM (no subtype)
# ./run.sh Standard Timeclock # explicit
#
# Env:
# VM_DOMAIN override VM name (default win11)
# SKIP_REVERT=1 skip blank-slate snapshot revert (faster iteration)
# PREINSTALL_PATH UNC to a preinstall payload tree (default unset = skip 00-PreInstall installs)
set -euo pipefail
PCTYPE="${1:-Standard}"
PCSUBTYPE="${2:-Machine}"
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEST_ROOT="$(cd "$HERE/.." && pwd)"
REPO_ROOT="$(cd "$TEST_ROOT/../../../.." && pwd)"
source "$TEST_ROOT/lib/qga.sh"
if [[ -t 1 ]]; then
G='\033[32m'; R='\033[31m'; Y='\033[33m'; D='\033[0m'
else
G=''; R=''; Y=''; D=''
fi
phase() { printf "\n${Y}=== %s ===${D}\n" "$*"; }
ok() { printf "${G}[ OK ]${D} %s\n" "$*"; }
fail() { printf "${R}[FAIL]${D} %s\n" "$*"; FAILED=1; }
FAILED=0
phase "A-imaging.run for PCType=$PCTYPE PCSubType=$PCSUBTYPE"
if [[ "${SKIP_REVERT:-}" != "1" ]]; then
vm_revert_to_blank_slate
else
log "skipping snapshot revert (SKIP_REVERT=1)"
vm_start_if_needed
fi
phase "1. stage shopfloor-setup tree on samba share"
# Stage from the live repo into a host-samba-accessible location so the VM
# can robocopy across.
SETUP_STAGE="/home/camp/pxe-images/test-stage-A/shopfloor-setup"
rm -rf "/home/camp/pxe-images/test-stage-A"
mkdir -p "$SETUP_STAGE"
cp -r "$REPO_ROOT/playbook/shopfloor-setup/." "$SETUP_STAGE/"
log "staged $(du -sh "$SETUP_STAGE" | cut -f1) at $SETUP_STAGE"
# Also stage the A-imaging chain runner (the PS1 we just wrote) so the VM
# can invoke it with parameters.
cp "$HERE/stage-image.ps1" "/home/camp/pxe-images/test-stage-A/stage-image.ps1"
# matrix.json + verify-state.ps1 for post-run state check
cp "$TEST_ROOT/matrix.json" "/home/camp/pxe-images/test-stage-A/matrix.json"
cp "$TEST_ROOT/lib/verify-state.ps1" "/home/camp/pxe-images/test-stage-A/verify-state.ps1"
vm_mount_share
phase "2. run imaging chain on VM"
qga_run_ps <<EOF
\$ErrorActionPreference='Continue'
# Pull stage-image.ps1 + verify-state.ps1 + matrix to local C:\\
New-Item -ItemType Directory -Force -Path 'C:\\Tools\\test-harness' | Out-Null
[System.IO.File]::WriteAllBytes('C:\\Tools\\test-harness\\stage-image.ps1', [System.IO.File]::ReadAllBytes('Z:\\test-stage-A\\stage-image.ps1'))
[System.IO.File]::WriteAllBytes('C:\\Tools\\test-harness\\verify-state.ps1', [System.IO.File]::ReadAllBytes('Z:\\test-stage-A\\verify-state.ps1'))
[System.IO.File]::WriteAllBytes('C:\\Tools\\test-harness\\matrix.json', [System.IO.File]::ReadAllBytes('Z:\\test-stage-A\\matrix.json'))
# Run the chain - SetupSrcPath points at the host samba mirror so the runner
# robocopies it onto C:\\Enrollment\\shopfloor-setup.
& C:\\Tools\\test-harness\\stage-image.ps1 -PCType '$PCTYPE' -PCSubType '$PCSUBTYPE' -SetupSrcPath 'Z:\\test-stage-A\\shopfloor-setup' ${PREINSTALL_PATH:+-PreInstallSrcPath '$PREINSTALL_PATH'}
EOF
RUN_RC=$?
if [[ $RUN_RC -eq 0 ]]; then
ok "stage chain returned exit 0"
else
fail "stage chain returned exit $RUN_RC"
fi
phase "3. collect transcripts"
qga_run_ps <<'EOF' || true
"--- C:\Logs\SFLD ---"
Get-ChildItem C:\Logs\SFLD -ErrorAction SilentlyContinue | Select-Object Name,Length,LastWriteTime
"--- C:\Logs\PreInstall ---"
Get-ChildItem C:\Logs\PreInstall -ErrorAction SilentlyContinue | Select-Object Name,Length,LastWriteTime
"--- ran stage scripts (last 60 lines per transcript) ---"
foreach ($f in Get-ChildItem C:\Logs\SFLD -Filter '*.log' -ErrorAction SilentlyContinue) {
"=== $($f.FullName) ==="
Get-Content $f.FullName -Tail 30
}
EOF
phase "result"
if [[ $FAILED -eq 0 ]]; then
ok "A-imaging: chain completed for $PCTYPE/$PCSUBTYPE"
exit 0
else
fail "A-imaging: chain had failures for $PCTYPE/$PCSUBTYPE"
exit 1
fi

View File

@@ -0,0 +1,103 @@
# A-imaging/stage-image.ps1 - run the imaging chain on the VM, stop before
# PPKG / Intune. Mimics what Run-ShopfloorSetup does up to the per-PC-type
# stage scripts; skips:
# - PPKG install (changes machine identity, rebooty)
# - sync_intune registration (Azure-bound)
# - final reboot
#
# Verifies the chain integrity (script ordering, PCTypes filter logic,
# transcripts written, expected stage scripts present per type) without
# touching real machine identity or burning a real provisioning cycle.
#
# Real binary installers (Oracle / VC++ / OpenText / UDC) live on the
# live PXE share, not in this repo, so 00-PreInstall-MachineApps.ps1 will
# log "Installer file not found" for every entry it tries to install.
# That is expected for orchestration-only testing - Path B covers actual
# install verification per app.
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)] [string]$PCType,
[string]$PCSubType = '',
[Parameter(Mandatory=$true)] [string]$SetupSrcPath, # UNC to repo's playbook/shopfloor-setup, e.g. \\host\share\shopfloor-setup
[string]$PreInstallSrcPath = '' # optional UNC to a preinstall payload tree; if missing we skip 00-PreInstall
)
$ErrorActionPreference = 'Continue'
function Log { param([string]$m) Write-Host "[$(Get-Date -Format HH:mm:ss)] $m" }
Log "=== A-imaging: stage chain for PCType=$PCType PCSubType=$PCSubType ==="
# --- Clean prior test state ---
Get-Process powershell -ErrorAction SilentlyContinue | Where-Object { $_.Id -ne $PID } | Stop-Process -Force -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force 'C:\Enrollment' -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force 'C:\Logs\SFLD' -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force 'C:\Logs\PreInstall' -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force 'C:\PreInstall' -ErrorAction SilentlyContinue
# --- Stage shopfloor-setup tree ---
Log "copying $SetupSrcPath -> C:\Enrollment\shopfloor-setup"
New-Item -ItemType Directory -Force -Path 'C:\Enrollment\shopfloor-setup' | Out-Null
robocopy $SetupSrcPath 'C:\Enrollment\shopfloor-setup' /E /R:2 /W:2 /NFL /NDL /NJH /NJS /NC /NS | Out-Null
# --- Drop pc-type / pc-subtype / site-config ---
$PCType | Set-Content -Path 'C:\Enrollment\pc-type.txt' -Encoding ascii
$PCSubType | Set-Content -Path 'C:\Enrollment\pc-subtype.txt' -Encoding ascii
@{
siteName = 'WJ (A-imaging test)'
shopfloorShareRoot = '\\192.168.122.1\pxe-images\tsgwp00525-v2\shared\dt\shopfloor'
common = @{ commonAppsSharePath = '\\192.168.122.1\pxe-images\tsgwp00525-v2\shared\dt\shopfloor\common\apps' }
} | ConvertTo-Json -Depth 5 | Set-Content -Path 'C:\Enrollment\site-config.json' -Encoding ascii
# --- Stage PreInstall payload if provided ---
if ($PreInstallSrcPath -and (Test-Path $PreInstallSrcPath)) {
Log "copying $PreInstallSrcPath -> C:\PreInstall"
New-Item -ItemType Directory -Force -Path 'C:\PreInstall' | Out-Null
robocopy $PreInstallSrcPath 'C:\PreInstall' /E /R:2 /W:2 /NFL /NDL /NJH /NJS /NC /NS | Out-Null
} else {
Log "no PreInstall payload provided - 00-PreInstall step will skip"
}
# --- Run stage scripts in order: Shopfloor baseline first, then per-PC-type ---
$setupDir = 'C:\Enrollment\shopfloor-setup'
$stageRan = @()
function Run-StageScripts {
param([string]$Dir)
if (-not (Test-Path $Dir)) { return }
$scripts = Get-ChildItem $Dir -Filter '*.ps1' -File -ErrorAction SilentlyContinue |
Where-Object { $_.Name -match '^[0-9]{2}-' } |
Sort-Object Name
foreach ($s in $scripts) {
Log "==> $($s.FullName)"
$rc = 0
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $s.FullName 2>&1 | ForEach-Object { Write-Host " $_" }
if ($LASTEXITCODE) { $rc = $LASTEXITCODE }
Log " exit $rc"
$script:stageRan += [pscustomobject]@{ Path = $s.FullName; ExitCode = $rc }
}
}
# 1. Shopfloor baseline scripts (run for every PC type, including Standard)
Run-StageScripts (Join-Path $setupDir 'Shopfloor')
# 2. Per-PC-type stage scripts. Standard / CMM / Keyence / etc each have their
# own subdir with numbered scripts. Skip "Shopfloor" since that's PCType==Shopfloor.
if ($PCType -ne 'Shopfloor') {
$typeDir = Join-Path $setupDir $PCType
Run-StageScripts $typeDir
}
# --- Summary ---
Log "=== chain summary ==="
foreach ($r in $stageRan) {
$tag = if ($r.ExitCode -eq 0) { '[ OK ]' } else { '[FAIL]' }
Log " $tag rc=$($r.ExitCode) $($r.Path)"
}
$failedCount = ($stageRan | Where-Object { $_.ExitCode -ne 0 }).Count
$totalCount = $stageRan.Count
Log "=== A-imaging summary: $($totalCount - $failedCount)/$totalCount stage scripts exited 0 ==="
if ($failedCount -gt 0) { exit 1 }
exit 0