From 84d454c6767cdd4061d437e4e26d39615f210ecf Mon Sep 17 00:00:00 2001 From: latte Date: Fri, 18 Sep 2026 20:27:52 +0200 Subject: [PATCH] added osdcloud script --- install-scripts/Build-OSDCloudSurface-x64.ps1 | 257 ++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 install-scripts/Build-OSDCloudSurface-x64.ps1 diff --git a/install-scripts/Build-OSDCloudSurface-x64.ps1 b/install-scripts/Build-OSDCloudSurface-x64.ps1 new file mode 100644 index 0000000..f750b69 --- /dev/null +++ b/install-scripts/Build-OSDCloudSurface-x64.ps1 @@ -0,0 +1,257 @@ +<# +.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