Skip to content

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

  1. Creates a differencing disk from a sysprepped base VHDX (the base is never modified).
  2. Boots the VM and connects via PowerShell Direct (no network required).
  3. Copies the engine + the config under test into the guest.
  4. Runs the requested phases in-guest and collects a structured outcome: overall status, per-phase results, and baseline compliance %.
  5. Asserts the outcome with Totl.TestKit (Test-TotlProvisionOutcome) against an expectations spec.
  6. 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$true to fail if any phase failed.
  • requirePhases — phases that must have success.
  • 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.