Summary
Add Azure as a third -Hypervisor option (alongside HyperV and VMware) so the opt-in boot test can run the throwaway VM in Azure — primarily for CI and hosts without a local hypervisor, and to validate the unattended install with real internet (useful for the 24H2 ConX online product-key/edition path, see #5).
Related: #5 (ConX online validation needs real DNS/network), #6 (WinPE NIC-driver injection for VMware). Azure's nested Hyper-V guest uses the inbox netvsc NIC, so — unlike VMware — WinPE networking works out of the box, making Azure a clean way to exercise the online path in CI.
Motivation
How the current abstraction works (what a new provider must implement)
Invoke-VmBootTest (in src/WindowsIsoMaker/Public/Test-ImageIntegrity.ps1) dispatches on $Hypervisor ([ValidateSet('HyperV','VMware')], default HyperV) through mockable per-provider seams. A HyperV/VMware pair exists for each; Azure adds a third:
| Concern |
HyperV |
VMware |
Azure (new) |
| Readiness |
Get-HyperVReadiness |
Get-VMwareReadiness |
Get-AzureReadiness (az CLI present + logged in + subscription/quota) |
| Create + start |
New-BootTestVm |
New-VMwareBootTestVm |
New-AzureBootTestVm |
| Interactive connect |
Start-BootTestVmConnect (vmconnect) |
Start-VMwareVmConnect (vmware -t) |
Start-AzureVmConnect (serial console / Bastion / RDP hint) |
| Boot status poll |
Get-VmBootStatus |
Get-VMwareVmBootStatus |
Get-AzureVmBootStatus (power state / boot diagnostics) |
| Teardown |
Remove-BootTestVm |
Remove-VMwareBootTestVm |
Remove-AzureBootTestVm (delete VM + disks + NIC + RG) |
The shared result contract (Method ∈ None/Heartbeat/StayedRunning/BootReset/Timeout/Error, plus Passed/State) and the polling/hold logic (Wait-BootTestInspection) should be reused unchanged.
The core challenge: Azure VMs don't boot from an arbitrary ISO
Unlike local hypervisors, an Azure VM boots from a managed OS disk, not a mounted ISO running Windows Setup. Two candidate approaches:
Approach A — Nested-virtualization runner (recommended)
Provision an ephemeral Azure VM on a size that supports nested virtualization (e.g. Dv3/Dv4/Dv5, Ev3+), install/enable Hyper-V on it, copy the built -bootcheck ISO up, then run the existing Hyper-V boot-test path inside that Azure VM and harvest the result back.
- Pros: reuses the whole Hyper-V provider (incl. WinPE
netvsc networking + real internet); faithfully tests the same ISO→Setup unattended flow we ship; clean pass/fail via the existing contract.
- Cons: provisioning time + cost; needs remote orchestration (e.g.
az vm run-command invoke / Custom Script Extension, or WinRM/SSH) to drive the inner boot test and copy logs back.
Approach B — Boot a produced VHD as a native Azure VM (secondary / future)
Convert the installed image to a Gen2 fixed VHD, upload to a managed disk, and create a VM from it to prove the produced golden image boots in Azure. This validates the result, not the unattended install from the ISO, so it's a different (complementary) scope — capture as an open question, not the initial deliverable.
Recommendation: implement Approach A first (it matches the existing "boot the bootcheck ISO" semantics); leave B as a possible later mode.
Requirements
- Add
Azure to the -Hypervisor ValidateSet in build.ps1, scripts/Invoke-QuickBootTest.ps1, src/WindowsIsoMaker/Public/Invoke-IsoBuild.ps1, and Invoke-VmBootTest (Test-ImageIntegrity.ps1), plus the tab-completion advisory lists.
- New provider
src/WindowsIsoMaker/Private/AzureBootTest.ps1 implementing the seams above via the Azure CLI (az) — per repo/user convention, prefer az over azure/* GitHub Actions.
- All Azure calls behind mockable seams (e.g.
Invoke-AzCli) so the polling/orchestration is unit-tested the same way HyperV/VMware are (no live Azure in the unit suite).
- Readiness (
Get-AzureReadiness): az installed, an authenticated context, a resolvable subscription, and (best-effort) region/size/quota check. On not-ready, emit a None (environmental) result with an actionable reason — never a false boot failure — mirroring the VMware winget/consent pattern.
- Cost & safety: everything ephemeral in a dedicated resource group with a unique name; guaranteed teardown in the
finally path (VM + OS/data disks + NIC + public IP + RG); support -KeepBootTestVm to leave it up for manual inspection with a clear teardown hint; sensible auto-timeout so a hung run can't run up cost.
- Auth: support both interactive (
az login) and non-interactive CI (service principal / OIDC federated credentials via az login --service-principal / --federated-token). Document required RBAC (Contributor on the RG/subscription).
- Interactive connect (
Start-AzureVmConnect): print how to watch it (boot diagnostics/serial console, or RDP/Bastion) since there's no local console window.
- Log harvest: retrieve Windows Setup logs from the inner boot test (Approach A: copy from the nested guest back to the Azure VM, then download; or via
az vm run-command output) into the existing DiagnosticsPath.
- Docs: new
docs/azure-boot-test.md (prereqs, auth, sizes that support nested virt, cost/teardown, CI usage) + update docs/usage.md; wire an opt-in Azure boot-test job into the single ci-cd.yml (gated, not on every PR, given cost).
- Config: allow
Hypervisor = 'Azure' and Azure settings (subscription, location, VM size, RG prefix) via config/params with env-var overrides for CI.
Conventions to honour (from repo/user prefs)
- Use the Azure CLI (
az), not azure/* Actions (those lag on deprecated runtimes).
- Pin the
az version via mise; pin any other tooling via Renovate (# renovate: + custom manager).
- Keep it in the single
ci-cd.yml pipeline.
- Minimal / trusted (Microsoft-maintained) dependencies.
- No long-lived hosted infra — everything ephemeral and torn down.
Affected files
build.ps1, scripts/Invoke-QuickBootTest.ps1, src/WindowsIsoMaker/Public/Invoke-IsoBuild.ps1, src/WindowsIsoMaker/Public/Test-ImageIntegrity.ps1 — ValidateSet + dispatch + completers.
src/WindowsIsoMaker/Private/AzureBootTest.ps1 (new) + tests/AzureBootTest.Tests.ps1 (new).
config/ default Hypervisor + Azure settings; .mise.toml for az; ci-cd.yml opt-in job; docs/azure-boot-test.md, docs/usage.md.
Acceptance criteria
Open questions
- Nested virt (Approach A) vs. native-VHD boot (Approach B) — start with A?
- Orchestration mechanism for the inner boot test:
az vm run-command vs. Custom Script Extension vs. WinRM/SSH.
- Image for the outer Azure VM (Windows Server + Hyper-V) and cheapest size that supports nested virtualization.
- Auth model for CI: OIDC federated credentials (preferred) vs. service-principal secret.
- Cost guardrails: default auto-timeout, max VM size, region selection.
Summary
Add
Azureas a third-Hypervisoroption (alongsideHyperVandVMware) so the opt-in boot test can run the throwaway VM in Azure — primarily for CI and hosts without a local hypervisor, and to validate the unattended install with real internet (useful for the 24H2 ConX online product-key/edition path, see #5).Related: #5 (ConX online validation needs real DNS/network), #6 (WinPE NIC-driver injection for VMware). Azure's nested Hyper-V guest uses the inbox
netvscNIC, so — unlike VMware — WinPE networking works out of the box, making Azure a clean way to exercise the online path in CI.Motivation
How the current abstraction works (what a new provider must implement)
Invoke-VmBootTest(insrc/WindowsIsoMaker/Public/Test-ImageIntegrity.ps1) dispatches on$Hypervisor([ValidateSet('HyperV','VMware')], defaultHyperV) through mockable per-provider seams. A HyperV/VMware pair exists for each; Azure adds a third:Get-HyperVReadinessGet-VMwareReadinessGet-AzureReadiness(az CLI present + logged in + subscription/quota)New-BootTestVmNew-VMwareBootTestVmNew-AzureBootTestVmStart-BootTestVmConnect(vmconnect)Start-VMwareVmConnect(vmware -t)Start-AzureVmConnect(serial console / Bastion / RDP hint)Get-VmBootStatusGet-VMwareVmBootStatusGet-AzureVmBootStatus(power state / boot diagnostics)Remove-BootTestVmRemove-VMwareBootTestVmRemove-AzureBootTestVm(delete VM + disks + NIC + RG)The shared result contract (
Method∈ None/Heartbeat/StayedRunning/BootReset/Timeout/Error, plusPassed/State) and the polling/hold logic (Wait-BootTestInspection) should be reused unchanged.The core challenge: Azure VMs don't boot from an arbitrary ISO
Unlike local hypervisors, an Azure VM boots from a managed OS disk, not a mounted ISO running Windows Setup. Two candidate approaches:
Approach A — Nested-virtualization runner (recommended)
Provision an ephemeral Azure VM on a size that supports nested virtualization (e.g. Dv3/Dv4/Dv5, Ev3+), install/enable Hyper-V on it, copy the built
-bootcheckISO up, then run the existing Hyper-V boot-test path inside that Azure VM and harvest the result back.netvscnetworking + real internet); faithfully tests the same ISO→Setup unattended flow we ship; clean pass/fail via the existing contract.az vm run-command invoke/ Custom Script Extension, or WinRM/SSH) to drive the inner boot test and copy logs back.Approach B — Boot a produced VHD as a native Azure VM (secondary / future)
Convert the installed image to a Gen2 fixed VHD, upload to a managed disk, and create a VM from it to prove the produced golden image boots in Azure. This validates the result, not the unattended install from the ISO, so it's a different (complementary) scope — capture as an open question, not the initial deliverable.
Recommendation: implement Approach A first (it matches the existing "boot the bootcheck ISO" semantics); leave B as a possible later mode.
Requirements
Azureto the-HypervisorValidateSetinbuild.ps1,scripts/Invoke-QuickBootTest.ps1,src/WindowsIsoMaker/Public/Invoke-IsoBuild.ps1, andInvoke-VmBootTest(Test-ImageIntegrity.ps1), plus the tab-completion advisory lists.src/WindowsIsoMaker/Private/AzureBootTest.ps1implementing the seams above via the Azure CLI (az) — per repo/user convention, preferazoverazure/*GitHub Actions.Invoke-AzCli) so the polling/orchestration is unit-tested the same way HyperV/VMware are (no live Azure in the unit suite).Get-AzureReadiness):azinstalled, an authenticated context, a resolvable subscription, and (best-effort) region/size/quota check. On not-ready, emit aNone(environmental) result with an actionable reason — never a false boot failure — mirroring the VMwarewinget/consent pattern.finallypath (VM + OS/data disks + NIC + public IP + RG); support-KeepBootTestVmto leave it up for manual inspection with a clear teardown hint; sensible auto-timeout so a hung run can't run up cost.az login) and non-interactive CI (service principal / OIDC federated credentials viaaz login --service-principal/--federated-token). Document required RBAC (Contributor on the RG/subscription).Start-AzureVmConnect): print how to watch it (boot diagnostics/serial console, or RDP/Bastion) since there's no local console window.az vm run-commandoutput) into the existingDiagnosticsPath.docs/azure-boot-test.md(prereqs, auth, sizes that support nested virt, cost/teardown, CI usage) + updatedocs/usage.md; wire an opt-in Azure boot-test job into the singleci-cd.yml(gated, not on every PR, given cost).Hypervisor = 'Azure'and Azure settings (subscription, location, VM size, RG prefix) via config/params with env-var overrides for CI.Conventions to honour (from repo/user prefs)
az), notazure/*Actions (those lag on deprecated runtimes).azversion via mise; pin any other tooling via Renovate (# renovate:+ custom manager).ci-cd.ymlpipeline.Affected files
build.ps1,scripts/Invoke-QuickBootTest.ps1,src/WindowsIsoMaker/Public/Invoke-IsoBuild.ps1,src/WindowsIsoMaker/Public/Test-ImageIntegrity.ps1—ValidateSet+ dispatch + completers.src/WindowsIsoMaker/Private/AzureBootTest.ps1(new) +tests/AzureBootTest.Tests.ps1(new).config/defaultHypervisor+ Azure settings;.mise.tomlforaz;ci-cd.ymlopt-in job;docs/azure-boot-test.md,docs/usage.md.Acceptance criteria
-Hypervisor Azureprovisions an ephemeral Azure VM, runs the bootcheck-ISO boot test (Approach A), returns the standard pass/fail contract, and always tears everything down (verified on a real subscription).az/ not logged in / no subscription) yields a clearNoneresult, not a boot failure.DiagnosticsPath.-KeepBootTestVmleaves the VM up with a teardown hint; theWait-BootTestInspectionhold works (power-off/Enter → cleanup).ci-cd.ymlusingaz+ OIDC/service-principal auth.Open questions
az vm run-commandvs. Custom Script Extension vs. WinRM/SSH.