VM test harness¶
build/Invoke-TotlVmTest.ps1 provisions a throwaway Hyper-V VM with a client config, asserts the
outcome, and tears it down. It is the gate that must pass before enabling destructive operations
(live sidReassign profile migration) on any real machine — "VM-gated, fully unit-tested before any
client box."
What it does¶
- Creates a differencing disk from a sysprepped base VHDX (the base is never modified).
- Boots the VM and connects via PowerShell Direct (no network required).
- Copies the engine + the config under test into the guest.
- Runs the requested phases in-guest and collects a structured outcome: overall status, per-phase results, and baseline compliance %.
- Asserts the outcome with
Totl.TestKit(Test-TotlProvisionOutcome) against an expectations spec. - Deletes the VM (unless
-KeepVm).
The assertion engine (Test-TotlProvisionOutcome) is pure and unit-tested
(engine/tests/Totl.TestKit.Tests.ps1); the harness feeds it real run data.
Prerequisites¶
- A Hyper-V host (Windows 10/11 Pro or Server).
- A base VHDX: a sysprepped Windows 11 image with a known local-admin account and PowerShell Direct usable (default on modern Windows guests).
- Run the host session elevated.
Running it¶
$cred = Get-Credential # the base image's local admin
.\build\Invoke-TotlVmTest.ps1 `
-BaseVhdx D:\base\win11.vhdx `
-ConfigPath .\engine\config\clients\contoso.config.json `
-GuestCredential $cred `
-Expect @{ status = 'success'; forbidFailedPhases = $true; requirePhases = @('baseline'); minComplianceScore = 90 }
Exit code is non-zero on assertion failure, so it drops into CI.
Expectations spec¶
-Expect is a hashtable understood by Test-TotlProvisionOutcome:
status— required overall status (success|partial|failed).forbidFailedPhases—$trueto fail if any phase failed.requirePhases— phases that must havesuccess.minComplianceScore— minimum baseline compliance %.
CI¶
.github/workflows/vm-harness.yml runs the harness on demand (workflow_dispatch) on a self-hosted
Windows runner labelled hyperv (GitHub-hosted runners can't nest Hyper-V reliably). The base VHDX
path and guest credential come from repo secrets (TOTL_VM_BASE_VHDX, TOTL_VM_GUEST_USER,
TOTL_VM_GUEST_PWD).
Why this gates live migration¶
The native sidReassign path (ProfileList rewrite / icacls /substitute / NTUSER.DAT hive ACL) is
destructive and irreversible if wrong. Its plan and argument builders are already unit-tested, but
the apply is deliberately withheld until this harness validates a full migration cycle on a VM with
before/after assertions. Once the harness passes a migration scenario, the apply can be enabled behind
migrate.requireVM=false for controlled rollout — never before.