Skip to content

feat: add Azure as a boot-test hypervisor (run the boot test on an ephemeral Azure VM) #7

Description

@DevSecNinja

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

  1. 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.
  2. 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.
  3. 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).
  4. 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.
  5. 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.
  6. 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).
  7. Interactive connect (Start-AzureVmConnect): print how to watch it (boot diagnostics/serial console, or RDP/Bastion) since there's no local console window.
  8. 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.
  9. 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).
  10. 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.ps1ValidateSet + 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

  • -Hypervisor Azure provisions 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).
  • A not-ready environment (no az / not logged in / no subscription) yields a clear None result, not a boot failure.
  • Windows Setup logs are harvested into DiagnosticsPath.
  • -KeepBootTestVm leaves the VM up with a teardown hint; the Wait-BootTestInspection hold works (power-off/Enter → cleanup).
  • All Azure access is mocked in the unit suite; full Pester suite green; PSScriptAnalyzer clean.
  • CI: an opt-in (manual/labelled) Azure boot-test job in ci-cd.yml using az + OIDC/service-principal auth.

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions