Private/New-Win32ToolkitGoldenVhdx.ps1

function New-Win32ToolkitGoldenVhdx {
    <#
    .SYNOPSIS
        Turns a Windows 11 ISO into a bootable Generation-2 (UEFI) VHDX with a seeded unattend.xml.
    .DESCRIPTION
        HOST-ONLY (in-box DISM + Storage + Hyper-V + bcdboot). Mounts the ISO, detects install.wim/esd,
        creates + partitions the VHDX, applies the chosen edition, makes it UEFI-bootable with bcdboot,
        and seeds the answer file into \Windows\Panther\unattend.xml WHILE the VHDX is still mounted (the
        verified ordering). All mounts are released in a finally block so the build is safe to re-run.
        See knowledge-base/designs/hyperv-golden-image-build.md (§2.2–2.3).
    .PARAMETER IsoPath
        Path to a Windows 11 x64 ISO (local, or one the operator fetched from the Evaluation Center).
    .PARAMETER VhdxPath
        Output VHDX path (overwritten only with -Force).
    .PARAMETER AdminCredential
        Guest local-admin account baked into the unattend (created + auto-logon).
    .PARAMETER ImageIndex
        Explicit edition index; omit to prefer an 'Enterprise' edition, else the first image.
    .PARAMETER SizeBytes
        VHDX size (dynamic). Default 64 GB.
    .PARAMETER ComputerName / Locale
        Passed to the unattend generator.
    .PARAMETER Force
        Overwrite an existing VHDX at VhdxPath.
    .OUTPUTS
        [string] the VhdxPath.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$IsoPath,
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$VhdxPath,
        [Parameter(Mandatory)] [ValidateNotNull()]        [pscredential]$AdminCredential,
        [int]$ImageIndex,
        [string]$Edition,
        [uint64]$SizeBytes = 64GB,
        [string]$ComputerName = 'GOLDENBASE',
        [string]$Locale = 'en-US',
        [switch]$Force
    )

    if (-not (Test-Path -LiteralPath $IsoPath)) { throw "ISO not found: $IsoPath" }
    if (Test-Path -LiteralPath $VhdxPath) {
        if (-not $Force) { throw "VHDX already exists: $VhdxPath (use -Force to rebuild)." }
        Dismount-VHD -Path $VhdxPath -ErrorAction SilentlyContinue
        Remove-Item -LiteralPath $VhdxPath -Force
    }
    $vhdxDir = Split-Path -Parent $VhdxPath
    if ($vhdxDir -and -not (Test-Path -LiteralPath $vhdxDir)) { New-Item -ItemType Directory -Path $vhdxDir -Force | Out-Null }

    $isoMount = $null
    try {
        # Robust mount: stages a local copy when the ISO is a OneDrive placeholder (or the in-place mount
        # times out — "The semaphore timeout period has expired") and retries transient failures.
        $isoMount = Mount-Win32ToolkitIso -IsoPath $IsoPath
        $isoDrive = $isoMount.DriveLetter
        if (-not $isoDrive) { throw 'Could not resolve the mounted ISO drive letter.' }

        $imgArgs = @{ SourcesPath = "${isoDrive}:\sources" }
        if ($PSBoundParameters.ContainsKey('ImageIndex') -and $ImageIndex) { $imgArgs['ImageIndex'] = $ImageIndex }
        elseif ($Edition) { $imgArgs['EditionPreference'] = @($Edition) }
        $image = Get-Win32ToolkitInstallImage @imgArgs
        Write-Verbose "Applying image [$($image.Index)] $($image.ImageName) ($($image.Format))..."

        New-VHD -Path $VhdxPath -SizeBytes $SizeBytes -Dynamic -BlockSizeBytes 32MB -ErrorAction Stop | Out-Null
        $layout = Initialize-Win32ToolkitVhdxLayout -VhdxPath $VhdxPath

        Expand-WindowsImage -ImagePath $image.ImagePath -Index $image.Index -ApplyPath "$($layout.WindowsDrive)\" -ErrorAction Stop | Out-Null

        # Use the HOST's bcdboot.exe, NOT the applied image's copy: a newer/different guest build (e.g.
        # 25H2 media on an older host) has a bcdboot.exe/bcrypt.dll that fails to load on this host with
        # a "Bad Image 0xc0e90002" error. The host bcdboot still copies the boot files FROM the guest
        # $winDir onto the ESP (host + guest are the same architecture).
        $winDir  = "$($layout.WindowsDrive)\Windows"
        $bcdboot = Join-Path $env:SystemRoot 'System32\bcdboot.exe'
        & $bcdboot $winDir /s $layout.EspDrive /f UEFI
        if ($LASTEXITCODE -ne 0) { throw "bcdboot failed (exit $LASTEXITCODE) — see the console output above." }

        # Seed the answer file BEFORE dismount (the verified ordering — W: must still be mounted).
        $panther = Join-Path $winDir 'Panther'
        New-Item -ItemType Directory -Force $panther | Out-Null
        $unattend = New-Win32ToolkitUnattendXml -AdminCredential $AdminCredential -ComputerName $ComputerName -Locale $Locale
        Set-Content -LiteralPath (Join-Path $panther 'unattend.xml') -Value $unattend -Encoding UTF8

        Write-Host "✓ Golden VHDX built: $VhdxPath" -ForegroundColor Green
    }
    finally {
        Dismount-VHD -Path $VhdxPath -ErrorAction SilentlyContinue
        if ($isoMount) {
            Dismount-DiskImage -ImagePath $isoMount.ImagePath -ErrorAction SilentlyContinue
            if ($isoMount.StagedPath) { Remove-Item -LiteralPath $isoMount.StagedPath -Force -ErrorAction SilentlyContinue }
        }
        else {
            Dismount-DiskImage -ImagePath $IsoPath -ErrorAction SilentlyContinue
        }
    }

    return $VhdxPath
}