Files
pxe-server/Upload-Image.ps1
cproudlock 76165495ff Shopfloor PC type system, webapp enhancements, slim Blancco GRUB
- Shopfloor PC type menu (CMM, WaxAndTrace, Keyence, Genspect, Display, Standard)
- Baseline scripts: OpenText CSF, Start Menu shortcuts, network/WinRM, power/display
- Standard type: eDNC + MarkZebra with 64-bit path mirroring
- CMM type: Hexagon CLM Tools, PC-DMIS 2016/2019 R2
- Display sub-type: Lobby vs Dashboard
- Webapp: enrollment management, image config editor, UI refresh
- Upload-Image.ps1: robocopy MCL cache to PXE server
- Download-Drivers.ps1: Dell driver download pipeline
- Slim Blancco GRUB EFI (10MB -> 660KB) for old hardware compat
- Shopfloor display imaging guide docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:25:07 -04:00

380 lines
15 KiB
PowerShell

#
# Upload-Image.ps1 — Copy MCL cached image to the PXE server
#
# Reads user_selections.json to upload only the selected OS, matching
# packages, and config files. Drivers are EXCLUDED by default.
#
# Usage:
# .\Upload-Image.ps1 (selected OS + packages, no drivers)
# .\Upload-Image.ps1 -IncludeDrivers (also upload selected hardware drivers)
# .\Upload-Image.ps1 -CachePath "D:\MCL\Cache" (custom cache location)
# .\Upload-Image.ps1 -Server 10.9.100.1 (custom server IP)
#
# After upload, use the PXE webapp (http://10.9.100.1:9009) to import
# the uploaded content into the desired image type.
#
param(
[string]$CachePath = "C:\ProgramData\GEAerospace\MediaCreator\Cache",
[string]$Server = "10.9.100.1",
[string]$User = "pxe-upload",
[string]$Pass = "pxe",
[switch]$IncludeDrivers
)
$Share = "\\$Server\image-upload"
function Format-Size {
param([long]$Bytes)
if ($Bytes -ge 1GB) { return "{0:N1} GB" -f ($Bytes / 1GB) }
if ($Bytes -ge 1MB) { return "{0:N1} MB" -f ($Bytes / 1MB) }
return "{0:N0} KB" -f ($Bytes / 1KB)
}
function Resolve-DestDir {
param([string]$Dir)
return ($Dir -replace '^\*destinationdir\*\\?', '')
}
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " PXE Server Image Uploader" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
# --- Validate source paths ---
$DeployPath = Join-Path $CachePath "Deploy"
$ToolsPath = Join-Path (Split-Path $CachePath -Parent) "Tools"
if (-not (Test-Path $ToolsPath -PathType Container)) {
$ToolsPath = "C:\ProgramData\GEAerospace\MediaCreator\Tools"
}
$SourcesZip = Join-Path $CachePath "Boot\Sources.zip"
if (-not (Test-Path $DeployPath -PathType Container)) {
Write-Host "ERROR: Deploy directory not found at $DeployPath" -ForegroundColor Red
Write-Host " .\Upload-Image.ps1 -CachePath ""D:\Path\To\Cache""" -ForegroundColor Yellow
exit 1
}
# --- Parse user_selections.json ---
$SelectionsFile = Join-Path $ToolsPath "user_selections.json"
if (-not (Test-Path $SelectionsFile)) {
Write-Host "ERROR: user_selections.json not found at $SelectionsFile" -ForegroundColor Red
Write-Host " Run Media Creator Lite first to create a configuration." -ForegroundColor Yellow
exit 1
}
$selections = (Get-Content $SelectionsFile -Raw | ConvertFrom-Json)[0]
$selectedOsId = $selections.OperatingSystemSelection
$selectedModelIds = @($selections.HardwareModelSelection | ForEach-Object { $_.Id } | Select-Object -Unique)
# --- Parse control JSONs ---
$ControlPath = Join-Path $DeployPath "Control"
$osJsonFile = Join-Path $ControlPath "OperatingSystem.json"
$driverJsonFile = Join-Path $ControlPath "HardwareDriver.json"
$pkgJsonFile = Join-Path $ControlPath "packages.json"
if (-not (Test-Path $osJsonFile)) {
Write-Host "ERROR: OperatingSystem.json not found in $ControlPath" -ForegroundColor Red
exit 1
}
$osJson = Get-Content $osJsonFile -Raw | ConvertFrom-Json
$driverJson = if (Test-Path $driverJsonFile) { Get-Content $driverJsonFile -Raw | ConvertFrom-Json } else { @() }
$pkgJson = if (Test-Path $pkgJsonFile) { Get-Content $pkgJsonFile -Raw | ConvertFrom-Json } else { @() }
# --- Resolve selections to paths ---
# OS: match OperatingSystemSelection ID to OperatingSystem.json entries
$matchedOs = @($osJson | Where-Object { $_.operatingSystemVersion.id -eq [int]$selectedOsId })
$osDirs = @()
$osTotalSize = [long]0
foreach ($os in $matchedOs) {
$rel = Resolve-DestDir $os.operatingSystemVersion.wim.DestinationDir
$osDirs += $rel
$osTotalSize += $os.operatingSystemVersion.wim.size
}
# Packages: enabled + matching OS ID
$matchedPkgs = @($pkgJson | Where-Object { $_.aOsIds -contains $selectedOsId -and $_.enabled -eq 1 })
$pkgTotalSize = [long]0
foreach ($pkg in $matchedPkgs) { $pkgTotalSize += $pkg.size }
# Drivers: match selected model IDs (family) + OS ID, deduplicate by path
$allMatchingDrivers = @($driverJson | Where-Object {
$selectedModelIds -contains $_.family -and $_.aOsIds -contains $selectedOsId
})
$allDriverDirSet = [ordered]@{}
foreach ($drv in $allMatchingDrivers) {
$rel = Resolve-DestDir $drv.DestinationDir
if (-not $allDriverDirSet.Contains($rel)) { $allDriverDirSet[$rel] = $drv.size }
}
$allDriverCount = $allDriverDirSet.Count
$allDriverTotalSize = [long]0
$allDriverDirSet.Values | ForEach-Object { $allDriverTotalSize += $_ }
$driverDirs = @()
$driverTotalSize = [long]0
if ($IncludeDrivers) {
$driverDirs = @($allDriverDirSet.Keys)
$driverTotalSize = $allDriverTotalSize
}
# --- Display upload plan ---
Write-Host " Cache: $CachePath"
Write-Host " Server: $Server"
Write-Host ""
Write-Host " Upload Plan (from user_selections.json):" -ForegroundColor Cyan
Write-Host " ------------------------------------------"
if ($matchedOs.Count -gt 0) {
$osName = $matchedOs[0].operatingSystemVersion.marketingName
Write-Host " OS: $osName ($(Format-Size $osTotalSize))" -ForegroundColor Green
} else {
Write-Host " OS: No match for selection ID $selectedOsId" -ForegroundColor Red
}
Write-Host " Packages: $($matchedPkgs.Count) update(s) ($(Format-Size $pkgTotalSize))" -ForegroundColor Green
if ($IncludeDrivers) {
Write-Host " Drivers: $($driverDirs.Count) model(s) ($(Format-Size $driverTotalSize))" -ForegroundColor Green
} else {
Write-Host " Drivers: SKIPPED -- $allDriverCount available, use -IncludeDrivers" -ForegroundColor Yellow
}
Write-Host " Control: Always included" -ForegroundColor Gray
Write-Host " Tools: $(if (Test-Path $ToolsPath) { 'Yes' } else { 'Not found' })" -ForegroundColor $(if (Test-Path $ToolsPath) { "Gray" } else { "Yellow" })
Write-Host " Sources: $(if (Test-Path $SourcesZip) { 'Yes (from Boot\Sources.zip)' } else { 'Not found' })" -ForegroundColor $(if (Test-Path $SourcesZip) { "Gray" } else { "Yellow" })
Write-Host ""
# --- Connect to SMB share ---
Write-Host "Connecting to $Share ..." -ForegroundColor Gray
net use $Share /delete 2>$null | Out-Null
$netResult = net use $Share /user:$User $Pass 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: Could not connect to $Share" -ForegroundColor Red
Write-Host $netResult -ForegroundColor Red
Write-Host ""
Write-Host "Make sure:" -ForegroundColor Yellow
Write-Host " - The PXE server is running at $Server" -ForegroundColor Yellow
Write-Host " - This PC is on the 10.9.100.x network" -ForegroundColor Yellow
Write-Host " - Samba is running on the PXE server" -ForegroundColor Yellow
exit 1
}
Write-Host "Connected." -ForegroundColor Green
Write-Host ""
$failed = $false
$stepNum = 0
$totalSteps = 1 # Deploy base always
if ($matchedOs.Count -gt 0) { $totalSteps++ }
if ($matchedPkgs.Count -gt 0) { $totalSteps++ }
if ($IncludeDrivers -and $driverDirs.Count -gt 0) { $totalSteps++ }
if (Test-Path $ToolsPath -PathType Container) { $totalSteps++ }
if (Test-Path $SourcesZip) { $totalSteps++ }
# --- Step: Deploy base (Control, Applications, config -- skip big dirs) ---
$stepNum++
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "[$stepNum/$totalSteps] Copying Deploy\ base (Control, Applications, config) ..." -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
robocopy $DeployPath "$Share\Deploy" /E /XD "Operating Systems" "Out-of-box Drivers" "Packages" /R:3 /W:5 /NP /ETA
if ($LASTEXITCODE -ge 8) {
Write-Host "ERROR: Deploy base copy failed (exit code $LASTEXITCODE)" -ForegroundColor Red
$failed = $true
}
# --- Step: Operating System ---
if ($matchedOs.Count -gt 0) {
$stepNum++
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "[$stepNum/$totalSteps] Copying Operating System ..." -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
foreach ($osDir in $osDirs) {
$src = Join-Path $CachePath $osDir
$dst = Join-Path $Share $osDir
if (Test-Path $src -PathType Container) {
Write-Host " $osDir" -ForegroundColor Gray
robocopy $src $dst /E /R:3 /W:5 /NP /ETA
if ($LASTEXITCODE -ge 8) {
Write-Host "ERROR: OS copy failed (exit code $LASTEXITCODE)" -ForegroundColor Red
$failed = $true
}
} else {
Write-Host " SKIPPED (not cached): $osDir" -ForegroundColor Yellow
}
}
}
# --- Step: Packages ---
if ($matchedPkgs.Count -gt 0) {
$stepNum++
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "[$stepNum/$totalSteps] Copying Packages ..." -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
# Group packages by destination directory for efficient robocopy
$pkgGroups = [ordered]@{}
foreach ($pkg in $matchedPkgs) {
$rel = Resolve-DestDir $pkg.destinationDir
if (-not $pkgGroups.Contains($rel)) { $pkgGroups[$rel] = @() }
$pkgGroups[$rel] += $pkg.fileName
}
foreach ($dir in $pkgGroups.Keys) {
$src = Join-Path $CachePath $dir
$dst = Join-Path $Share $dir
$files = $pkgGroups[$dir]
if (Test-Path $src -PathType Container) {
foreach ($f in $files) { Write-Host " $f" -ForegroundColor Gray }
$robocopyArgs = @($src, $dst) + $files + @("/R:3", "/W:5", "/NP", "/ETA")
& robocopy @robocopyArgs
if ($LASTEXITCODE -ge 8) {
Write-Host "ERROR: Package copy failed (exit code $LASTEXITCODE)" -ForegroundColor Red
$failed = $true
}
} else {
Write-Host " SKIPPED (not cached): $dir" -ForegroundColor Yellow
}
}
}
# --- Step: Drivers (only with -IncludeDrivers) ---
if ($IncludeDrivers -and $driverDirs.Count -gt 0) {
$stepNum++
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "[$stepNum/$totalSteps] Copying Drivers ($($driverDirs.Count) model(s)) ..." -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
$drvCopied = 0
foreach ($drvDir in $driverDirs) {
$drvCopied++
$src = Join-Path $CachePath $drvDir
$dst = Join-Path $Share $drvDir
if (Test-Path $src -PathType Container) {
Write-Host " [$drvCopied/$($driverDirs.Count)] $drvDir" -ForegroundColor Gray
robocopy $src $dst /E /R:3 /W:5 /NP /ETA
if ($LASTEXITCODE -ge 8) {
Write-Host "ERROR: Driver copy failed (exit code $LASTEXITCODE)" -ForegroundColor Red
$failed = $true
}
} else {
Write-Host " SKIPPED (not cached): $drvDir" -ForegroundColor Yellow
}
}
}
# --- Step: Tools ---
if (Test-Path $ToolsPath -PathType Container) {
$stepNum++
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "[$stepNum/$totalSteps] Copying Tools\ ..." -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
robocopy $ToolsPath "$Share\Tools" /E /R:3 /W:5 /NP /ETA
if ($LASTEXITCODE -ge 8) {
Write-Host "ERROR: Tools copy failed (exit code $LASTEXITCODE)" -ForegroundColor Red
$failed = $true
}
}
# --- Step: Sources ---
$TempSources = $null
if (Test-Path $SourcesZip) {
$stepNum++
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "[$stepNum/$totalSteps] Extracting and copying Sources\ ..." -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
$TempExtract = Join-Path $env:TEMP "SourcesExtract"
Remove-Item -Recurse -Force $TempExtract -ErrorAction SilentlyContinue
Expand-Archive $SourcesZip -DestinationPath $TempExtract -Force
# Handle nested Sources folder (zip may contain Sources/ at root)
$TempSources = $TempExtract
if ((Test-Path (Join-Path $TempExtract "Sources")) -and -not (Test-Path (Join-Path $TempExtract "Diskpart"))) {
$TempSources = Join-Path $TempExtract "Sources"
}
robocopy $TempSources "$Share\Sources" /E /R:3 /W:5 /NP /ETA
if ($LASTEXITCODE -ge 8) {
Write-Host "ERROR: Sources copy failed (exit code $LASTEXITCODE)" -ForegroundColor Red
$failed = $true
}
Remove-Item -Recurse -Force $TempExtract -ErrorAction SilentlyContinue
}
# --- Verify small files (SMB write-cache workaround) ---
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Verifying small files ..." -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
$fixCount = 0
$verifyPairs = @(
@{ Local = (Join-Path $DeployPath "Control"); Remote = "$Share\Deploy\Control" }
)
if (Test-Path $ToolsPath -PathType Container) {
$verifyPairs += @{ Local = $ToolsPath; Remote = "$Share\Tools" }
}
foreach ($pair in $verifyPairs) {
$localDir = $pair.Local
$remoteDir = $pair.Remote
if (-not (Test-Path $localDir -PathType Container)) { continue }
Get-ChildItem -Path $localDir -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.Length -gt 0 -and $_.Length -lt 1MB } |
ForEach-Object {
$rel = $_.FullName.Substring($localDir.Length)
$dstFile = Join-Path $remoteDir $rel
if (Test-Path $dstFile) {
$dstSize = (Get-Item $dstFile).Length
if ($dstSize -ne $_.Length) {
Write-Host " Fixing: $rel ($dstSize -> $($_.Length) bytes)" -ForegroundColor Yellow
$bytes = [System.IO.File]::ReadAllBytes($_.FullName)
[System.IO.File]::WriteAllBytes($dstFile, $bytes)
$fixCount++
}
}
}
}
if ($fixCount -eq 0) {
Write-Host " All files verified OK." -ForegroundColor Green
} else {
Write-Host " Fixed $fixCount file(s)." -ForegroundColor Yellow
}
# --- Disconnect ---
net use $Share /delete 2>$null | Out-Null
# --- Summary ---
Write-Host ""
if ($failed) {
Write-Host "========================================" -ForegroundColor Red
Write-Host " Upload completed with errors." -ForegroundColor Red
Write-Host "========================================" -ForegroundColor Red
} else {
Write-Host "========================================" -ForegroundColor Green
Write-Host " Upload complete!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
}
Write-Host ""
Write-Host "Next steps:" -ForegroundColor Cyan
Write-Host " 1. Open the PXE webapp: http://$Server`:9009" -ForegroundColor White
Write-Host " 2. Go to Image Import" -ForegroundColor White
Write-Host " 3. Select source 'image-upload' and target image type" -ForegroundColor White
Write-Host " 4. Click Import" -ForegroundColor White
Write-Host ""