The driver check - the most valuable check in this script - has never actually run. It died on: Driver check failed: Cannot convert the JSON string because a dictionary that was converted from the string contains the duplicated keys 'FileName' and 'FileName' HardwareDriver.json carries both casings of the same fields - fileName and FileName, destinationDir and DestinationDir. ConvertFrom-Json treats object keys case-insensitively and refuses the document. -AsHashtable would handle it but that is PowerShell 6+, and WinPE runs 5.1. Confirmed it throws on PowerShell 7 too, so no version of ConvertFrom-Json can read this file as-is. Pulls the four needed fields out of each entry by regex instead, preferring the lowercase key and falling back to the capitalised one, and unescaping the backslashes in destinationDir. Tested against the real 44-entry catalogue, all three outcomes: OptiPlex Micro 7020, pack present -> OK, win11_optiplexd13mlk7020_a09.zip same model, pack removed -> FAIL, names the missing path Surface Laptop 7 -> FAIL, no pack matches Worth noting the check was failing SAFE - a WARN that reads like a tooling glitch rather than a missing driver pack. It would have stayed invisible until a bay imaged with no NIC.
171 lines
7.3 KiB
PowerShell
Executable File
171 lines
7.3 KiB
PowerShell
Executable File
# preflight.ps1 - check the things PESetup fails on, before it fails on them.
|
|
#
|
|
# Called by startnet.cmd once the media is mapped. Prints a short report a tech
|
|
# can read at the bay and exits non-zero if a check is fatal.
|
|
#
|
|
# WHY EACH CHECK IS HERE - all four come from PESetup's own behaviour
|
|
# (docs/PESETUP-INTERNALS.md, decompiled 4.0.0.17, observed on 4.0.0.20):
|
|
#
|
|
# Secure boot GatherDataSelection fails the step outright when
|
|
# SecurebootEnabled != 1. Hard failure, minutes into a build.
|
|
# Disk size MinRequiredSpaceWithoutCompression is 128849018880 (120 GB).
|
|
# Driver match GetDriverByModel returns null on a miss and PESetup logs a
|
|
# WARNING and keeps going. The bay images with no drivers, so no
|
|
# NIC, so DNS fails at first boot and enrollment cannot reach the
|
|
# CDN. The symptom appears far from the cause - this is the check
|
|
# that earns the script.
|
|
# Media age The media expires 30 days after build. PESetup shows days-left
|
|
# on a screen nobody reads.
|
|
#
|
|
# Lives on the enrollment share so it can be fixed without rebuilding boot.wim.
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$MediaDrive = 'Z:',
|
|
[int]$MinDiskGB = 120,
|
|
[int]$MediaWarnDays = 25
|
|
)
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
$fatal = 0
|
|
$warn = 0
|
|
|
|
function Ok { param($m) Write-Host (" [ OK ] " + $m) }
|
|
function Warn { param($m) Write-Host (" [WARN] " + $m); $script:warn++ }
|
|
function Fail { param($m) Write-Host (" [FAIL] " + $m); $script:fatal++ }
|
|
|
|
Write-Host ""
|
|
Write-Host "======== Pre-imaging checks ========"
|
|
|
|
# --- 1. Secure boot ------------------------------------------------------
|
|
# Confirm-SecureBootUEFI is not always present in WinPE; read the state the
|
|
# firmware exposes instead.
|
|
try {
|
|
$sb = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\State' `
|
|
-Name UEFISecureBootEnabled -ErrorAction Stop
|
|
if ($sb.UEFISecureBootEnabled -eq 1) {
|
|
Ok "Secure boot enabled"
|
|
} else {
|
|
Fail "Secure boot is OFF. PESetup will fail at GatherData. Enable it in BIOS."
|
|
}
|
|
} catch {
|
|
Warn "Could not read secure boot state - if this is a legacy/CSM boot, PESetup will fail."
|
|
}
|
|
|
|
# --- 2. Disk size --------------------------------------------------------
|
|
try {
|
|
$disk = Get-CimInstance Win32_DiskDrive -ErrorAction Stop |
|
|
Where-Object { $_.MediaType -like '*Fixed*' } |
|
|
Sort-Object Index | Select-Object -First 1
|
|
if ($disk) {
|
|
$gb = [math]::Round($disk.Size / 1GB, 1)
|
|
if ($gb -ge $MinDiskGB) {
|
|
Ok ("Disk 0 is {0} GB ({1})" -f $gb, $disk.Model)
|
|
} else {
|
|
Fail ("Disk 0 is only {0} GB; PESetup needs {1} GB. ({2})" -f $gb, $MinDiskGB, $disk.Model)
|
|
}
|
|
} else {
|
|
Fail "No fixed disk found. PESetup has nothing to image."
|
|
}
|
|
} catch {
|
|
Warn "Could not enumerate disks: $_"
|
|
}
|
|
|
|
# --- 3. Driver match for THIS model --------------------------------------
|
|
# Reimplements GetDriverByModel: family filter first (it knows only Latitude,
|
|
# OptiPlex and Precision), then a substring test of comma-separated tokens,
|
|
# first match wins. Tokens are NOT trimmed, matching the C#.
|
|
try {
|
|
$model = (Get-CimInstance Win32_ComputerSystem -ErrorAction Stop).Model
|
|
$catalogue = Join-Path $MediaDrive 'Deploy\Control\HardwareDriver.json'
|
|
if (-not (Test-Path $catalogue)) {
|
|
Warn "HardwareDriver.json not found at $catalogue - cannot check drivers."
|
|
} else {
|
|
# NOT ConvertFrom-Json. HardwareDriver.json carries both casings of the
|
|
# same fields - "fileName" and "FileName", "destinationDir" and
|
|
# "DestinationDir". Windows PowerShell 5.1 (which is what WinPE runs)
|
|
# treats object keys case-insensitively and throws:
|
|
# "cannot convert the JSON string because a dictionary that was
|
|
# converted from the string contains the duplicated keys 'FileName'
|
|
# and 'FileName'"
|
|
# -AsHashtable would handle it but that is PowerShell 6+. So pull the
|
|
# four fields we need out of each entry by text instead. Prefers the
|
|
# lowercase key, falls back to the capitalised one.
|
|
$raw = Get-Content $catalogue -Raw
|
|
$entries = @()
|
|
foreach ($chunk in ([regex]::Split($raw, '\}\s*,\s*\{'))) {
|
|
$get = {
|
|
param($names)
|
|
foreach ($n in $names) {
|
|
$m = [regex]::Match($chunk, '"' + $n + '"\s*:\s*"((?:[^"\\]|\\.)*)"')
|
|
if ($m.Success) { return $m.Groups[1].Value -replace '\\\\', '\' }
|
|
}
|
|
return ''
|
|
}
|
|
$e = [pscustomobject]@{
|
|
modelswminame = (& $get @('modelswminame','models'))
|
|
family = (& $get @('family'))
|
|
fileName = (& $get @('fileName','FileName'))
|
|
destinationDir = (& $get @('destinationDir','DestinationDir'))
|
|
}
|
|
if ($e.modelswminame) { $entries += $e }
|
|
}
|
|
if (-not $entries.Count) {
|
|
Warn "Could not extract any entries from $catalogue - driver check skipped."
|
|
}
|
|
$family = ''
|
|
if ($model.ToUpper().Contains('LATITUDE')) { $family = 'Latitude' }
|
|
if ($model.ToUpper().Contains('OPTIPLEX')) { $family = 'Optiplex' }
|
|
if ($model.ToUpper().Contains('PRECISION')) { $family = 'Precision' }
|
|
|
|
$hit = $null
|
|
foreach ($e in $entries) {
|
|
if ($family -and ($e.family -notlike "*$family*")) { continue }
|
|
foreach ($tok in ([string]$e.modelswminame).Split(',')) {
|
|
if ($tok -and $model.ToLower().Contains($tok.ToLower())) { $hit = $e; break }
|
|
}
|
|
if ($hit) { break }
|
|
}
|
|
if ($hit) {
|
|
$zip = Join-Path $MediaDrive (([string]$hit.destinationDir) -replace '\*destinationdir\*\\?','')
|
|
$zip = Join-Path $zip $hit.fileName
|
|
if (Test-Path $zip) {
|
|
Ok ("Driver pack for '{0}': {1}" -f $model, $hit.fileName)
|
|
} else {
|
|
Fail ("Driver pack for '{0}' is listed but MISSING on the media: {1}" -f $model, $hit.fileName)
|
|
}
|
|
} else {
|
|
Fail ("NO driver pack matches '{0}'. PESetup logs this as a warning only - the bay will image with NO network drivers." -f $model)
|
|
}
|
|
}
|
|
} catch {
|
|
Warn "Driver check failed: $_"
|
|
}
|
|
|
|
# --- 4. Media age --------------------------------------------------------
|
|
# Approximate: PESetup expires media 30 days after build and Media.tag is
|
|
# rewritten when the media is rebuilt, so its timestamp is the best local proxy.
|
|
try {
|
|
$tag = Join-Path $MediaDrive 'Deploy\Control\Media.tag'
|
|
if (Test-Path $tag) {
|
|
$age = [int]((Get-Date) - (Get-Item $tag).LastWriteTime).TotalDays
|
|
if ($age -ge $MediaWarnDays) {
|
|
Warn ("Media is about {0} days old; it expires at 30. Rebuild it soon." -f $age)
|
|
} else {
|
|
Ok ("Media is about {0} days old" -f $age)
|
|
}
|
|
}
|
|
} catch { }
|
|
|
|
Write-Host "===================================="
|
|
if ($fatal -gt 0) {
|
|
Write-Host ""
|
|
Write-Host " $fatal BLOCKING problem(s) found. Imaging this bay will not work."
|
|
Write-Host ""
|
|
exit 1
|
|
}
|
|
if ($warn -gt 0) { Write-Host " $warn warning(s), no blockers." }
|
|
else { Write-Host " All checks passed." }
|
|
Write-Host ""
|
|
exit 0
|