Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
152 commits
Select commit Hold shift + click to select a range
d19ac00
nuc-wim: config-driven baked install.wim pipeline (Packer, Azure buil…
markcor Jul 24, 2026
c278174
nuc-wim: add ci/kickoff-nuc-wim-build.ps1 (build-host kickoff, used b…
markcor Jul 24, 2026
f4c045e
win-hw-wim: rename pipeline from nuc-wim (generic Windows HW WIM pre-…
markcor Jul 24, 2026
5d68cf6
win-hw-wim: config-driven driver injection + resolve WIM index by edi…
markcor Jul 24, 2026
19fbb55
win-hw-wim: set win11-24h2-hw base edition to Windows 11 Enterprise
markcor Jul 24, 2026
b1f5b65
win-hw-wim: pin ronin bake to wim-bake-role @ 4463ab7 (ronin_puppet#1…
markcor Jul 24, 2026
f87f577
win-hw-wim: use short SHA (4463ab7) for ronin hash to match deploymen…
markcor Jul 24, 2026
1bc32a8
win-hw-wim: add temp-testing image config win11-24h2-hw-test
markcor Jul 24, 2026
8a24866
win-hw-wim review pass 1 (correctness): bin-dir fallback + robust dri…
markcor Jul 24, 2026
75e366b
win-hw-wim review pass 2 (robustness): input guards + build-host rebo…
markcor Jul 24, 2026
b3466e6
win-hw-wim review pass 3 (consistency): docs match Entra-only reality
markcor Jul 24, 2026
0d31028
win-hw-wim: packer fmt (fix pre-commit packer_fmt hook)
markcor Jul 24, 2026
06caefb
win-hw-wim: make azcopy actually use the identity (AZCLI auto-login +…
markcor Jul 24, 2026
66108b0
win-hw-wim: ephemeral build-VM lifecycle + poll-to-completion
markcor Jul 24, 2026
fe1046f
win-hw-wim: attach pre-provisioned user-assigned identity (no per-run…
markcor Jul 24, 2026
02048be
win-hw-wim: fix infinite recursion in az helper (call depth overflow)
markcor Jul 24, 2026
1c8af59
win-hw-wim: portable password gen (System.Web absent in pwsh 7 on Linux)
markcor Jul 24, 2026
c3b23c6
win-hw-wim: set short --computer-name (Windows 15-char limit)
markcor Jul 24, 2026
f43a253
win-hw-wim: fix silent bootstrap no-op (phase passing, PATH, fail-loud)
markcor Jul 27, 2026
7a7eece
win-hw-wim: destroy sweeps leaked NIC/disks by name prefix (failed-cr…
markcor Jul 27, 2026
2246716
win-hw-wim: fix bootstrap script path on Linux runner + inline phase
markcor Jul 27, 2026
364c9a9
win-hw-wim: capture stderr from run-command (value[].message)
markcor Jul 27, 2026
22684bf
win-hw-wim: fix nested-virt build VM — Standard security type + WS202…
markcor Jul 27, 2026
5fea10f
win-hw-wim: use D64ads_v5 (D96ads_v5 exceeds the 64-core DADSv5 quota)
markcor Jul 27, 2026
1b9e1bc
win-hw-wim: fix PowerShell parse error in bootstrap ($DataDriveLetter:)
markcor Jul 27, 2026
4560e16
win-hw-wim: drop nonexistent choco pkg windows-adk-deploymenttools (D…
markcor Jul 27, 2026
e423243
fix(win-hw-wim): stop powershell-yaml Install-Module hang in run-command
markcor Jul 27, 2026
5bf9ecf
fix(win-hw-wim): fix quote escaping in kickoff scheduled-task arg
markcor Jul 27, 2026
1925f4d
fix(win-hw-wim): don't single-quote scheduled-task args (literal quot…
markcor Jul 27, 2026
d76c021
fix(win-hw-wim): az login with the user-assigned identity on the buil…
markcor Jul 27, 2026
d1afcb3
fix(win-hw-wim): use az login --identity --client-id (2.88 dropped --…
markcor Jul 27, 2026
30cf374
fix(win-hw-wim): replace PS7 ternary in download-wim (build runs unde…
markcor Jul 27, 2026
d177dc9
fix(win-hw-wim): drop azcopy --auth-mode (removed from copy in 10.32)
markcor Jul 27, 2026
d931032
fix(win-hw-wim): don't Join-Path an already-rooted OutVhdx
markcor Jul 27, 2026
218e4ba
feat(win-hw-wim): NAT switch for the nested bake VM (no 'Default Swit…
markcor Jul 27, 2026
8536279
fix(win-hw-wim): run packer against the dir, not a single file
markcor Jul 27, 2026
964c50d
fix(win-hw-wim): add required publicKeyToken to unattend components
markcor Jul 27, 2026
7205132
fix(win-hw-wim): auto-answer OOBE region/keyboard (International-Core)
markcor Jul 27, 2026
f4e2129
fix(win-hw-wim): fully-unattended OOBE, modeled on MDC1 base-autounat…
markcor Jul 27, 2026
6111398
fix(win-hw-wim): create WinRM listener on the NAT (Public) network
markcor Jul 27, 2026
d59387f
perf(win-hw-wim): max bake VM resources + put artifacts on the data disk
markcor Jul 28, 2026
bdcfadd
fix(win-hw-wim): cap bake VM memory at 32 GiB (Packer hyperv builder …
markcor Jul 28, 2026
efc4730
fix(win-hw-wim): set up WinRM in specialize pass, not FirstLogonCommands
markcor Jul 28, 2026
ddbd760
fix(win-hw-wim): build the packer VM on the data disk (temp_path)
markcor Jul 28, 2026
614b592
fix(win-hw-wim): repair specialize unattend parse failure (over-long …
markcor Jul 28, 2026
8aa3e44
fix(win-hw-wim): NTLM WinRM auth + Private profile + post-specialize …
markcor Jul 28, 2026
dcbb390
feat(win-hw-wim): make the bake-time Windows Update pass a per-image …
markcor Jul 28, 2026
5fc0433
fix(win-hw-wim): whole-build boot watchdog + reliable Private profile…
markcor Jul 28, 2026
f8b3299
debug(win-hw-wim): surface puppet-apply failures + keep failed VM for…
markcor Jul 28, 2026
7cf40ae
feat(win-hw-wim): plumb a build-scoped GitHub token into the bake (to…
markcor Jul 28, 2026
b862009
fix(win-hw-wim): fix bake puppet compile (rc=1) — no-BOM manifests + …
markcor Jul 28, 2026
0c8ac06
fix(win-hw-wim): run bake puppet apply as SYSTEM (fixes NgcCtnrSvc rc=6)
markcor Jul 28, 2026
7f0cfe2
fix(win-hw-wim): sysprep-generalize — parseable AppX-leftover warning
markcor Jul 28, 2026
baaf1da
style(win-hw-wim): packer fmt — drop stray alignment on temp_path
markcor Jul 28, 2026
161ffcb
refactor(win-hw-wim): make the pre-Sysprep AppX guard ASCII/robust
markcor Jul 28, 2026
91ff202
fix(win-hw-wim): drop em-dash inside Sysprep warning string
markcor Jul 28, 2026
86853cd
refactor(win-hw-wim): drop the dead pre-Sysprep AppX guard
markcor Jul 28, 2026
da9a99d
fix(win-hw-wim): capture-wim verification must not fail the build on …
markcor Jul 29, 2026
358f3aa
fix(win-hw-wim): robust managed-identity az login on the build VM
markcor Jul 29, 2026
8d8341a
fix(win-hw-wim): az login probe must not die under ErrorActionPrefere…
markcor Jul 29, 2026
b452ab2
feat(MDC1Windows): relops1213 WIM-apply canary on the feature branch
markcor Jul 29, 2026
1f072c2
feat(MDC1Windows): DISM /Apply-Image deploy path for pre-baked WIMs
markcor Jul 29, 2026
510acf0
fix(MDC1Windows): locate + mount the ESP before bcdboot (was hardcode…
markcor Jul 29, 2026
d5a10cd
feat(win-hw-wim): bake OpenSSH + SetupComplete first-boot runner into…
markcor Jul 29, 2026
3f8d1ad
fix(win-hw-wim): re-assert bake network/WinRM on every boot (mid-bake…
markcor Jul 29, 2026
7438ee1
fix(win-hw-wim): make the kickoff build.done poll resilient (was hang…
markcor Jul 29, 2026
c7cd415
chore(MDC1Windows): point relops1213 canary at the SSH+SetupComplete …
markcor Jul 29, 2026
7b21547
fix(win-hw-wim): run first-boot bootstrap via a baked SYSTEM startup …
markcor Jul 29, 2026
95fc039
fix(win-hw-wim): retry the transient StepCloneVM/secure-boot flake in…
markcor Jul 30, 2026
6b50185
fix(win-hw-wim): stop the boot watchdog racing packer at StepCloneVM …
markcor Jul 30, 2026
f4e2d85
revert(win-hw-wim): drop the ineffective in-place packer-build retry
markcor Jul 30, 2026
21184f3
test(win-hw-wim): option 1 - rebuild the SOURCE VM on a StepCloneVM f…
markcor Jul 30, 2026
bf8102d
fix(win-hw-wim): kickoff poll crashed on multi-line status (killed go…
markcor Jul 30, 2026
0910fc1
chore(MDC1Windows): point relops1213 canary at the startup-task golde…
markcor Jul 30, 2026
96fdc2f
fix(win-hw-wim): detect bake completion via blob, not run-command (ki…
markcor Jul 30, 2026
d47aa16
bake: add admin-group SSH key + nxlog log shipping to golden WIM
markcor Jul 30, 2026
bf1026e
bake: enable built-in Administrator + disable packer acct for deploy …
markcor Jul 30, 2026
97288b4
ci(win-hw-wim): keep az authed across the long bake (OIDC re-login)
markcor Jul 31, 2026
0b30b4f
MDC1Windows: relops1213 image -> win11-24h2-hw-test-20260730-223108
markcor Jul 31, 2026
09982ce
ci(win-hw-wim): only accept a completion marker newer than kickoff
markcor Jul 31, 2026
61b2318
canary(RELOPS-2487): consume prebaked artifacts in the deploy bootstrap
markcor Jul 31, 2026
7f2c717
canary(RELOPS-2487): version-guard nxlog install (consume prebake)
markcor Jul 31, 2026
3260646
canary(RELOPS-2487): nxlog guard = presence, not version
markcor Jul 31, 2026
2261ab3
canary(RELOPS-2487): deterministically set node name offline in DISM …
markcor Jul 31, 2026
d972886
bake: drop C:\ronin after the puppet run so deploy re-clones fresh
markcor Jul 31, 2026
24dfe0d
MDC1Windows: bump relops1213 ronin hash d409da5 -> adf1034 (AppX log …
markcor Jul 31, 2026
ff1072a
canary(RELOPS-2487): bake NuGet+modules, drop deploy NuGet poll, skip…
markcor Jul 31, 2026
83f7807
MDC1Windows: relops1213 image -> win11-24h2-hw-test-20260731-190855
markcor Aug 3, 2026
7a03908
MDC1Windows: bump relops1213 ronin hash adf1034 -> c3b9793
markcor Aug 3, 2026
0dbb23c
OS-deploy: cache the baked WIM on D: instead of recopying ~6 GB every…
markcor Aug 3, 2026
8acdc40
OS-deploy: quick-format C: before DISM apply (clean target)
markcor Aug 3, 2026
8d65460
MDC1Windows: bump relops1213 ronin hash c3b9793 -> 8d713d9
markcor Aug 3, 2026
0ca9df1
win-hw-wim: bake NUC13 drivers into the WIM (scalable .cab/.zip list)
markcor Aug 3, 2026
560f41e
MDC1Windows: relops1213 -> drivers-in-WIM image + ronin be05a38
markcor Aug 3, 2026
35a1271
MDC1Windows: relops1213 ronin hash be05a38 -> 883b4d8
markcor Aug 3, 2026
47d109d
bootstrap: on-host driver install for the pre-baked deploy path (RELO…
markcor Aug 3, 2026
1457e39
bootstrap: temporarily disable Install-Drivers call (RELOPS-2487)
markcor Aug 3, 2026
5323c97
win-hw-wim: bake the Intel graphics driver (32.0.101.7085) into the WIM
markcor Aug 4, 2026
9baa437
win-hw-wim: fix multi-cab arg passing (-File can't bind array params)
markcor Aug 4, 2026
2c56233
MDC1Windows: relops1213 -> drivers-in-WIM image 20260804-175453 + ron…
markcor Aug 4, 2026
159039f
MDC1Windows: relops1213 ronin hash 0d530e1 -> 5230f32 (Set-PXE re-ena…
markcor Aug 4, 2026
533f827
MDC1Windows: point perf-debug pool at prebake (our branches + drivers…
markcor Aug 5, 2026
e8aa9c7
win-hw-wim: scrub baked autologon (Winlogon) keys before capture (bak…
markcor Aug 5, 2026
f59996a
OS-deploy: drop offline rename so first boot runs the real generalize…
markcor Aug 5, 2026
0953ebc
Revert "OS-deploy: drop offline rename so first boot runs the real ge…
markcor Aug 5, 2026
cebb757
MDC1Windows: bump canary pools to WIM 20260805-193514 (autologon-scru…
markcor Aug 5, 2026
c17e6c1
win-hw-wim: add 'iso' stage - build a requirement-bypass Windows 11 ISO
markcor Aug 6, 2026
847242c
win-hw-wim: add iso: section to win11-24h2-hw.yaml (POC, reuse same c…
markcor Aug 6, 2026
3bc91e1
win-hw-wim: keep the iso builder in its own config (distinct from the…
markcor Aug 6, 2026
071130f
win-hw-wim: thread -Stages through kickoff -> run-build-task -> New-W…
markcor Aug 6, 2026
5084932
win-hw-wim: config-driven ISO toggle (iso.enabled) instead of a stage…
markcor Aug 6, 2026
0e05798
win-hw-wim: modular iso builder — base + scripts inject-library (noch…
markcor Aug 6, 2026
444ed21
win-hw-wim: 25H2 media + base-WIM-from-ISO fallback
markcor Aug 6, 2026
1a78250
win-hw-wim: iso stage self-provisions oscdimg from our blob
markcor Aug 6, 2026
19e7357
win-hw-wim: iso output -> captured/ with golden-WIM naming
markcor Aug 6, 2026
549deb5
Get-Bootstrap: stop calling Set-WinRM (always fails); keep the function
markcor Aug 7, 2026
3e95d0a
win-hw-wim: retarget storage to hardwareimaging + resources/captured …
markcor Aug 7, 2026
f4f9520
win-hw-wim: rebrand infra refs nuc-wim -> hardware-imaging
markcor Aug 7, 2026
be31cae
win-hw-wim iso: offline driver injection into boot.wim + install.wim
markcor Aug 7, 2026
85a968c
win11-24h2-hw: bump ronin pin 4463ab7 -> 48a8b9d4 (fixes marlin_pw co…
markcor Aug 10, 2026
1876078
win-hw-wim: 60m restart timeout + fix the flake classifier that ate r…
markcor Aug 11, 2026
7506574
win11-24h2-hw: turn OFF bake-time Windows Update
markcor Aug 11, 2026
44caa86
win-hw-wim: stream the build log live into the GH job, plus a filtere…
markcor Aug 11, 2026
73afd13
win-hw-wim: generate release notes / SBOM the same way the Azure imag…
markcor Aug 11, 2026
25f2dbf
win-hw-wim: drop the SolarWinds tail for a PowerShell Direct stall ca…
markcor Aug 11, 2026
6543896
win-hw-wim: silence az blob JSON, which drowned the live log 8:1
markcor Aug 12, 2026
0070ba0
perf-debug: point the canary at the win11-24h2-hw production WIM
markcor Aug 18, 2026
ca1eeb8
perf-debug: bump ronin hash to 48a8b9d4, matching the new WIM's bake
markcor Aug 18, 2026
822ad09
pools: take main's node membership for every pool except relops1213
markcor Aug 18, 2026
4c476a9
pools: sync relops1213's nodes from main too, clearing the 159/160 ov…
markcor Aug 18, 2026
0a89a79
ref-alpha: point at the same baked WIM as perf-debug
markcor Aug 18, 2026
794b780
ref-alpha: align puppet with perf-debug (wim-bake-role @ 48a8b9d4)
markcor Aug 18, 2026
d8dd24f
win11-24h2-hw: bump ronin pin to b64eb0b5 (bake stops disabling AppXSvc)
markcor Aug 20, 2026
1721ae5
pools: point perf-debug + ref-alpha at win11-24h2-hw-20260820-193703
markcor Aug 20, 2026
f949eb0
win11-24h2-hw: bump ronin pin to 7dac7f0a (bake disables AppXSvc again)
markcor Aug 20, 2026
3000bb7
OS-deploy: re-assert the node name after specialize via SetupComplete…
markcor Aug 20, 2026
b461314
OS-deploy: randomise the deployment sign-off line
markcor Aug 21, 2026
b519d71
pools: point perf-debug + ref-alpha at win11-24h2-hw-20260820-235936
markcor Aug 21, 2026
bba83a0
pools: perf-debug + ref-alpha to ronin f1eeb1fd (AppX Write-Log fix)
markcor Aug 21, 2026
acd1c13
pools: perf-debug + ref-alpha to ronin aaf6203f (self_redeploy_check …
markcor Aug 21, 2026
47ea36d
win-hw-wim: stage in-guest bake payloads to C:\bake\extras (Intel Gra…
markcor Aug 26, 2026
579b7de
pools: move the two canary pools to ronin 4c2e223d
markcor Aug 26, 2026
3147fd0
win11-24h2-hw: bump the bake's ronin pin to 4c2e223d
markcor Aug 26, 2026
6c97f88
win-hw-wim: fix the bake's puppet log encoding so class output is gre…
markcor Aug 26, 2026
edb4026
pools: point the canary pools at win11-24h2-hw-20260826-180420
markcor Aug 26, 2026
3e9142d
win-hw-wim: inject the platform/chipset driver packs so baked nodes m…
markcor Aug 27, 2026
7a35d34
win-hw-wim: stop the boot watchdog racing Packer's initial Start-VM
markcor Aug 27, 2026
b84e1a3
pools: point the canary pools at win11-24h2-hw-20260827-174334
markcor Aug 27, 2026
cb635f4
pools: align ref-alpha's git_version with perf-debug
markcor Sep 4, 2026
7a4157d
win-hw-wim: ship the Intel graphics installer in the WIM at C:\extras
markcor Sep 8, 2026
9a2963e
pools: point the canary pools at win11-24h2-hw-20260908-172915
markcor Sep 8, 2026
4a82154
pools: bump the canary pools to ronin 459102e1 (master merged in)
markcor Sep 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions ci/az-relogin.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<#
.SYNOPSIS
Re-authenticate the Azure CLI mid-job using the GitHub Actions OIDC token.

.DESCRIPTION
The workflow authenticates once via azure/login (OIDC). That yields an Azure access
token valid for ~1h that CANNOT be refreshed. The WIM bake step runs ~1h40m+, so by
the time the completion marker appears the runner's token is dead and every
`az ... --auth-mode login` call fails silently — the poll never detects completion
(240-min timeout) and the always() teardown can't delete the VM (quota leak).

This re-runs `az login` with a FRESH GitHub OIDC token. The job has
`permissions: id-token: write`, so any step can mint a new OIDC JWT on demand; a token
minted in the same job carries the same subject claim azure/login already succeeded
with, so the federated login succeeds again and yields a fresh ~1h Azure token.

Client/tenant/subscription are recovered from the CACHED az profile when not passed:
`az account show` reads ~/.azure locally and works even after the access token has
expired (SP login -> user.name is the app/client id). So callers need thread NO
secrets through the workflow YAML — this fix stays entirely in the checked-out ci/
scripts on the pipeline branch.

.NOTES
Requires the GH OIDC request vars (present whenever the job has id-token: write):
ACTIONS_ID_TOKEN_REQUEST_URL / ACTIONS_ID_TOKEN_REQUEST_TOKEN.
#>
[CmdletBinding()]
param(
[string] $ClientId,
[string] $TenantId,
[string] $SubscriptionId
)
$ErrorActionPreference = 'Stop'

if (-not $env:ACTIONS_ID_TOKEN_REQUEST_URL -or -not $env:ACTIONS_ID_TOKEN_REQUEST_TOKEN) {
throw 'GH OIDC token endpoint unavailable (need permissions: id-token: write on the job).'
}

# Recover any missing identity fields from the cached az profile (survives token expiry).
if (-not ($ClientId -and $TenantId -and $SubscriptionId)) {
$acct = az account show -o json 2>$null | ConvertFrom-Json
if (-not $acct) { throw 'Cannot recover identity for OIDC re-login: no cached az profile (az account show returned nothing).' }
if (-not $ClientId) { $ClientId = $acct.user.name } # SP login: user.name = app/client id
if (-not $TenantId) { $TenantId = $acct.tenantId }
if (-not $SubscriptionId) { $SubscriptionId = $acct.id }
}
if (-not ($ClientId -and $TenantId -and $SubscriptionId)) {
throw "Incomplete identity for OIDC re-login (client='$ClientId' tenant='$TenantId' sub='$SubscriptionId')."
}

# Mint a fresh GitHub OIDC token scoped to the Azure token-exchange audience.
$uri = "$($env:ACTIONS_ID_TOKEN_REQUEST_URL)&audience=api://AzureADTokenExchange"
$jwt = (Invoke-RestMethod -Uri $uri -Method GET -Headers @{ Authorization = "Bearer $($env:ACTIONS_ID_TOKEN_REQUEST_TOKEN)" }).value
if (-not $jwt) { throw 'GH OIDC token request returned an empty token.' }

az login --service-principal -u $ClientId -t $TenantId --federated-token $jwt --output none
if ($LASTEXITCODE -ne 0) { throw "az login (federated OIDC) failed with exit $LASTEXITCODE." }
az account set --subscription $SubscriptionId --output none
if ($LASTEXITCODE -ne 0) { throw "az account set --subscription '$SubscriptionId' failed with exit $LASTEXITCODE." }

Write-Host " [az-relogin] re-authenticated to Azure via GH OIDC (subscription $SubscriptionId)"
264 changes: 264 additions & 0 deletions ci/kickoff-win-hw-wim-build.ps1

Large diffs are not rendered by default.

130 changes: 130 additions & 0 deletions ci/win-hw-wim-vm.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<#
.SYNOPSIS
Create or destroy the EPHEMERAL Azure build VM for a Windows HW WIM build. Runs on
the GitHub Actions runner (pwsh + az, already authenticated by azure/login).

.DESCRIPTION
The workflow spins this VM up per run and tears it down afterward (in an
if: always() step), so there is no idle cost and no long-lived build host.

-Action create : nested-virt VM (no public IP / no NSG — driven only via
az vm run-command), system-assigned managed identity granted Storage Blob Data
Contributor on the storage account, Premium data disk, then bootstrap Hyper-V +
tooling (provisioners/windows/win-hw-wim/scripts/bootstrap-build-host.ps1).
-Action destroy : remove the VM + its OS disk + NIC + the MI role assignment.
Idempotent and best-effort so teardown never leaves the job stuck; safe to run
even if create only partially succeeded.

The VM name is run-scoped (e.g. win-hw-wim-build-<run_id>) so parallel runs don't
collide and teardown targets exactly this run's VM.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)][ValidateSet('create', 'destroy')] [string] $Action,
[Parameter(Mandatory)] [string] $VmName,
[string] $ResourceGroup = 'rg-central-us-hardware-imaging',
[string] $VnetName = 'vn-central-us-hardware-imaging',
[string] $SubnetName = 'sn-central-us-hardware-imaging-packer',
[string] $Size = 'Standard_D64ads_v5', # AMD, 64 vCPU/256 GiB, nested-virt capable (fits DADSv5 64-core quota)
# Plain Windows Server 2025 gen2 (NOT azure-edition — azure-edition pushes Trusted
# Launch, which is incompatible with nested virtualization).
[string] $Image = 'MicrosoftWindowsServer:WindowsServer:2025-datacenter-g2:latest',
[int] $DataDiskGB = 512,
# Pre-provisioned user-assigned identity (Terraform) attached to the VM for blob
# access — so no per-run role assignment (and no role-assignment rights) is needed.
[string] $BuilderIdentityName = 'id-central-us-hardware-imaging-builder'
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
# Resolve the real az executable — PowerShell is case-insensitive, so a function
# named "Az" would otherwise shadow "az" and recurse infinitely.
$azExe = (Get-Command az -CommandType Application -ErrorAction Stop | Select-Object -First 1).Source
function Az { $o = & $azExe @args 2>&1; if ($LASTEXITCODE) { throw "az $($args -join ' ') failed:`n$o" }; return $o }
function AzTry { & $azExe @args 2>&1 | Out-Null } # best-effort (teardown)

if ($Action -eq 'create') {
$subnetId = (Az network vnet subnet show -g $ResourceGroup --vnet-name $VnetName -n $SubnetName --query id -o tsv)

# Random admin password (never used — no RDP; az just requires one). Portable:
# System.Web isn't available in pwsh 7 on Linux. Guid hex + 'Aa1!' meets Azure complexity.
$pw = 'Aa1!' + [guid]::NewGuid().ToString('N') + [guid]::NewGuid().ToString('N').Substring(0, 12)

$uamiId = (Az identity show -g $ResourceGroup -n $BuilderIdentityName --query id -o tsv)
# Windows computer name is capped at 15 chars, so derive a short one from the
# (longer) run-scoped VM resource name. It's just an ephemeral standalone host.
$computerName = ($VmName -replace '[^a-zA-Z0-9]', '')
if ($computerName.Length -gt 15) { $computerName = $computerName.Substring(0, 15) }
Write-Host "== Creating ephemeral VM $VmName ($Size, no public IP; computer $computerName; identity $BuilderIdentityName) =="
Az vm create -g $ResourceGroup -n $VmName `
--image $Image --size $Size --computer-name $computerName `
--security-type Standard `
--admin-username nucadmin --admin-password $pw `
--subnet $subnetId --public-ip-address "" --nsg "" `
--assign-identity $uamiId `
--os-disk-size-gb 128 --storage-sku Premium_LRS `
--data-disk-sizes-gb $DataDiskGB --output none

# Forward slashes so the path resolves on the Linux runner too.
$boot = "$PSScriptRoot/../provisioners/windows/win-hw-wim/scripts/bootstrap-build-host.ps1"
if (-not (Test-Path $boot)) { throw "bootstrap script not found: $boot" }
$bootBody = Get-Content -Raw $boot

# Run a bootstrap phase and ASSERT its success sentinel. `az vm run-command` returns
# exit 0 even if the inner script throws, so we can't rely on the az exit code.
# Inline the phase assignment + script content as ONE --scripts value (mixing a
# literal line with @file, or --parameters -> params, proved unreliable).
function Invoke-Phase([string]$Ph) {
$script = "`$Phase = '$Ph'`n" + $bootBody
# Capture ALL message streams (value[0]=stdout, value[1]=stderr) — a thrown
# error lands in stderr, so querying only value[0] hid the real failure.
$msg = Az vm run-command invoke -g $ResourceGroup -n $VmName --command-id RunPowerShellScript `
--scripts $script --query "join('`n', value[].message)" -o tsv
if ("$msg" -notmatch 'BOOTSTRAP_PHASE_OK') { throw "bootstrap phase '$Ph' did not succeed:`n$msg" }
Write-Host " phase $Ph OK"
}

Write-Host '== Bootstrap phase 1: Hyper-V =='
Invoke-Phase 'Hyperv'
Write-Host '== Reboot for Hyper-V =='
Az vm restart -g $ResourceGroup -n $VmName --output none
Start-Sleep -Seconds 30
Write-Host '== Bootstrap phase 2: tooling (retry for guest-agent readiness) =='
$ok = $false
for ($i = 1; $i -le 5; $i++) {
try { Invoke-Phase 'Tooling'; $ok = $true; break }
catch { Write-Warning "phase 2 attempt $i failed; retry in 30s. $_"; Start-Sleep 30 }
}
if (-not $ok) { throw 'Bootstrap phase 2 (tooling) failed after retries.' }
Write-Host "== Ephemeral build VM $VmName ready =="
}
else {
Write-Host "== Destroying ephemeral VM $VmName (best-effort) =="
# This if: always() step runs AFTER the ~2h build, by which point azure/login's ~1h
# token is dead - so `az vm delete` silently no-ops (auth error) and the VM leaks,
# holding the single-bake 64-core quota. Re-auth with a fresh GitHub OIDC token first.
# Best-effort: never let a re-login failure abort teardown.
try { & (Join-Path $PSScriptRoot 'az-relogin.ps1') }
catch { Write-Warning "teardown OIDC re-login failed (continuing best-effort): $_" }

# Capture child resource ids before deleting the VM (az vm delete doesn't cascade).
# Use $azExe directly (best-effort; the VM may not exist) — not the throwing Az wrapper.
$diskId = (& $azExe vm show -g $ResourceGroup -n $VmName --query "storageProfile.osDisk.managedDisk.id" -o tsv 2>$null)
$nicIds = (& $azExe vm show -g $ResourceGroup -n $VmName --query "networkProfile.networkInterfaces[].id" -o tsv 2>$null)
$dataDisks = (& $azExe vm show -g $ResourceGroup -n $VmName --query "storageProfile.dataDisks[].managedDisk.id" -o tsv 2>$null)
# The UAMI is persistent (Terraform-managed) and just attached — nothing to detach/remove here.

AzTry vm delete -g $ResourceGroup -n $VmName --yes
foreach ($nic in ($nicIds -split "`n" | Where-Object { $_ })) { Write-Host " deleting nic $nic"; AzTry network nic delete --ids $nic }
foreach ($d in (@($diskId) + ($dataDisks -split "`n") | Where-Object { $_ })) { Write-Host " deleting disk $d"; AzTry disk delete --ids $d --yes }

# Sweep by name prefix — catches resources az created before a FAILED 'vm create'
# (the VM never existed, so 'az vm show' above found nothing). az's default NIC is
# <VmName>VMNic; disks are <VmName>_*.
foreach ($n in ((& $azExe network nic list -g $ResourceGroup --query "[?starts_with(name,'$VmName')].name" -o tsv 2>$null) -split "`n" | Where-Object { $_ })) {
Write-Host " deleting leaked nic $n"; AzTry network nic delete -g $ResourceGroup -n $n
}
foreach ($d in ((& $azExe disk list -g $ResourceGroup --query "[?starts_with(name,'$VmName')].name" -o tsv 2>$null) -split "`n" | Where-Object { $_ })) {
Write-Host " deleting leaked disk $d"; AzTry disk delete -g $ResourceGroup -n $d --yes
}
Write-Host "== Teardown complete for $VmName =="
}
22 changes: 15 additions & 7 deletions provisioners/windows/MDC1Windows/Get-Bootstrap.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -358,22 +358,30 @@ Test-ConnectionUntilOnline
# Enable SSH and import keys
Set-SSH

# Enable WinRM (non-fatal, retry twice)
$winrmOk = Set-WinRM -Retries 2 -DelaySeconds 10
if (-not $winrmOk) {
Write-Log -message 'get-bootstrap :: WinRM setup did not succeed after retries; continuing without it.' -severity 'WARN'
}
# WinRM disabled for the pre-baked NUC deploy.
# RELOPS-2487: Set-WinRM is NOT called here - Enable-PSRemoting can't publish a listener on the
# NUC's Public/workgroup network (it failed every deploy) and nothing in the bootstrap path needs
# WinRM; SSH (baked) is the access path. The Set-WinRM function is kept for future use / if Puppet
# ever needs it. Mirrors the same skip in bootstrap.ps1.
# $winrmOk = Set-WinRM -Retries 2 -DelaySeconds 10
# if (-not $winrmOk) {
# Write-Log -message 'get-bootstrap :: WinRM setup did not succeed after retries; continuing without it.' -severity 'WARN'
# }

# Install Chocolatey
Install-Choco

# Fetch bootstrap.ps1
# TEMPORARY (RELOPS-2487 canary): pull bootstrap.ps1 from the `nuc-wim-pipeline` feature
# branch instead of `main` so we can iterate on a prebake-aware bootstrap (consume baked
# Git/openvox/puppet/ronin/registry instead of re-doing them). *** REVERT THIS URL TO
# `main` BEFORE MERGING TO main *** - production must always take bootstrap.ps1 from main.
$local_bootstrap = "C:\bootstrap\bootstrap.ps1"
if (-Not (Test-Path "D:\Secrets\pat.txt")) {
$splat = @{ Url = "https://raw.githubusercontent.com/mozilla-platform-ops/worker-images/main/provisioners/windows/MDC1Windows/bootstrap.ps1"; Path = $local_bootstrap }
$splat = @{ Url = "https://raw.githubusercontent.com/mozilla-platform-ops/worker-images/nuc-wim-pipeline/provisioners/windows/MDC1Windows/bootstrap.ps1"; Path = $local_bootstrap }
Invoke-DownloadWithRetry @splat
} else {
$splat = @{ Url = "https://raw.githubusercontent.com/mozilla-platform-ops/worker-images/main/provisioners/windows/MDC1Windows/bootstrap.ps1"; Path = $local_bootstrap; PAT = Get-Content "D:\Secrets\pat.txt" }
$splat = @{ Url = "https://raw.githubusercontent.com/mozilla-platform-ops/worker-images/nuc-wim-pipeline/provisioners/windows/MDC1Windows/bootstrap.ps1"; Path = $local_bootstrap; PAT = Get-Content "D:\Secrets\pat.txt" }
Invoke-DownloadWithRetryGithub @splat
}

Expand Down
Loading