<# .SYNOPSIS Builds (and maintains) an OSDCloud x64 deployment USB for Surface devices on a WinRE base. .DESCRIPTION Run as Administrator on an x64 Windows 11 machine, preferably an Intel Surface: New-OSDCloudTemplate -WinRE uses the WinRE of the machine it runs on, and the WinRE of a Surface already contains Microsoft's own Surface drivers. The script, in order: 1. Checks prerequisites (x64, admin, ADK + WinPE add-on, OSD module, WinRE enabled) 2. Creates the WinRE template once (skipped if it already exists) 3. Creates the workspace once (skipped if it already exists) 4. Runs Edit-OSDCloudWinPE with all driver folders, the Wi-Fi profile, WirelessConnect and a fixed Start-OSDCloud start line (edition, language, activation) 5. Verifies boot.wim (WLAN stack present, no VHF HidMini, key drivers present) 6. Updates the USB stick if an OSDCloudUSB volume is present 7. Optionally downloads the Windows image to the stick (-UpdateOS) .PARAMETER DriverRoot Folder with one subfolder per model containing .inf drivers for WinPE. All subfolders are included. Example: C:\Drivers\IntelWiFi24, C:\Drivers\SL5-WinPE, C:\Drivers\SL6-WinPE. .PARAMETER WifiProfile Path to the exported Wi-Fi profile (netsh wlan export profile name= key=clear). .PARAMETER ZTI Adds -ZTI to Start-OSDCloud: no confirmation, the disk is wiped immediately. .PARAMETER UpdateOS Download/refresh the Windows image on the stick (Update-OSDCloudUSB -OSName ...). .PARAMETER SkipArchCheck Allow running on an ARM64 host, only when the x64 template already exists. Editing an existing x64 wim works on any architecture; only creating the WinRE template does not. .PARAMETER IncludePnPDump Adds a StartNet script to the wim that logs PnP device status to the stick on every boot (OSDCloud\Logs\PnP-*.txt). Useful for troubleshooting, costs a few seconds of boot time. .EXAMPLE # First run, on an Intel Surface: .\Build-OSDCloudSurface-x64.ps1 -UpdateOS .EXAMPLE # Monthly maintenance (new OSD module, refreshed image), on any machine: Update-Module OSD -Force .\Build-OSDCloudSurface-x64.ps1 -UpdateOS -SkipArchCheck .NOTES Deliberately NOT used: -CloudDriver Surface. That pack contains surface_hid_mini.inf 0.9.60.0, the VHF variant of the Surface HidMini driver, which does not start in WinPE and blocks the keyboard on the Surface Laptop series. Surface drivers come from the MSI folders in DriverRoot. Per-model WinPE driver folder lists: https://learn.microsoft.com/surface/enable-surface-keyboard-for-windows-pe-deployment #> [CmdletBinding()] param ( [string] $TemplateName = 'SurfaceX64', [string] $WorkspacePath = 'C:\OSDCloud\SurfaceX64', [string] $DriverRoot = 'C:\Drivers', [string] $WifiProfile = 'C:\Wifi\WiFiProfile.xml', [string] $OSEdition = 'Enterprise', [string] $OSLanguage = 'en-us', [string] $OSActivation = 'Volume', [switch] $ZTI, [switch] $UpdateOS, [switch] $IncludePnPDump, [switch] $SkipArchCheck ) $ErrorActionPreference = 'Stop' function Step($t) { Write-Host "`n=== $t ===" -ForegroundColor Cyan } function Ok($t) { Write-Host " [OK] $t" -ForegroundColor Green } function Warn($t) { Write-Host " [!!] $t" -ForegroundColor Yellow } function Fail($t) { Write-Host " [XX] $t" -ForegroundColor Red; exit 1 } # ------------------------------------------------------------------ Step '1. Prerequisites' # ------------------------------------------------------------------ if ($env:PROCESSOR_ARCHITECTURE -ne 'AMD64') { if ($SkipArchCheck) { Warn "$($env:PROCESSOR_ARCHITECTURE) host: can only edit an existing x64 template, cannot create a new -WinRE template." } else { Fail "This is a $($env:PROCESSOR_ARCHITECTURE) machine. The WinRE template must be created once on x64 Windows. If the template already exists, use -SkipArchCheck." } } else { Ok 'x64 host' } $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('Administrators') if (-not $isAdmin) { Fail 'Run PowerShell as Administrator.' } Ok 'Administrator' $TemplatePath = "$env:ProgramData\OSDCloud\Templates\$TemplateName" $TemplateExists = Test-Path "$TemplatePath\Media\sources\boot.wim" if (-not $TemplateExists) { if ($env:PROCESSOR_ARCHITECTURE -ne 'AMD64') { Fail 'No existing x64 template and this is not an x64 host. Create the template on x64 Windows first.' } $re = reagentc /info 2>&1 | Out-String if ($re -notmatch 'Enabled') { Fail 'WinRE is not Enabled on this machine (reagentc /info). A -WinRE template cannot be created.' } Ok 'WinRE enabled on this machine' } $adk = "${env:ProgramFiles(x86)}\Windows Kits\10\Assessment and Deployment Kit" if (-not (Test-Path "$adk\Deployment Tools")) { Warn 'ADK not found, installing via winget...' winget install Microsoft.WindowsADK --silent --accept-package-agreements --accept-source-agreements | Out-Null } if (-not (Test-Path "$adk\Windows Preinstallation Environment\amd64")) { Warn 'WinPE add-on not found, installing via winget...' winget install Microsoft.ADKPEAddon --silent --accept-package-agreements --accept-source-agreements | Out-Null } if (-not (Test-Path "$adk\Windows Preinstallation Environment\amd64")) { Fail 'ADK / WinPE add-on still missing.' } Ok 'ADK + WinPE add-on' if (-not (Get-Module OSD -ListAvailable)) { Warn 'OSD module not found, installing...' Install-Module OSD -Force -Scope AllUsers } Import-Module OSD -Force Ok "OSD module $((Get-Module OSD).Version)" if (-not (Test-Path $WifiProfile)) { Fail "Wi-Fi profile not found: $WifiProfile" } $wifiXml = Get-Content $WifiProfile -Raw if ($wifiXml -notmatch 'false') { Fail 'Wi-Fi profile was exported encrypted. Re-export as Administrator with key=clear.' } Ok "Wi-Fi profile: $WifiProfile" $DriverPaths = @(Get-ChildItem $DriverRoot -Directory -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName) if ($DriverPaths.Count -eq 0) { Fail "No driver folders found under $DriverRoot" } foreach ($p in $DriverPaths) { $n = (Get-ChildItem $p -Recurse -Filter *.inf).Count if ($n -eq 0) { Warn "$p contains no .inf files" } else { Ok "$p ($n inf)" } } # Start-OSDCloud OSName: pick the 25H2 x64 name this OSD version knows $valid = (Get-Command Start-OSDCloud).Parameters['OSName'].Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] } | Select-Object -ExpandProperty ValidValues $OSName = $valid | Where-Object { $_ -match '^Windows 11 25H2' -and $_ -notmatch 'ARM' } | Select-Object -First 1 if (-not $OSName) { Fail "No 'Windows 11 25H2' entry in the Start-OSDCloud OSName list. Update the OSD module." } Ok "OSName for Start-OSDCloud: '$OSName'" # ------------------------------------------------------------------ Step "2. Template '$TemplateName' (WinRE)" # ------------------------------------------------------------------ if ($TemplateExists) { Ok 'Template already exists, skipping' } else { Write-Host ' This takes 10 to 20 minutes...' New-OSDCloudTemplate -Name $TemplateName -WinRE } Set-OSDCloudTemplate -Name $TemplateName | Out-Null # ------------------------------------------------------------------ Step "3. Workspace $WorkspacePath" # ------------------------------------------------------------------ if (Test-Path "$WorkspacePath\Media\sources\boot.wim") { Ok 'Workspace already exists, skipping' } else { New-OSDCloudWorkspace -WorkspacePath $WorkspacePath } Set-OSDCloudWorkspace -WorkspacePath $WorkspacePath | Out-Null # Optional: PnP dump script in Config\Scripts\StartNet (ends up in the wim and on the stick) $startNet = "$WorkspacePath\Config\Scripts\StartNet" $dump = "$startNet\PnPDump.ps1" if ($IncludePnPDump) { New-Item $startNet -ItemType Directory -Force | Out-Null @' $usb = (Get-Volume | Where-Object FileSystemLabel -eq 'OSDCloudUSB').DriveLetter if (-not $usb) { return } $log = "${usb}:\OSDCloud\Logs\PnP-$(Get-Date -Format yyyyMMdd-HHmmss).txt" New-Item (Split-Path $log) -ItemType Directory -Force | Out-Null "=== DEVICES WITH PROBLEMS ===" | Set-Content $log Get-CimInstance Win32_PnPEntity | Where-Object Status -ne 'OK' | Select-Object Status, ConfigManagerErrorCode, Name, DeviceID | Format-Table -AutoSize | Out-String -Width 250 | Add-Content $log "=== NET ADAPTERS ===" | Add-Content $log Get-NetAdapter | Format-List Name, InterfaceDescription, Status, MacAddress, DriverVersion | Out-String | Add-Content $log "=== WLAN ===" | Add-Content $log netsh wlan show drivers 2>&1 | Add-Content $log netsh wlan show interfaces 2>&1 | Add-Content $log '@ | Set-Content $dump -Encoding UTF8 Ok 'PnPDump.ps1 added to StartNet' } elseif (Test-Path $dump) { Remove-Item $dump -Force Ok 'PnPDump.ps1 removed from StartNet' } # ------------------------------------------------------------------ Step '4. Edit-OSDCloudWinPE' # ------------------------------------------------------------------ $startArgs = "-OSName `"$OSName`" -OSEdition $OSEdition -OSLanguage $OSLanguage -OSActivation $OSActivation" if ($ZTI) { $startArgs += ' -ZTI'; Warn 'ZTI enabled: no confirmation, disk is wiped immediately' } Write-Host " Start-OSDCloud $startArgs" Edit-OSDCloudWinPE ` -DriverPath $DriverPaths ` -CloudDriver USB ` -WifiProfile $WifiProfile ` -WirelessConnect ` -StartOSDCloud $startArgs # ------------------------------------------------------------------ Step '5. Verify boot.wim' # ------------------------------------------------------------------ $Wim = "$WorkspacePath\Media\sources\boot.wim" $Mnt = "$env:TEMP\OSDCloudCheck" New-Item $Mnt -ItemType Directory -Force | Out-Null try { Mount-WindowsImage -ImagePath $Wim -Index 1 -Path $Mnt -ReadOnly | Out-Null if (Test-Path "$Mnt\Windows\System32\wlanapi.dll") { Ok 'WLAN stack present (WinRE base)' } else { Warn 'NO wlanapi.dll: this is not a WinRE base, Wi-Fi will not work' } $drv = Get-WindowsDriver -Path $Mnt if ($drv | Where-Object OriginalFileName -match 'surface_hid_mini') { Warn 'surface_hid_mini.inf (VHF variant) is in the wim. Remove it or the Surface keyboard will not work.' } else { Ok 'No VHF HidMini in the wim' } foreach ($k in 'surfacehidminidriver','surfaceserialhub','surfaceintegration','netwtw08','ialpss2_uart2') { $hit = $drv | Where-Object OriginalFileName -match $k | Select-Object -First 1 if ($hit) { Ok "$k ($($hit.Version))" } else { Warn "$k not found" } } if (Test-Path "$Mnt\OSDCloud\Config\Scripts\WiFiProfile.xml") { Ok 'WiFiProfile.xml in the wim' } else { Warn 'WiFiProfile.xml missing' } if (Test-Path "$Mnt\Windows\System32\WirelessConnect.exe") { Ok 'WirelessConnect.exe in the wim' } else { Warn 'WirelessConnect.exe missing' } $sn = Get-Content "$Mnt\Windows\System32\startnet.cmd" -Raw if ($sn -match 'Start-OSDCloud ') { Ok 'startnet.cmd launches Start-OSDCloud with fixed parameters' } else { Warn 'startnet.cmd has no Start-OSDCloud line' } } finally { Dismount-WindowsImage -Path $Mnt -Discard | Out-Null } Write-Host (" boot.wim: {0:N0} MB" -f ((Get-Item $Wim).Length / 1MB)) # ------------------------------------------------------------------ Step '6. USB stick' # ------------------------------------------------------------------ $usb = Get-Volume | Where-Object FileSystemLabel -eq 'OSDCloudUSB' if ($usb) { Update-OSDCloudUSB Ok "Stick updated ($($usb.DriveLetter):)" if ($UpdateOS) { Step '7. Windows image on the stick' $shortName = ($OSName -replace ' x64','').Trim() Update-OSDCloudUSB -OSName $shortName -OSLanguage $OSLanguage -OSActivation $OSActivation # remove old ESDs, keep the newest Get-ChildItem "$($usb.DriveLetter):\OSDCloud\OS" -Recurse -Filter *.esd -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -Skip 1 | Remove-Item -Force Ok 'Image updated, old ESD files removed' } Write-Host (" Free on stick: {0:N1} GB" -f ((Get-Volume -DriveLetter $usb.DriveLetter).SizeRemaining / 1GB)) } else { Warn 'No stick with label OSDCloudUSB found. First time: run New-OSDCloudUSB, then run this script again.' } Write-Host "`nDone. Test on an Intel Surface without a cable: keyboard, Wi-Fi, local image, confirmation prompt." -ForegroundColor Green