added osdcloud script
This commit is contained in:
@@ -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=<SSID> 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 '<protected>false</protected>') { 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
|
||||||
Reference in New Issue
Block a user