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>
This commit is contained in:
305
Upload-Image.ps1
305
Upload-Image.ps1
@@ -1,13 +1,14 @@
|
||||
#
|
||||
# Upload-Image.ps1 — Copy Media Creator Lite cached image to the PXE server
|
||||
# Upload-Image.ps1 — Copy MCL cached image to the PXE server
|
||||
#
|
||||
# Copies Deploy/, Tools/, and Sources (from Boot/Sources.zip) to the
|
||||
# PXE server's image-upload share using robocopy with authentication.
|
||||
# Reads user_selections.json to upload only the selected OS, matching
|
||||
# packages, and config files. Drivers are EXCLUDED by default.
|
||||
#
|
||||
# Usage:
|
||||
# .\Upload-Image.ps1 (uses default MCL cache path)
|
||||
# .\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)
|
||||
# .\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.
|
||||
@@ -18,11 +19,23 @@ param(
|
||||
[string]$Server = "10.9.100.1",
|
||||
[string]$User = "pxe-upload",
|
||||
[string]$Pass = "pxe",
|
||||
[switch]$IncludeDell10
|
||||
[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
|
||||
@@ -32,11 +45,6 @@ Write-Host ""
|
||||
# --- Validate source paths ---
|
||||
$DeployPath = Join-Path $CachePath "Deploy"
|
||||
$ToolsPath = Join-Path (Split-Path $CachePath -Parent) "Tools"
|
||||
# Tools is a sibling of Cache in the MCL directory structure
|
||||
if (-not (Test-Path $ToolsPath -PathType Container)) {
|
||||
# Fallback: try Tools inside CachePath parent's parent
|
||||
$ToolsPath = Join-Path (Split-Path (Split-Path $CachePath -Parent) -Parent) "Tools"
|
||||
}
|
||||
if (-not (Test-Path $ToolsPath -PathType Container)) {
|
||||
$ToolsPath = "C:\ProgramData\GEAerospace\MediaCreator\Tools"
|
||||
}
|
||||
@@ -44,24 +52,104 @@ $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 " Provide the correct cache path: .\Upload-Image.ps1 -CachePath ""D:\Path\To\Cache""" -ForegroundColor Yellow
|
||||
Write-Host " .\Upload-Image.ps1 -CachePath ""D:\Path\To\Cache""" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host " Cache Path: $CachePath"
|
||||
Write-Host " Deploy: $DeployPath" -ForegroundColor $(if (Test-Path $DeployPath) { "Green" } else { "Red" })
|
||||
Write-Host " Tools: $ToolsPath" -ForegroundColor $(if (Test-Path $ToolsPath) { "Green" } else { "Yellow" })
|
||||
Write-Host " Sources.zip: $SourcesZip" -ForegroundColor $(if (Test-Path $SourcesZip) { "Green" } else { "Yellow" })
|
||||
Write-Host " Server: $Server"
|
||||
if (-not $IncludeDell10) {
|
||||
Write-Host " Excluding: Dell_10 drivers (use -IncludeDell10 to include)" -ForegroundColor Gray
|
||||
# --- 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
|
||||
|
||||
# Remove any stale connection
|
||||
net use $Share /delete 2>$null | Out-Null
|
||||
|
||||
$netResult = net use $Share /user:$User $Pass 2>&1
|
||||
@@ -79,47 +167,135 @@ 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 1: Copy Deploy/ ---
|
||||
# --- Step: Deploy base (Control, Applications, config -- skip big dirs) ---
|
||||
$stepNum++
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "[1/3] Copying Deploy/ ..." -ForegroundColor Cyan
|
||||
Write-Host "[$stepNum/$totalSteps] Copying Deploy\ base (Control, Applications, config) ..." -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
|
||||
$robocopyArgs = @($DeployPath, "$Share\Deploy", "/E", "/R:3", "/W:5", "/NP", "/ETA")
|
||||
if (-not $IncludeDell10) {
|
||||
$robocopyArgs += @("/XD", "Dell_10")
|
||||
}
|
||||
& robocopy @robocopyArgs
|
||||
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 copy failed (exit code $LASTEXITCODE)" -ForegroundColor Red
|
||||
Write-Host "ERROR: Deploy base copy failed (exit code $LASTEXITCODE)" -ForegroundColor Red
|
||||
$failed = $true
|
||||
}
|
||||
|
||||
# --- Step 2: Copy Tools/ ---
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "[2/3] Copying Tools/ ..." -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
# --- 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
|
||||
}
|
||||
} else {
|
||||
Write-Host "SKIPPED: Tools directory not found at $ToolsPath" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# --- Step 3: Extract and copy Sources/ ---
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "[3/3] Extracting and copying Sources/ ..." -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
|
||||
# --- 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"
|
||||
Write-Host " Extracting Sources.zip..."
|
||||
Remove-Item -Recurse -Force $TempExtract -ErrorAction SilentlyContinue
|
||||
Expand-Archive $SourcesZip -DestinationPath $TempExtract -Force
|
||||
|
||||
@@ -135,10 +311,49 @@ if (Test-Path $SourcesZip) {
|
||||
$failed = $true
|
||||
}
|
||||
|
||||
# Clean up temp extraction
|
||||
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 "SKIPPED: Sources.zip not found at $SourcesZip" -ForegroundColor Yellow
|
||||
Write-Host " Fixed $fixCount file(s)." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# --- Disconnect ---
|
||||
|
||||
Reference in New Issue
Block a user