diff --git a/Download-Drivers.ps1 b/Download-Drivers.ps1 new file mode 100644 index 0000000..11a8c34 --- /dev/null +++ b/Download-Drivers.ps1 @@ -0,0 +1,230 @@ +# +# Download-Drivers.ps1 — Download selected hardware drivers from GE CDN +# +# Reads user_selections.json and HardwareDriver.json from the MCL cache +# to download only the driver packs for your selected hardware models. +# Bypasses Media Creator Lite's unreliable download mechanism. +# +# Downloads go into the MCL cache structure so Upload-Image.ps1 can +# upload them with -IncludeDrivers. +# +# Usage: +# .\Download-Drivers.ps1 (download all selected models) +# .\Download-Drivers.ps1 -ListOnly (show what would be downloaded) +# .\Download-Drivers.ps1 -CachePath "D:\MCL\Cache" (custom cache location) +# .\Download-Drivers.ps1 -Force (re-download even if already cached) +# +# Requires internet access. Run on the workstation, not the PXE server. +# + +param( + [string]$CachePath = "C:\ProgramData\GEAerospace\MediaCreator\Cache", + [switch]$ListOnly, + [switch]$Force +) + +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 Driver Downloader" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" + +# --- Validate paths --- +$DeployPath = Join-Path $CachePath "Deploy" +$ControlPath = Join-Path $DeployPath "Control" +$ToolsPath = Join-Path (Split-Path $CachePath -Parent) "Tools" +if (-not (Test-Path $ToolsPath -PathType Container)) { + $ToolsPath = "C:\ProgramData\GEAerospace\MediaCreator\Tools" +} + +if (-not (Test-Path $ControlPath -PathType Container)) { + Write-Host "ERROR: Deploy\Control not found at $ControlPath" -ForegroundColor Red + Write-Host " Run Media Creator Lite first to cache the base content." -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 + exit 1 +} + +$selections = (Get-Content $SelectionsFile -Raw | ConvertFrom-Json)[0] +$selectedOsId = $selections.OperatingSystemSelection +$selectedModelIds = @($selections.HardwareModelSelection | ForEach-Object { $_.Id } | Select-Object -Unique) + +# --- Parse HardwareDriver.json --- +$driverJsonFile = Join-Path $ControlPath "HardwareDriver.json" +if (-not (Test-Path $driverJsonFile)) { + Write-Host "ERROR: HardwareDriver.json not found in $ControlPath" -ForegroundColor Red + exit 1 +} + +$driverJson = Get-Content $driverJsonFile -Raw | ConvertFrom-Json + +# --- Match drivers to selections --- +$matchedDrivers = @($driverJson | Where-Object { + $selectedModelIds -contains $_.family -and $_.aOsIds -contains $selectedOsId +}) + +# Deduplicate by DestinationDir (some models share a driver pack) +$uniqueDrivers = [ordered]@{} +foreach ($drv in $matchedDrivers) { + $rel = Resolve-DestDir $drv.DestinationDir + if (-not $uniqueDrivers.Contains($rel)) { + $uniqueDrivers[$rel] = $drv + } +} + +$totalSize = [long]0 +$uniqueDrivers.Values | ForEach-Object { $totalSize += $_.size } + +# --- Display plan --- +Write-Host " Cache: $CachePath" +Write-Host " OS ID: $selectedOsId" +Write-Host " Models: $($selectedModelIds.Count) selected" +Write-Host " Drivers: $($uniqueDrivers.Count) unique pack(s) ($(Format-Size $totalSize))" -ForegroundColor Cyan +Write-Host "" + +if ($uniqueDrivers.Count -eq 0) { + Write-Host "No drivers match your selections." -ForegroundColor Yellow + exit 0 +} + +# Show each driver pack +$idx = 0 +foreach ($rel in $uniqueDrivers.Keys) { + $idx++ + $drv = $uniqueDrivers[$rel] + $localDir = Join-Path $CachePath $rel + $cached = Test-Path $localDir -PathType Container + $status = if ($cached -and -not $Force) { "[CACHED]" } else { "[DOWNLOAD]" } + $color = if ($cached -and -not $Force) { "Green" } else { "Yellow" } + + Write-Host (" {0,3}. {1,-12} {2} ({3})" -f $idx, $status, $drv.modelsfriendlyname, (Format-Size $drv.size)) -ForegroundColor $color + Write-Host " $($drv.FileName)" -ForegroundColor Gray +} +Write-Host "" + +if ($ListOnly) { + Write-Host " (list only — run without -ListOnly to download)" -ForegroundColor Gray + exit 0 +} + +# --- Download and extract --- +$downloadDir = Join-Path $env:TEMP "PXE-DriverDownloads" +if (-not (Test-Path $downloadDir)) { New-Item -ItemType Directory -Path $downloadDir -Force | Out-Null } + +$completed = 0 +$skipped = 0 +$errors = 0 + +foreach ($rel in $uniqueDrivers.Keys) { + $drv = $uniqueDrivers[$rel] + $localDir = Join-Path $CachePath $rel + $zipFile = Join-Path $downloadDir $drv.FileName + + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "[$($completed + $skipped + $errors + 1)/$($uniqueDrivers.Count)] $($drv.modelsfriendlyname)" -ForegroundColor Cyan + Write-Host "========================================" -ForegroundColor Cyan + + # Skip if already cached (unless -Force) + if ((Test-Path $localDir -PathType Container) -and -not $Force) { + Write-Host " Already cached at $rel" -ForegroundColor Green + $skipped++ + Write-Host "" + continue + } + + # Download + Write-Host " Downloading $(Format-Size $drv.size) ..." -ForegroundColor Gray + Write-Host " URL: $($drv.url)" -ForegroundColor DarkGray + + try { + # Use curl.exe for progress display on large files + if (Get-Command curl.exe -ErrorAction SilentlyContinue) { + & curl.exe -L -o $zipFile $drv.url --progress-bar --fail + if ($LASTEXITCODE -ne 0) { throw "curl failed with exit code $LASTEXITCODE" } + } else { + # Fallback: WebClient (streams to disk, no buffering) + $wc = New-Object System.Net.WebClient + $wc.DownloadFile($drv.url, $zipFile) + } + } catch { + Write-Host " ERROR: Download failed - $_" -ForegroundColor Red + $errors++ + Write-Host "" + continue + } + + # Verify SHA256 hash + Write-Host " Verifying SHA256 hash ..." -ForegroundColor Gray + $actualHash = (Get-FileHash -Path $zipFile -Algorithm SHA256).Hash + if ($actualHash -ne $drv.hash) { + Write-Host " ERROR: Hash mismatch!" -ForegroundColor Red + Write-Host " Expected: $($drv.hash)" -ForegroundColor Red + Write-Host " Got: $actualHash" -ForegroundColor Red + Remove-Item -Path $zipFile -Force -ErrorAction SilentlyContinue + $errors++ + Write-Host "" + continue + } + Write-Host " Hash OK." -ForegroundColor Green + + # Extract to cache destination + Write-Host " Extracting to $rel ..." -ForegroundColor Gray + if (Test-Path $localDir) { Remove-Item -Recurse -Force $localDir -ErrorAction SilentlyContinue } + New-Item -ItemType Directory -Path $localDir -Force | Out-Null + + try { + Expand-Archive -Path $zipFile -DestinationPath $localDir -Force + } catch { + Write-Host " ERROR: Extraction failed - $_" -ForegroundColor Red + $errors++ + Write-Host "" + continue + } + + # Clean up zip + Remove-Item -Path $zipFile -Force -ErrorAction SilentlyContinue + + Write-Host " Done." -ForegroundColor Green + $completed++ + Write-Host "" +} + +# --- Summary --- +Write-Host "========================================" -ForegroundColor Cyan +Write-Host " Download Summary" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host " Downloaded: $completed" -ForegroundColor Green +if ($skipped -gt 0) { Write-Host " Skipped: $skipped (already cached)" -ForegroundColor Gray } +if ($errors -gt 0) { Write-Host " Failed: $errors" -ForegroundColor Red } +Write-Host "" + +if ($completed -gt 0 -or $skipped -gt 0) { + Write-Host "Driver packs are in the MCL cache at:" -ForegroundColor Cyan + Write-Host " $DeployPath\Out-of-box Drivers\" -ForegroundColor White + Write-Host "" + Write-Host "To upload to the PXE server:" -ForegroundColor Cyan + Write-Host " .\Upload-Image.ps1 -IncludeDrivers" -ForegroundColor White + Write-Host "" +} + +# Clean up temp dir if empty +if ((Get-ChildItem $downloadDir -Force -ErrorAction SilentlyContinue | Measure-Object).Count -eq 0) { + Remove-Item $downloadDir -Force -ErrorAction SilentlyContinue +} diff --git a/Upload-Image.ps1 b/Upload-Image.ps1 index bead22f..d2e0011 100644 --- a/Upload-Image.ps1 +++ b/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 --- diff --git a/docs/Shopfloor_Display_MicroPC_Imaging_Guide.html b/docs/Shopfloor_Display_MicroPC_Imaging_Guide.html new file mode 100644 index 0000000..045f546 --- /dev/null +++ b/docs/Shopfloor_Display_MicroPC_Imaging_Guide.html @@ -0,0 +1,422 @@ + + + + + + Shopfloor Display MicroPC Imaging Guide - GE Aerospace Knowledge Base + + + +
+ +
+
+ Knowledge Base Article +

Shopfloor Display MicroPC Imaging Guide

+

PXE-based imaging procedure for Dell Pro Micro shopfloor dashboard displays

+
+ +
+ + +
+ + + + + +
+

Overview

+

This guide covers the complete process for imaging a Dell Pro Micro MicroPC as a Shopfloor Dashboard Display using the PXE boot server.

+ +

Prerequisites:

+
    +
  • Dell Pro Micro MicroPC (e.g. QCM1250)
  • +
  • USB mouse and keyboard
  • +
  • Ethernet cable connected to PXE switch
  • +
  • PXE server running and reachable on the network
  • +
+
+ + +

Step 1: BIOS Configuration

+ +
+ Step 1 +

Enter BIOS Setup

+ +
    +
  1. Plug the MicroPC into the PXE switch along with a mouse and keyboard.
  2. +
  3. Power on the MicroPC and tap F12 to reach the One-Time-Boot menu.
  4. +
  5. Select BIOS Setup.
  6. +
  7. Enable Advanced Setup.
  8. +
+
+ +
+ Step 1a +

Verify Boot Configuration

+ +

Select Boot Configuration and verify the following settings:

+ +
    +
  • Enable Secure Boot is set to ON
  • +
  • Enable Microsoft UEFI CA is set to Enabled
  • +
+
+ +
+ Step 1b +

Verify Storage

+ +

Select Storage and verify:

+ +
    +
  • AHCI/NVMe is selected
  • +
+
+ +
+ Step 1c +

Verify Connection

+ +

Select Connection and verify:

+ +
    +
  • Integrated NIC is set to Enabled with PXE
  • +
+
+ +
+ Step 1d +

Apply and Exit

+ +
    +
  1. Click Apply Changes.
  2. +
  3. Click OK.
  4. +
  5. Click Exit and immediately begin tapping F12 again.
  6. +
+
+ +
+ + +

Step 2: PXE Boot

+ +
+ Step 2 +

Boot from Network

+ +
    +
  1. Once you're back in the One-Time-Boot menu, select the IPV4 option.
  2. +
  3. In the PXE Boot Menu, select Windows PE (Image Deployment) (auto-selected after 30 seconds).
  4. +
  5. Hit any key to verify that Secure Boot is enabled, or wait 5 seconds to automatically continue.
  6. +
+
+ +
+ + +

Step 3: Image Selection

+ +
+ Step 3 +

Select Image and Configuration

+ +

Make the following selections at each menu:

+ +
    +
  1. WinPE Setup Menu: Select 3. GEA Shopfloor
  2. +
  3. GCCH Enrollment Profile: Select 1. No Office
  4. +
  5. Shopfloor PC Type: Select 5. Display
  6. +
  7. Display Type: Select 2. Dashboard
  8. +
+ +
+ Note: + The enrollment profile and PC type selections determine what software and configuration will be applied after imaging completes. +
+
+ +
+ + +

Step 4: Imaging

+ +
+ Step 4 +

Start Imaging

+ +
    +
  1. Once GE Image Setup launches, click Start.
  2. +
  3. From this point the process is mostly automated.
  4. +
  5. Note the Serial Number displayed on screen.
  6. +
  7. Provide the serial number to Patrick and let him know it's a Shopfloor Display.
  8. +
  9. When imaging completes, the PC will reboot into Windows.
  10. +
  11. Once Windows begins loading, unplug the ethernet cable from the PXE switch and plug it into an internet-connected port.
  12. +
+ +
+ Important: Switch network cable after reboot! + The first-logon setup requires internet connectivity to complete GCCH enrollment. The PC will wait at a "Waiting for internet connectivity..." prompt until a connection is available. Unplug from the PXE switch and plug into the corporate network. +
+ +
+ Do not power off or disconnect the MicroPC during imaging. + The imaging process will take several minutes. The PC will reboot automatically when complete. +
+
+ + +
+ Imaging Complete + Once the process finishes and the PC reboots, the Shopfloor Dashboard display configuration will be applied automatically during the first logon sequence. +
+ +
+ + + +
+ + + + + diff --git a/docs/shopfloor-display-imaging-guide.md b/docs/shopfloor-display-imaging-guide.md new file mode 100644 index 0000000..b7ac2a8 --- /dev/null +++ b/docs/shopfloor-display-imaging-guide.md @@ -0,0 +1,40 @@ +# Shopfloor Display MicroPC Imaging Guide + +## Prerequisites +- MicroPC connected to PXE switch +- USB mouse and keyboard connected +- PXE server running and reachable + +## Step 1: BIOS Configuration + +1. Power on the MicroPC and **tap F12** to reach the One-Time-Boot menu. +2. Select **BIOS Setup**. +3. Enable **Advanced Setup**. +4. Select **Boot Configuration**: + - Verify **Enable Secure Boot** is **ON** + - Verify **Enable Microsoft UEFI CA** is set to **Enabled** +5. Select **Storage**: + - Verify **AHCI/NVMe** is selected +6. Select **Connection**: + - Verify **Integrated NIC** is set to **Enabled with PXE** +7. Click **Apply Changes**, then click **OK**. +8. Click **Exit** and immediately begin **tapping F12** again. + +## Step 2: PXE Boot + +1. Once you're back in the One-Time-Boot menu, select the **IPV4** option. +2. In the PXE Boot Menu, select **Windows PE (Image Deployment)** (auto-selected after 30 seconds). +3. Hit any key to verify Secure Boot is enabled, or wait 5 seconds to automatically continue. + +## Step 3: Image Selection + +1. For the WinPE Setup Menu, select **3. GEA Shopfloor**. +2. For GCCH Enrollment Profile, select **1. No Office**. +3. For Shopfloor PC Type, select **5. Display**. +4. For Display Type, select **2. Dashboard**. + +## Step 4: Imaging + +1. Once GE Image Setup launches, click **Start**. +2. From this point the process is mostly automated. +3. Note the **Serial Number** from the screen and let Patrick know it's a Shopfloor Display. diff --git a/playbook/FlatUnattendW10-shopfloor.xml b/playbook/FlatUnattendW10-shopfloor.xml index df26d82..4de6e7a 100644 --- a/playbook/FlatUnattendW10-shopfloor.xml +++ b/playbook/FlatUnattendW10-shopfloor.xml @@ -87,6 +87,16 @@ reg.exe add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v BypassNRO /t REG_DWORD /d 1 /f Bypass OOBE network requirement + + 14 + reg.exe add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v SkipMachineOOBE /t REG_DWORD /d 1 /f + Skip machine OOBE phase + + + 15 + reg.exe add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v SkipUserOOBE /t REG_DWORD /d 1 /f + Skip user OOBE phase + @@ -151,11 +161,16 @@ 5 + powershell.exe -ExecutionPolicy Bypass -Command "Get-NetAdapter -Physical | Where-Object { $_.InterfaceDescription -notmatch 'Wi-Fi|Wireless' } | Disable-NetAdapter -Confirm:$false; while (-not (Test-Connection -ComputerName login.microsoftonline.us -Count 1 -Quiet -ErrorAction SilentlyContinue)) { Start-Sleep -Seconds 5 }; Write-Host 'Internet confirmed over WiFi.'" + Disable wired adapters and wait for WiFi internet + + + 6 powershell.exe -ExecutionPolicy Bypass -File "C:\run-enrollment.ps1" Run GCCH Enrollment - - 6 + + 7 powershell.exe -ExecutionPolicy Bypass -File "C:\Enrollment\Run-ShopfloorSetup.ps1" Run shopfloor PC type setup diff --git a/playbook/pxe_server_setup.yml b/playbook/pxe_server_setup.yml index ab5b5c3..89001d6 100644 --- a/playbook/pxe_server_setup.yml +++ b/playbook/pxe_server_setup.yml @@ -37,11 +37,12 @@ - gea-standard - gea-engineer - gea-shopfloor - - gea-shopfloor-mce - ge-standard - ge-engineer - ge-shopfloor-lockdown - ge-shopfloor-mce + shopfloor_types: + - gea-shopfloor deploy_subdirs: - Applications - Control @@ -298,6 +299,36 @@ state: directory mode: '0777' + - name: "Create enrollment packages directory" + file: + path: /srv/samba/enrollment + state: directory + mode: '0777' + + - name: "Deploy shopfloor setup scripts to enrollment share" + copy: + src: "{{ usb_mount }}/shopfloor-setup/" + dest: /srv/samba/enrollment/shopfloor-setup/ + mode: '0755' + directory_mode: '0755' + ignore_errors: yes + + - name: "Create BIOS update directory on enrollment share" + file: + path: /srv/samba/enrollment/BIOS + state: directory + mode: '0755' + + - name: "Deploy BIOS check script and manifest" + copy: + src: "{{ usb_mount }}/shopfloor-setup/BIOS/{{ item }}" + dest: /srv/samba/enrollment/BIOS/{{ item }} + mode: '0644' + loop: + - check-bios.cmd + - models.txt + ignore_errors: yes + - name: "Create image upload staging directory" file: path: /home/pxe/image-upload @@ -348,6 +379,15 @@ force user = root comment = Blancco Drive Eraser reports + [enrollment] + path = /srv/samba/enrollment + browseable = yes + read only = no + guest ok = no + valid users = pxe-upload + force user = root + comment = GCCH bulk enrollment packages + [image-upload] path = /home/pxe/image-upload browseable = yes @@ -357,6 +397,9 @@ force user = pxe force group = pxe comment = PXE image upload staging area + oplocks = no + level2 oplocks = no + strict sync = yes - name: "Create Samba users (pxe-upload and blancco)" shell: | @@ -392,6 +435,15 @@ force: no loop: "{{ image_types }}" + - name: "Deploy shopfloor unattend.xml template" + copy: + src: "{{ usb_mount }}/FlatUnattendW10-shopfloor.xml" + dest: "{{ samba_share }}/{{ item }}/Deploy/FlatUnattendW10.xml" + mode: '0644' + force: no + loop: "{{ shopfloor_types }}" + ignore_errors: yes + - name: "Daily cron to create/refresh Media.tag for all images" copy: content: | @@ -635,6 +687,7 @@ Environment=CLONEZILLA_SHARE=/srv/samba/clonezilla Environment=WEB_ROOT={{ web_root }} Environment=BLANCCO_REPORTS=/srv/samba/blancco-reports + Environment=ENROLLMENT_SHARE=/srv/samba/enrollment Environment=AUDIT_LOG=/var/log/pxe-webapp-audit.log ExecStart=/usr/bin/python3 app.py Restart=always diff --git a/playbook/shopfloor-setup/BIOS/check-bios.cmd b/playbook/shopfloor-setup/BIOS/check-bios.cmd new file mode 100644 index 0000000..d745d68 --- /dev/null +++ b/playbook/shopfloor-setup/BIOS/check-bios.cmd @@ -0,0 +1,158 @@ +@echo off +REM check-bios.cmd - Check and apply Dell BIOS update from WinPE x64 +REM Called from startnet.cmd before imaging menu +REM Requires: Flash64W.exe (Dell 64-Bit BIOS Flash Utility) in same directory +REM +REM Exit behavior: +REM - BIOS update applied -> reboots automatically (flashes during POST) +REM - Already up to date -> returns to startnet.cmd +REM - No match / no files -> returns to startnet.cmd + +set BIOSDIR=%~dp0 +set FLASH=%BIOSDIR%Flash64W.exe +set MANIFEST=%BIOSDIR%models.txt + +if not exist "%FLASH%" ( + echo Flash64W.exe not found, skipping BIOS check. + exit /b 0 +) + +if not exist "%MANIFEST%" ( + echo models.txt not found, skipping BIOS check. + exit /b 0 +) + +REM --- Get system model from WMI --- +set SYSMODEL= +for /f "skip=1 tokens=*" %%M in ('wmic csproduct get name 2^>NUL') do ( + if not defined SYSMODEL set "SYSMODEL=%%M" +) +REM Trim trailing whitespace +for /f "tokens=*" %%a in ("%SYSMODEL%") do set "SYSMODEL=%%a" + +if "%SYSMODEL%"=="" ( + echo Could not detect system model, skipping BIOS check. + exit /b 0 +) + +echo Model: %SYSMODEL% + +REM --- Get current BIOS version --- +set BIOSVER= +for /f "skip=1 tokens=*" %%V in ('wmic bios get smbiosbiosversion 2^>NUL') do ( + if not defined BIOSVER set "BIOSVER=%%V" +) +for /f "tokens=*" %%a in ("%BIOSVER%") do set "BIOSVER=%%a" +echo Current BIOS: %BIOSVER% + +REM --- Read manifest and find matching BIOS file --- +REM Format: ModelSubstring|BIOSFile|Version +set BIOSFILE= +set TARGETVER= +for /f "usebackq eol=# tokens=1,2,3 delims=|" %%A in ("%MANIFEST%") do ( + echo "%SYSMODEL%" | find /I "%%A" >NUL + if not errorlevel 1 ( + set "BIOSFILE=%%B" + set "TARGETVER=%%C" + goto :found_bios + ) +) + +echo No BIOS update available for this model. +exit /b 0 + +:found_bios +if not exist "%BIOSDIR%%BIOSFILE%" ( + echo WARNING: %BIOSFILE% not found in BIOS folder. + exit /b 0 +) + +REM --- Skip if already at target version --- +echo.%BIOSVER%| find /I "%TARGETVER%" >NUL +if not errorlevel 1 goto :already_current + +REM --- Compare versions to prevent downgrade --- +REM Split current and target into major.minor.patch and compare numerically +call :compare_versions "%BIOSVER%" "%TARGETVER%" +if "%VERCMP%"=="newer" goto :already_newer +goto :do_flash + +:already_current +echo BIOS is already up to date - %TARGETVER% +exit /b 0 + +:already_newer +echo Current BIOS %BIOSVER% is newer than target %TARGETVER% - skipping. +exit /b 0 + +:do_flash + +echo Update: %BIOSVER% -^> %TARGETVER% +echo Applying BIOS update (this may take a few minutes, do not power off)... + +REM --- Run Flash64W.exe from BIOS directory to avoid UNC path issues --- +REM Exit codes: 0=success, 2=reboot needed, 3=already current, 6=reboot needed +pushd "%BIOSDIR%" +Flash64W.exe /b="%BIOSFILE%" /s /f /l=X:\bios-update.log +set FLASHRC=%ERRORLEVEL% +popd +echo Flash complete (exit code %FLASHRC%). + +if "%FLASHRC%"=="3" ( + echo BIOS is already up to date. + exit /b 0 +) + +if "%FLASHRC%"=="0" ( + echo BIOS update complete. + exit /b 0 +) + +if "%FLASHRC%"=="2" goto :staged +if "%FLASHRC%"=="6" goto :staged + +echo WARNING: Flash64W.exe returned unexpected code %FLASHRC%. +echo Check X:\bios-update.log for details. +exit /b 0 + +:staged +echo. +echo ======================================== +echo BIOS update staged successfully. +echo It will flash during POST after the +echo post-imaging reboot. +echo ======================================== +echo. +exit /b 0 + +REM ============================================================ +REM compare_versions - Compare two dotted version strings +REM Usage: call :compare_versions "current" "target" +REM Sets VERCMP=newer if current > target, older if current < target, equal if same +REM ============================================================ +:compare_versions +set "VERCMP=equal" +set "_CV=%~1" +set "_TV=%~2" + +REM Parse current version parts +for /f "tokens=1,2,3 delims=." %%a in ("%_CV%") do ( + set /a "C1=%%a" 2>NUL + set /a "C2=%%b" 2>NUL + set /a "C3=%%c" 2>NUL +) +REM Parse target version parts +for /f "tokens=1,2,3 delims=." %%a in ("%_TV%") do ( + set /a "T1=%%a" 2>NUL + set /a "T2=%%b" 2>NUL + set /a "T3=%%c" 2>NUL +) + +if %C1% GTR %T1% ( set "VERCMP=newer" & goto :eof ) +if %C1% LSS %T1% ( set "VERCMP=older" & goto :eof ) +if %C2% GTR %T2% ( set "VERCMP=newer" & goto :eof ) +if %C2% LSS %T2% ( set "VERCMP=older" & goto :eof ) +if %C3% GTR %T3% ( set "VERCMP=newer" & goto :eof ) +if %C3% LSS %T3% ( set "VERCMP=older" & goto :eof ) +set "VERCMP=equal" +goto :eof diff --git a/playbook/shopfloor-setup/CMM/01-Setup-CMM.ps1 b/playbook/shopfloor-setup/CMM/01-Setup-CMM.ps1 new file mode 100644 index 0000000..87d8820 --- /dev/null +++ b/playbook/shopfloor-setup/CMM/01-Setup-CMM.ps1 @@ -0,0 +1,45 @@ +# 01-Setup-CMM.ps1 — CMM-specific setup (runs after Shopfloor baseline) +# Installs Hexagon CLM Tools, PC-DMIS 2016, and PC-DMIS 2019 R2 + +Write-Host "=== CMM Setup ===" + +$hexDir = "C:\Enrollment\shopfloor-setup\CMM\hexagon" + +if (-not (Test-Path $hexDir)) { + Write-Warning "Hexagon folder not found at $hexDir — skipping CMM installs." + exit 0 +} + +# --- Find installers --- +$clm = Get-ChildItem -Path $hexDir -Filter "CLM_*.exe" | Select-Object -First 1 +$pcdmis16 = Get-ChildItem -Path $hexDir -Filter "Pcdmis2016*x64.exe" | Select-Object -First 1 +$pcdmis19 = Get-ChildItem -Path $hexDir -Filter "Pcdmis2019*x64.exe" | Select-Object -First 1 + +# --- 1. Install CLM Tools (license manager — must be first) --- +if ($clm) { + Write-Host "Installing CLM Tools: $($clm.Name)..." + $p = Start-Process -FilePath $clm.FullName -ArgumentList "-q -norestart" -Wait -PassThru + Write-Host " CLM Tools exit code: $($p.ExitCode)" +} else { + Write-Warning "CLM Tools installer not found in $hexDir (expected CLM_*.exe)" +} + +# --- 2. Install PC-DMIS 2016 --- +if ($pcdmis16) { + Write-Host "Installing PC-DMIS 2016: $($pcdmis16.Name)..." + $p = Start-Process -FilePath $pcdmis16.FullName -ArgumentList "-q INSTALLPDFCONVERTER=0 INSTALLOFFLINEHELP=0 HEIP=0 -norestart" -Wait -PassThru + Write-Host " PC-DMIS 2016 exit code: $($p.ExitCode)" +} else { + Write-Warning "PC-DMIS 2016 installer not found in $hexDir (expected Pcdmis2016*x64.exe)" +} + +# --- 3. Install PC-DMIS 2019 R2 --- +if ($pcdmis19) { + Write-Host "Installing PC-DMIS 2019 R2: $($pcdmis19.Name)..." + $p = Start-Process -FilePath $pcdmis19.FullName -ArgumentList "-q INSTALLPDFCONVERTER=0 INSTALLOFFLINEHELP=0 HEIP=0 -norestart" -Wait -PassThru + Write-Host " PC-DMIS 2019 exit code: $($p.ExitCode)" +} else { + Write-Warning "PC-DMIS 2019 installer not found in $hexDir (expected Pcdmis2019*x64.exe)" +} + +Write-Host "=== CMM Setup Complete ===" diff --git a/playbook/shopfloor-setup/Check-Policies.bat b/playbook/shopfloor-setup/Check-Policies.bat new file mode 100644 index 0000000..592150f --- /dev/null +++ b/playbook/shopfloor-setup/Check-Policies.bat @@ -0,0 +1,13 @@ +@echo off +title MDM Policy Check +powershell.exe -ExecutionPolicy Bypass -Command ^ + "$path = 'HKLM:\SOFTWARE\Microsoft\PolicyManager\current\device\ADMX_Power';" ^ + "Write-Host '';" ^ + "if (Test-Path $path) {" ^ + " Write-Host ' READY FOR LOCKDOWN ' -ForegroundColor White -BackgroundColor DarkGreen;" ^ + "} else {" ^ + " Write-Host ' NOT READY FOR LOCKDOWN ' -ForegroundColor White -BackgroundColor Red;" ^ + "};" ^ + "Write-Host '';" ^ + "Write-Host 'Press any key to exit...';" ^ + "$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')" diff --git a/playbook/shopfloor-setup/Display/01-Setup-Display.ps1 b/playbook/shopfloor-setup/Display/01-Setup-Display.ps1 new file mode 100644 index 0000000..bcd393d --- /dev/null +++ b/playbook/shopfloor-setup/Display/01-Setup-Display.ps1 @@ -0,0 +1,45 @@ +# 01-Setup-Display.ps1 — Display-specific setup (runs after Shopfloor baseline) +# Reads display-type.txt to install either LobbyDisplay or Dashboard kiosk app. + +$enrollDir = "C:\Enrollment" +$typeFile = Join-Path $enrollDir "display-type.txt" +$setupDir = Split-Path -Parent $MyInvocation.MyCommand.Path + +if (-not (Test-Path $typeFile)) { + Write-Warning "No display-type.txt found - skipping display setup." + return +} + +$displayType = (Get-Content $typeFile -First 1).Trim() +Write-Host "=== Display Setup: $displayType ===" + +switch ($displayType) { + "Lobby" { + $installer = Join-Path $setupDir "GEAerospaceLobbyDisplaySetup.exe" + $appName = "Lobby Display" + } + "Dashboard" { + $installer = Join-Path $setupDir "GEAerospaceDashboardSetup.exe" + $appName = "Dashboard" + } + default { + Write-Warning "Unknown display type: $displayType" + return + } +} + +if (-not (Test-Path $installer)) { + Write-Warning "$appName installer not found at $installer - skipping." + return +} + +Write-Host "Installing $appName..." +$proc = Start-Process -FilePath $installer -ArgumentList '/VERYSILENT', '/SUPPRESSMSGBOXES', '/NORESTART', "/LOG=C:\Enrollment\$appName-install.log" -Wait -PassThru + +if ($proc.ExitCode -eq 0) { + Write-Host "$appName installed successfully." +} else { + Write-Warning "$appName exited with code $($proc.ExitCode). Check C:\Enrollment\$appName-install.log" +} + +Write-Host "=== Display Setup Complete ===" diff --git a/playbook/shopfloor-setup/Genspect/01-Setup-Genspect.ps1 b/playbook/shopfloor-setup/Genspect/01-Setup-Genspect.ps1 new file mode 100644 index 0000000..d5a7010 --- /dev/null +++ b/playbook/shopfloor-setup/Genspect/01-Setup-Genspect.ps1 @@ -0,0 +1,14 @@ +# 01-Setup-Genspect.ps1 — Genspect-specific setup (runs after Shopfloor baseline) + +Write-Host "=== Genspect Setup ===" + +# --- Add Genspect credentials --- +# cmdkey /generic:genspect-server /user:domain\genspectuser /pass:password + +# --- Install Genspect applications --- +# Start-Process msiexec.exe -ArgumentList '/i "C:\Enrollment\shopfloor-setup\Genspect\GenspectApp.msi" /qn' -Wait + +# --- Genspect configuration --- +# Set-ItemProperty -Path "HKLM:\SOFTWARE\CompanyName" -Name "PCType" -Value "Genspect" + +Write-Host "=== Genspect Setup Complete ===" diff --git a/playbook/shopfloor-setup/Keyence/01-Setup-Keyence.ps1 b/playbook/shopfloor-setup/Keyence/01-Setup-Keyence.ps1 new file mode 100644 index 0000000..1377245 --- /dev/null +++ b/playbook/shopfloor-setup/Keyence/01-Setup-Keyence.ps1 @@ -0,0 +1,14 @@ +# 01-Setup-Keyence.ps1 — Keyence-specific setup (runs after Shopfloor baseline) + +Write-Host "=== Keyence Setup ===" + +# --- Add Keyence credentials --- +# cmdkey /generic:keyence-server /user:domain\keyenceuser /pass:password + +# --- Install Keyence applications --- +# Start-Process msiexec.exe -ArgumentList '/i "C:\Enrollment\shopfloor-setup\Keyence\KeyenceApp.msi" /qn' -Wait + +# --- Keyence configuration --- +# Set-ItemProperty -Path "HKLM:\SOFTWARE\CompanyName" -Name "PCType" -Value "Keyence" + +Write-Host "=== Keyence Setup Complete ===" diff --git a/playbook/shopfloor-setup/Run-ShopfloorSetup.ps1 b/playbook/shopfloor-setup/Run-ShopfloorSetup.ps1 index 151a624..7574579 100644 --- a/playbook/shopfloor-setup/Run-ShopfloorSetup.ps1 +++ b/playbook/shopfloor-setup/Run-ShopfloorSetup.ps1 @@ -4,6 +4,19 @@ # Cancel any pending reboot so it doesn't interrupt setup shutdown -a 2>$null +# Prompt user to unplug from PXE switch before re-enabling wired adapters +Write-Host "" +Write-Host "========================================" -ForegroundColor Yellow +Write-Host " UNPLUG the ethernet cable from the" -ForegroundColor Yellow +Write-Host " PXE imaging switch NOW." -ForegroundColor Yellow +Write-Host "========================================" -ForegroundColor Yellow +Write-Host "" +Write-Host "Press any key to continue..." -ForegroundColor Yellow +$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") + +# Re-enable wired adapters +Get-NetAdapter -Physical | Where-Object { $_.InterfaceDescription -notmatch 'Wi-Fi|Wireless' } | Enable-NetAdapter -Confirm:$false -ErrorAction SilentlyContinue + $enrollDir = "C:\Enrollment" $typeFile = Join-Path $enrollDir "pc-type.txt" $setupDir = Join-Path $enrollDir "shopfloor-setup" @@ -56,5 +69,17 @@ if ($pcType -ne "Shopfloor") { } Write-Host "Shopfloor setup complete for $pcType." + +# Copy backup lockdown script to SupportUser desktop +$lockdownScript = Join-Path $setupDir "backup_lockdown.bat" +if (Test-Path $lockdownScript) { + Copy-Item -Path $lockdownScript -Destination "C:\Users\SupportUser\Desktop\backup_lockdown.bat" -Force + Write-Host "backup_lockdown.bat copied to desktop." +} + +# Set auto-logon to expire after 2 more logins +reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v AutoLogonCount /t REG_DWORD /d 2 /f | Out-Null +Write-Host "Auto-logon set to 2 remaining logins." + Write-Host "Rebooting in 10 seconds..." shutdown /r /t 10 diff --git a/playbook/shopfloor-setup/Shopfloor/02-OpenTextCSF.ps1 b/playbook/shopfloor-setup/Shopfloor/02-OpenTextCSF.ps1 new file mode 100644 index 0000000..daf5bb6 --- /dev/null +++ b/playbook/shopfloor-setup/Shopfloor/02-OpenTextCSF.ps1 @@ -0,0 +1,51 @@ +# 02-OpenTextCSF.ps1 — Deploy OpenText HostExplorer CSF profiles (baseline) +# Copies connection profiles, keymaps, menus, and macros to ProgramData. + +$setupDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$csfSource = Join-Path $setupDir "csf" +$destRoot = "C:\ProgramData\Hummingbird\Connectivity\15.00\Shared" + +if (-not (Test-Path $csfSource)) { + Write-Warning "CSF source folder not found at $csfSource - skipping." + return +} + +Write-Host "Deploying OpenText CSF profiles to $destRoot ..." + +# Map of source subdirectories to destination subdirectories +$folders = @( + @{ Src = "Profile"; Dest = "Profile" } + @{ Src = "Accessories\EB"; Dest = "Accessories\EB" } + @{ Src = "HostExplorer\Keymap"; Dest = "HostExplorer\Keymap" } + @{ Src = "HostExplorer\Menu"; Dest = "HostExplorer\Menu" } +) + +foreach ($folder in $folders) { + $src = Join-Path $csfSource $folder.Src + $dest = Join-Path $destRoot $folder.Dest + + if (-not (Test-Path $src)) { + Write-Host " Skipping $($folder.Src) (not present in csf source)" + continue + } + + if (-not (Test-Path $dest)) { + New-Item -Path $dest -ItemType Directory -Force | Out-Null + Write-Host " Created $dest" + } + + $files = Get-ChildItem -Path $src -File + foreach ($file in $files) { + Copy-Item -Path $file.FullName -Destination $dest -Force + Write-Host " Copied $($file.Name) -> $dest" + } +} + +# Copy pre-made .lnk shortcuts to Public Desktop +$lnkFiles = Get-ChildItem -Path $csfSource -Filter "*.lnk" -File +foreach ($lnk in $lnkFiles) { + Copy-Item -Path $lnk.FullName -Destination "C:\Users\Public\Desktop" -Force + Write-Host " Copied $($lnk.Name) -> Public Desktop" +} + +Write-Host "OpenText CSF deployment complete." diff --git a/playbook/shopfloor-setup/Shopfloor/03-StartMenu.ps1 b/playbook/shopfloor-setup/Shopfloor/03-StartMenu.ps1 new file mode 100644 index 0000000..5ad476f --- /dev/null +++ b/playbook/shopfloor-setup/Shopfloor/03-StartMenu.ps1 @@ -0,0 +1,46 @@ +# 03-StartMenu.ps1 — Create Start Menu shortcuts for all users (baseline) +# Shortcuts in ProgramData\Microsoft\Windows\Start Menu\Programs\ persist for all accounts. + +$startMenu = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs" +$shell = New-Object -ComObject WScript.Shell + +# --- Defect Tracker --- +$lnk = $shell.CreateShortcut("$startMenu\Defect Tracker.lnk") +$lnk.TargetPath = "S:\DT\Defect_Tracker\Defect_Tracker.application" +$lnk.Save() +Write-Host "Created Start Menu shortcut: Defect Tracker" + +# --- Plant Applications (Edge) --- +$lnk = $shell.CreateShortcut("$startMenu\Plant Applications.lnk") +$lnk.TargetPath = "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" +$lnk.Arguments = "https://mes-wjefferson.apps.lr.geaerospace.net/run/?app_name=Plant%20Applications" +$lnk.Save() +Write-Host "Created Start Menu shortcut: Plant Applications" + +# --- ShopDB --- +$lnk = $shell.CreateShortcut("$startMenu\ShopDB.lnk") +$lnk.TargetPath = "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" +$lnk.Arguments = "http://tsgwp00524.logon.ds.ge.com" +$lnk.Save() +Write-Host "Created Start Menu shortcut: ShopDB" + +# --- Shopfloor Dashboard --- +$lnk = $shell.CreateShortcut("$startMenu\Shopfloor Dashboard.lnk") +$lnk.TargetPath = "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" +$lnk.Arguments = "https://tsgwp00525.wjs.geaerospace.net/shopdb/shopfloor-dashboard/" +$lnk.Save() +Write-Host "Created Start Menu shortcut: Shopfloor Dashboard" + +# --- ShopDB (GEA) --- +$lnk = $shell.CreateShortcut("$startMenu\ShopDB (GEA).lnk") +$lnk.TargetPath = "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" +$lnk.Arguments = "https://tsgwp00525.wjs.geaerospace.net/shopdb/" +$lnk.Save() +Write-Host "Created Start Menu shortcut: ShopDB (GEA)" + +# --- Add more shortcuts below --- +# $lnk = $shell.CreateShortcut("$startMenu\AppName.lnk") +# $lnk.TargetPath = "C:\Path\To\App.exe" +# $lnk.Save() + +Write-Host "Start Menu shortcuts complete." diff --git a/playbook/shopfloor-setup/Shopfloor/04-NetworkAndWinRM.ps1 b/playbook/shopfloor-setup/Shopfloor/04-NetworkAndWinRM.ps1 new file mode 100644 index 0000000..3f8de95 --- /dev/null +++ b/playbook/shopfloor-setup/Shopfloor/04-NetworkAndWinRM.ps1 @@ -0,0 +1,9 @@ +# 04-NetworkAndWinRM.ps1 — Set network profiles to Private and enable WinRM (baseline) + +# --- Set all network profiles to Private --- +Get-NetConnectionProfile | Set-NetConnectionProfile -NetworkCategory Private +Write-Host "All network profiles set to Private." + +# --- Enable and configure WinRM --- +Enable-PSRemoting -Force -SkipNetworkProfileCheck +Write-Host "WinRM enabled." diff --git a/playbook/shopfloor-setup/Shopfloor/05-PowerAndDisplay.ps1 b/playbook/shopfloor-setup/Shopfloor/05-PowerAndDisplay.ps1 new file mode 100644 index 0000000..0829b7f --- /dev/null +++ b/playbook/shopfloor-setup/Shopfloor/05-PowerAndDisplay.ps1 @@ -0,0 +1,24 @@ +# 05-PowerAndDisplay.ps1 — Shopfloor power plan and display settings (baseline) + +# --- Set display timeout to Never (0 = never) on AC and DC --- +powercfg /change monitor-timeout-ac 0 +powercfg /change monitor-timeout-dc 0 +powercfg /change standby-timeout-ac 0 +powercfg /change standby-timeout-dc 0 +Write-Host "Power: display and standby set to Never." + +# --- Set High Performance power plan --- +$highPerf = powercfg /list | Select-String "High performance" +if ($highPerf -match "([a-f0-9-]{36})") { + powercfg /setactive $Matches[1] + Write-Host "Power plan set to High Performance." +} else { + Write-Host "High Performance plan not found, using current plan." +} + +# --- Disable lock screen timeout (screen saver) --- +Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name ScreenSaveActive -Value "0" -Force +Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name ScreenSaveTimeOut -Value "0" -Force +Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name "SCRNSAVE.EXE" -Value "" -Force +Write-Host "Screen saver set to None and disabled." + diff --git a/playbook/shopfloor-setup/Shopfloor/csf/Accessories/EB/Office.ebs b/playbook/shopfloor-setup/Shopfloor/csf/Accessories/EB/Office.ebs new file mode 100755 index 0000000..fc7a516 --- /dev/null +++ b/playbook/shopfloor-setup/Shopfloor/csf/Accessories/EB/Office.ebs @@ -0,0 +1,51 @@ +'---------------------------------------------------------------------- +' This macro was created by the macro recorder. +' Macro File: Office.ebs +' Date: Wed May 18 09:55:44 2016 +' Recorded for profile: WJ_Office +'---------------------------------------------------------------------- + +Sub Main + Dim HostExplorer as Object + Dim MyHost as Object + Dim Rc as Integer + + Dim iPSUpdateTimeout + Dim iWaitForStringTimeout + + On Error goto GenericErrorHandler + + Set HostExplorer = CreateObject("HostExplorer") ' Initialize HostExplorer Object + Set MyHost = HostExplorer.HostFromProfile("WJ_Office") ' Set object for the desired session + If MyHost is Nothing Then Goto NoSession + + iPSUpdateTimeout = 60 ' WaitPSUpdated timeout set to 60 seconds + iWaitForStringTimeout = 60 ' WaitForString timeout set to 60 seconds + + Rc = MyHost.WaitForString( "Username:", -1, iWaitForStringTimeout, TRUE ) + If Rc = 0 Then Goto OnWaitForStringTimeout + Rc = MyHost.Keys("shop_pc^M") + Rc = MyHost.WaitPSUpdated( iPSUpdateTimeout, TRUE ) + If Rc <> 0 Then Goto OnWaitPSUpdatedTimeout + Rc = MyHost.WaitForString( "Do you have a barcode reader?", -1, iWaitForStringTimeout, TRUE ) + If Rc = 0 Then Goto OnWaitForStringTimeout + Exit Sub + +'-------------------- Runtime Error Handlers -------------------- +GenericErrorHandler: + Msgbox "Error " & Err & " : """ & Error(Err) & """ has occurred on line " & Erl-1 & "." & Chr(10) & "Unable to continue macro execution.", 16, "HostExplorer Basic Macro Error" + Exit Sub + +NoSession: + Msgbox "Profile ""WJ_Office"" is not running." & Chr(10) & "Unable to execute macro.", 16, "HostExplorer Macro Error" + Exit Sub + +OnWaitPSUpdatedTimeout: + Msgbox "Timeout occured waiting for host to update screen." & Chr(10) & "Unable to continue macro execution.", 16, "HostExplorer Basic Macro Error" + Exit Sub + +OnWaitForStringTimeout: + Msgbox "Timeout occured waiting for string on host screen." & Chr(10) & "Unable to continue macro execution.", 16, "HostExplorer Basic Macro Error" + Exit Sub + +End Sub diff --git a/playbook/shopfloor-setup/Shopfloor/csf/Accessories/EB/mmcs.ebs b/playbook/shopfloor-setup/Shopfloor/csf/Accessories/EB/mmcs.ebs new file mode 100755 index 0000000..e6cb9a6 --- /dev/null +++ b/playbook/shopfloor-setup/Shopfloor/csf/Accessories/EB/mmcs.ebs @@ -0,0 +1,35 @@ +'---------------------------------------------------------------------- +' This macro was created by the macro recorder. +' Macro File: mmcs.ebs +' Date: Tue Jun 04 08:56:51 2013 +' Recorded for profile: mmcs +'---------------------------------------------------------------------- + +Sub Main + Dim HostExplorer as Object + Dim MyHost as Object + Dim iIdleTime + + Dim iPSUpdateTime + + On Error goto ErrorCheck + + Set HostExplorer = CreateObject("HostExplorer") ' Initialize HostExplorer Object + Set MyHost = HostExplorer.HostFromProfile("mmcs") ' Set object for the desired session + + iPSUpdateTime = 60 ' PS Update wait time set to 60 seconds + iIdleTime = 2000 ' Idle time set to 2000 milliseconds + + MyHost.WaitPSUpdated(iPSUpdateTime) + Rc = MyHost.WaitForString("Username:", -1, 9999, TRUE) + MyHost.Keys("mmcswj^M") + MyHost.WaitPSUpdated(iPSUpdateTime) + Rc = MyHost.WaitForString("| Badge : |", -1, 9999, TRUE) + Exit Sub + +ErrorCheck: + if (Err = 440) Then + Msgbox "The specified session is not running.", 16, "Hummingbird Macro Error" + End If + Exit Sub +End Sub diff --git a/playbook/shopfloor-setup/Shopfloor/csf/Accessories/EB/shopfloor.ebs b/playbook/shopfloor-setup/Shopfloor/csf/Accessories/EB/shopfloor.ebs new file mode 100755 index 0000000..796b971 --- /dev/null +++ b/playbook/shopfloor-setup/Shopfloor/csf/Accessories/EB/shopfloor.ebs @@ -0,0 +1,56 @@ +'---------------------------------------------------------------------- +' This macro was created by the macro recorder. +' Macro File: shopfloor.ebs +' Date: Wed May 18 09:57:00 2016 +' Recorded for profile: WJ Shopfloor +'---------------------------------------------------------------------- + +Sub Main + Dim HostExplorer as Object + Dim MyHost as Object + Dim Rc as Integer + + Dim iPSUpdateTimeout + Dim iWaitForStringTimeout + + On Error goto GenericErrorHandler + + Set HostExplorer = CreateObject("HostExplorer") ' Initialize HostExplorer Object + Set MyHost = HostExplorer.HostFromProfile("WJ Shopfloor") ' Set object for the desired session + If MyHost is Nothing Then Goto NoSession + + iPSUpdateTimeout = 60 ' WaitPSUpdated timeout set to 60 seconds + iWaitForStringTimeout = 60 ' WaitForString timeout set to 60 seconds + + Rc = MyHost.WaitForString( "Username:", -1, iWaitForStringTimeout, TRUE ) + If Rc = 0 Then Goto OnWaitForStringTimeout + Rc = MyHost.Keys("shop_xmi^M") + Rc = MyHost.WaitPSUpdated( iPSUpdateTimeout, TRUE ) + If Rc <> 0 Then Goto OnWaitPSUpdatedTimeout + Rc = MyHost.WaitForString( "Password:", -1, iWaitForStringTimeout, TRUE ) + If Rc = 0 Then Goto OnWaitForStringTimeout + Rc = MyHost.Keys("dnc123^M") + Rc = MyHost.WaitPSUpdated( iPSUpdateTimeout, TRUE ) + If Rc <> 0 Then Goto OnWaitPSUpdatedTimeout + Rc = MyHost.WaitForString( "| EXIT | |", -1, iWaitForStringTimeout, TRUE ) + If Rc = 0 Then Goto OnWaitForStringTimeout + Exit Sub + +'-------------------- Runtime Error Handlers -------------------- +GenericErrorHandler: + Msgbox "Error " & Err & " : """ & Error(Err) & """ has occurred on line " & Erl-1 & "." & Chr(10) & "Unable to continue macro execution.", 16, "HostExplorer Basic Macro Error" + Exit Sub + +NoSession: + Msgbox "Profile ""WJ Shopfloor"" is not running." & Chr(10) & "Unable to execute macro.", 16, "HostExplorer Macro Error" + Exit Sub + +OnWaitPSUpdatedTimeout: + Msgbox "Timeout occured waiting for host to update screen." & Chr(10) & "Unable to continue macro execution.", 16, "HostExplorer Basic Macro Error" + Exit Sub + +OnWaitForStringTimeout: + Msgbox "Timeout occured waiting for string on host screen." & Chr(10) & "Unable to continue macro execution.", 16, "HostExplorer Basic Macro Error" + Exit Sub + +End Sub diff --git a/playbook/shopfloor-setup/Shopfloor/csf/HostExplorer/Keymap/Default.kmv b/playbook/shopfloor-setup/Shopfloor/csf/HostExplorer/Keymap/Default.kmv new file mode 100755 index 0000000..11b0978 --- /dev/null +++ b/playbook/shopfloor-setup/Shopfloor/csf/HostExplorer/Keymap/Default.kmv @@ -0,0 +1,9 @@ +[KEYMAP] +Normal Entries=1 +Normal0=Backspace,Delete +Shift Entries=0 +Ctrl Entries=0 +ShiftCtrl Entries=0 +Alt Entries=0 +ShiftAlt Entries=0 +AltCtrl Entries=0 diff --git a/playbook/shopfloor-setup/Shopfloor/csf/HostExplorer/Keymap/Office.kmv b/playbook/shopfloor-setup/Shopfloor/csf/HostExplorer/Keymap/Office.kmv new file mode 100755 index 0000000..3dbfc48 --- /dev/null +++ b/playbook/shopfloor-setup/Shopfloor/csf/HostExplorer/Keymap/Office.kmv @@ -0,0 +1,13 @@ +[KEYMAP] +Normal Entries=5 +Normal0=F4,Pf4 +Normal1=Subtract,Num-Pad-Minus +Normal2=Multiply,Num-Pad-* +Normal3=Divide,Num-Pad-/ +Normal4=Backspace,Delete +Shift Entries=0 +Ctrl Entries=0 +ShiftCtrl Entries=0 +Alt Entries=0 +ShiftAlt Entries=0 +AltCtrl Entries=0 diff --git a/playbook/shopfloor-setup/Shopfloor/csf/HostExplorer/Menu/Default Shop.hmv b/playbook/shopfloor-setup/Shopfloor/csf/HostExplorer/Menu/Default Shop.hmv new file mode 100755 index 0000000..9a5d572 --- /dev/null +++ b/playbook/shopfloor-setup/Shopfloor/csf/HostExplorer/Menu/Default Shop.hmv @@ -0,0 +1,23 @@ +[Version] +AssemblyVersion=1 +[Console] +Title=HostExplorer Menu Editor +IconPath=HumCSSPlugins.HumCSSActiveTunnelsPlugin +IconID=IDI_CSS_CONSOLE +[Attributes] +SupportAssemblyEditing=1 +SupportSeparator=1 +ParentLevels=100 +[Parent Node] +name=Menu +[SubParent Node] +name=Submenu +[Child Node] +name=Menu Option +[Messages] +Create New Parent=Create New Menu +Create New SubParent=Create New Submenu +Insert New Parent=Insert New Menu +Insert New SubParent=Insert New Submenu +[Root Group] +Name=Menu diff --git a/playbook/shopfloor-setup/Shopfloor/csf/HostExplorer/Menu/Default VT.hmv b/playbook/shopfloor-setup/Shopfloor/csf/HostExplorer/Menu/Default VT.hmv new file mode 100755 index 0000000..9c43eca --- /dev/null +++ b/playbook/shopfloor-setup/Shopfloor/csf/HostExplorer/Menu/Default VT.hmv @@ -0,0 +1,474 @@ +[version] +AssemblyVersion=1 + +[nls] +filename=menunls$LANGUAGE_EXTENSION$.hma + +[Console] +Title=HostExplorer Menu Editor +IconPath=HumCSSPlugins.HumCSSActiveTunnelsPlugin +IconID=IDI_CSS_CONSOLE + +[Attributes] +SupportAssemblyEditing=1 +SupportEnablingDisablingItems=0 +SupportSeparator=1 + +[Parent Node] +Nameid=Parent + +[SubParent Node] +Nameid=SubParent + +[Child Node] +Nameid=Child + +[Messages] +Nameid1=Create New Parent +Nameid2=Create New SubParent +Nameid3=Insert New Parent +Nameid4=Insert New SubParent + +[root group] +Name=Menu +item1=File +item2=Edit +item3=Transfer +item4=Fonts +item5=options +item6=tools +item7=view +item8=window +item9=help + +[Separator] +name= +id=0 + +[File] +Nameid=File +id=4071 +Parent=1 +item1=new session +item2=duplicate session +item3=open session +item4=open session in same window +item5=save session profile +item6=close session +item7=separator +item8=open layout +item9=save layout +item10=separator +item11=connect +item12=disconnect +item13=separator +item14=print screen +item15=print multiple screens +item16=save screen +item17=send screen +item18=separator +item19=screen capture +item20=separator +item21=Recent Sessions Submenu +item22=separator +item23=exit all + +[New Session] +nameid=New Session +id=Dlg-New-Session + +[Duplicate Session] +nameid=Duplicate Session +id=Duplicate-Session + +[Open Session] +nameid=Open Session +id=Dlg-Open-Session + +[Open Session In Same Window] +nameid=Open Session In Same Window +id=Dlg-Open-Session-In-Same-Window + +[Save Session Profile] +nameid=Save Session Profile +id=Dlg-Save-Profile + +[Close Session] +nameid=Close Session +id=Dlg-Close-Session + +[open layout] +nameid=Open Layout +id=Dlg-Open-Layout + +[save layout] +nameid=Save Layout +id=Dlg-Save-Layout + +[connect] +nameid=connect +id=Connect + +[disconnect] +nameid=disconnect +id=Disconnect + +[print screen] +nameid=print screen +id=Dlg-Print-Screen + +[print multiple screens] +nameid=print multiple screens +id=Dlg-Print-Multiple-Screens + + +[save screen] +nameid=Save Screen +id=Dlg-Save-Screen + +[send screen] +nameid=Send Screen +id=Send-Screen + +[screen capture] +nameid=Screen Capture +id=Toggle-Capture + +[Recent Sessions Submenu] +nameid=Recent Sessions +id=4187 +Parent=1 +item1=recent sessions + +[recent sessions] +nameid=Recent Sessions +id=Recent-Sessions + +[Exit all] +nameid=exit all +id=Dlg-Exit + + +[Edit] +Nameid=Edit +id=4072 +Parent=1 +item1=copy +item2=copy append +item3=paste +item4=separator +item5=select all +item6=separator +item7=edit find +item8=separator +item9=clear display +item10=clear all +item11=soft terminal reset + +[undo] +nameid=undo +id=Edit-Undo + +[redo] +nameid=redo +id=Edit-Redo + +[cut] +nameid=cut +id=Edit-Cut + +[Copy] +nameid=copy +id=Edit-Copy + +[Copy append] +nameid=Copy Append +id=Edit-Copy-Append + +[Paste] +nameid=Paste +id=Edit-Paste + +[Paste continue] +nameid=Paste Continue +id=Edit-Paste-Continue + +[Select All] +nameid=Select All +id=Edit-SelectAll + +[Edit Options] +nameid=Options +id=Dlg-Options + +[edit find] +nameid=Find +id=Dlg-Find + +[clear display] +nameid=Clear Display +id=Clear-Display + +[clear all] +nameid=Clear All +id=Clear-Buffer + +[soft terminal reset] +nameid=Soft Terminal Reset +id=Power-On-Reset + + +[Transfer] +Nameid=Transfer +id=4073 +Parent=1 +item1=send +item2=receive + +[send] +nameid=Send File to Host +id=Dlg-Upload + +[receive] +nameid=Receive File from Host +id=Dlg-Download + + +[Fonts] +Nameid=Fonts +id=4074 +Parent=1 +item1=next larger +item2=next smaller +item3=choose font +item4=maximize font + +[next larger] +nameid=next larger font +id=Font-Larger + +[next smaller] +nameid=Next smaller font +id=Font-Smaller + +[choose font] +nameid=font +id=Dlg-Font-Select + +[maximize font] +nameid=Maximize font +id=Maximize-Font + + +[options] +nameid=Options +id=4075 +Parent=1 +item1=global options +item2=api settings +item3=separator +item4=keyboard mapping +item5=quick keys +item6=separator +item7=session properties + +[global options] +nameid=Global Options +id=Dlg-Global + +[api settings] +nameid=API Settings +id=Dlg-API-Settings + +[keyboard mapping] +nameid=Keyboard Mapping +id=Dlg-Keyboard-Mapper + +[quick keys] +nameid=Quick Keys +id=Dlg-Quick-Key-Editor + +[session properties] +nameid=session Properties +id=Dlg-Edit-Session-Profile + + +[tools] +nameid=Tools +id=4076 +Parent=1 +item1=Macro Submenu +item2=Quick Script Submenu +item3=separator +item4=Customizetoolbar +item5=Customizemenu +item6=Customizesessionproperties + +[Macro Submenu] +nameid=Macro +id=4177 +Parent=1 +item1=edit macro +item2=run macro +item3=separator +item4=start recording +item5=pause recording +item6=resume recording +item7=stop recording +item8=cancel recording + +[Edit macro] +nameid=Macro Edit +id=Dlg-Edit-Macro + +[run macro] +nameid=run +id=Dlg-Run-Macro + +[start recording] +nameid=Start Recording +id=Record-Macro + +[pause recording] +nameid=Pause Recording +id=Toggle-Recording-Pause + +[resume recording] +nameid=Resume Recording +id=Resume-Recording-Macro + +[stop recording] +nameid=Stop Recording +id=End-Recording + +[cancel recording] +nameid=Cancel Recording +id=Cancel-Macro-Recording + +[Quick Script Submenu] +nameid=Quick Script +id=4091 +Parent=1 +item1=edit qs +item2=run qs +item3=stop qs +item4=separator +item5=start recording qs +item6=pause recording qs +item7=resume recording qs +item8=stop recording qs +item9=cancel recording qs + +[Customizetoolbar] +nameid=Customize Toolbar +id=Dlg-Toolbars + +[Customizemenu] +nameid=Customize Menu +id=Dlg-Customize-Menus + +[CustomizeSessionProperties] +nameid=Customize Session Properties +id=Dlg-Customize-Session-Properties + +[Edit qs] +nameid=Edit +id=Dlg-QuickScript-Editor + +[run qs] +nameid=Run +id=Dlg-Run-QuickScript + +[stop qs] +nameid=Stop +id=Stop-QuickScript + +[start recording qs] +nameid=Start Recording +id=Record-QuickScript + +[pause recording qs] +nameid=Pause Recording +id=Pause-Recording-QuickScript + +[resume recording qs] +nameid=Resume Recording +id=Resume-Recording-QuickScript + +[stop recording qs] +nameid=Stop Recording +id=Stop-Recording-QuickScript + +[cancel recording qs] +nameid=Cancel Recording +id=Cancel-Recording-QuickScript + + +[view] +nameid=view +id=4178 +Parent=1 +item1=hotspots +item2=row and column indicator +item3=cross hair cursor +item4=full screen + +[hotspots] +nameid=Hotspots +id=Toggle-View-Hotspots + +[Row and Column Indicator] +nameid=Row and Column Indicator +id=Toggle-Row-And-Column-Indicator + +[cross hair cursor] +nameid=Cross-Hair Cursor +id=Toggle-CrossHair-Cursor + +[full screen] +nameid=Full Screen +id=Toggle-Full-Screen + + +[window] +nameid=Window +id=4179 +Parent=1 +item1=cascade +item2=next session +item3=previous session +item4=separator +item5=session window list + +[cascade] +nameid=Cascade +id=Cascade-Session-Windows + +[next session] +nameid=next session +id=Next-Session + +[previous session] +nameid=previous session +id=Prev-Session + +[session window list] +name= +id=4142 + + +[help] +nameid=Help +id=4180 +Parent=1 +item1=contents +item2=separator +item3=about + +[contents] +nameid=Contents +id=Help-Index + +[about] +nameid=about +id=Help-About diff --git a/playbook/shopfloor-setup/Shopfloor/csf/IBM_qks.lnk b/playbook/shopfloor-setup/Shopfloor/csf/IBM_qks.lnk new file mode 100755 index 0000000..973732c Binary files /dev/null and b/playbook/shopfloor-setup/Shopfloor/csf/IBM_qks.lnk differ diff --git a/playbook/shopfloor-setup/Shopfloor/csf/Profile/IBM_qks.hep b/playbook/shopfloor-setup/Shopfloor/csf/Profile/IBM_qks.hep new file mode 100755 index 0000000..68fc23a --- /dev/null +++ b/playbook/shopfloor-setup/Shopfloor/csf/Profile/IBM_qks.hep @@ -0,0 +1,106 @@ +[PROFILE] +Session Properties=Default 3270 +Menu=Default 3270 +Auto Run Macro Delay Time=1 +Gateway Name=ibmqks.ae.ge.com +Session Keep Alive Interval=600 +PC Keyboard Type=5 +IND$FILE Parms=0,0,1,1,0,0,0,257,253,2048,200,430,430,0,1252,"IND$FILE","","","",0 +SSL/TLS Current User Certificate=1 +Window Title=%s - %p (%h) +WinInfo Default Restored=0 0 0 0 70 405 822 595 0 0 0=20 10 0 0 400 0 0 0 0 3 2 2 0 HE_Bitmap| 0 0 +WinInfo Default Maximized=0 0 0 0 -8 -8 1936 1048 0 0 0=26 12 0 0 400 0 0 0 0 3 2 2 0 HE_Bitmap| 0 0 +Print Header2=Page: %# Document Name: %F +QuickKey=Default +Never Run Profile=Off +Profile File Version=200 +Space Allocation Parms=0,0,0,0 +Kerberos Use Hummingbird Kerberos=On +Cryptographic Mode=1 +IP Port=992 +Security Option=2 +SSL/TLS User Cipher Suites=DEFAULT +[HEBar-59440] +sizeFloatCX=505 +sizeFloatCY=28 +[Toolbars Layout-Summary] +Bars=3 +ScreenCX=1920 +ScreenCY=1080 +[Session Options] +Last Selected Page=139 +[Toolbars] +ToolBarVersion=6 +bar0=Default 3270 ToolBar.tb3 +[Toolbars Layout-Bar0] +BarID=59393 +[Toolbars Layout-Bar1] +BarID=59419 +Bars=3 +Bar#0=0 +Bar#1=59440 +Bar#2=0 +[Toolbars Layout-Bar2] +BarID=59440 +XPos=-2 +YPos=-2 +Docking=1 +MRUDockID=59419 +MRUDockLeftPos=-2 +MRUDockTopPos=-2 +MRUDockRightPos=512 +MRUDockBottomPos=26 +MRUFloatStyle=8196 +MRUFloatXPos=-1 +MRUFloatYPos=1025 +[Hotspots] +ShowTips=1 +[ShortcutReplacers] +Size=0 +[Site Info] +EnableCache=Yes +RefreshCacheOnConnect=0 +AlwaysUploadUsing=0 +AlwaysDownloadUsing=0 +ProfileVersion=5 +SecurityType=0 +Port=21 +SSL Implicit Port=990 +Directory2=000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +SFTPProxyAddress=localhost +SFTPProxyPort=0 +SFTPWriteSize=256 +GssApiServiceName=host +GssApiProtLevel=P +GssApiIssueCCC=No +GssApiPBSZ=16000 +GssApiKerbClient=0 +GssApiUseHMS2MIT=No +PassiveMode=2 +AllowServer2ServerTransfer=0 +KeepAlive=No +AutoDetectServerType=Yes +KeepAliveTime=0 +FirewallPort=21 +FirewallType=0 +NetworkTimeout=120 +ConvertGMTTime=0 +ResolveLinks=1 +RootToInitialDir=0 +ChangeDirChangesRoot=0 +DropVmsVersion=0 +LocalHourOffset=0 +LocalMinuteOffset=0 +TemporaryProfile=0 +SSLVersion=3 +SSLReuse=0 +SSLUserCertificateMode=0 +SSLCloseOnNegotiateionFail=0 +SSLAcceptServerSelfSignedCertificates=Yes +SSLAcceptServerUnverifiedCertificates=Yes +SSLDataChannelProtected=Yes +SSLCryptographicMode=1 +AllowResume=No +EPSV4=0 +[Stats] +NumVisits=0 diff --git a/playbook/shopfloor-setup/Shopfloor/csf/Profile/WJ Shopfloor.hep b/playbook/shopfloor-setup/Shopfloor/csf/Profile/WJ Shopfloor.hep new file mode 100755 index 0000000..d380778 --- /dev/null +++ b/playbook/shopfloor-setup/Shopfloor/csf/Profile/WJ Shopfloor.hep @@ -0,0 +1,122 @@ +[PROFILE] +Session Terminal Type=2 +Session Properties=Default VT +Menu=Default VT +Auto Run Macro Delay Time=1 +Gateway Name=wjfms3.apps.wlm.geaerospace.net +Session Keep Alive Interval=600 +PC Keyboard Type=5 +Tabstops='8 16 24 32 40 48 56 64 72 ' +VT BG Colors='1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 8 7 7 9 15 14 10 2 10 ' +Default VT Recv Path=$MYDOCUMENTS$\ +SSL/TLS Version=3 +SSL/TLS Current User Certificate=1 +Window Title=%s - %p (%h) +WinInfo Default Restored=0 0 0 0 176 21 892 619 0 0 0=-17 0 0 0 400 0 0 0 0 3 2 2 0 Courier New| 0 0 +WinInfo Default Maximized=0 0 0 0 -8 -8 1634 1066 0 0 0=30 15 0 0 700 0 0 0 1 3 2 2 0 HE_Bitmap| 0 0 +Print Header2=Page: %# Document Name: %F +QuickKey=Super Key F6 +Never Run Profile=Off +dwVTFlags1=29369344 +VT FG Colors='7 7 9 15 14 10 2 10 0 1 2 3 4 5 15 7 8 9 10 11 12 13 14 15 15 0 0 1 0 0 0 0 0 0 0 ' +Connect Timeout=60 +Disable Secure Connection Warning=Yes +Profile File Version=200 +Cryptographic Mode=1 +Display RowCol Indicator=Off +VT Scrollback Save Attribs=Off +VT True Display Mode=1 +Status Line=1 +Kerberos Use Hummingbird Kerberos=On +Keyboard=Office +Auto Start Quick-Key=shopfloor.ebs +On Disconnect=0 +Allow sleep while connected=0 +Toolbar Scheme Name=Default VT +Save Profile on Window Close=Off +Toolbar Scheme Modified=Default VT:30026745--1815298816 +Save Toolbar Info on Exit=Off +Save Mode=0 +[Session Options] +Last Selected Page=4000 +[HEBar-59440] +[Toolbars Layout-Summary] +Bars=3 +ScreenCX=1600 +ScreenCY=1200 +[Toolbars] +ToolBarVersion=5 +bar0=Default VT ToolBar.tbv +[Toolbars Layout-Bar0] +BarID=59393 +Visible=0 +[Toolbars Layout-Bar1] +BarID=59419 +Bars=3 +Bar#0=0 +Bar#1=59440 +Bar#2=0 +[Toolbars Layout-Bar2] +BarID=59440 +XPos=-2 +YPos=-2 +Docking=1 +MRUDockID=59419 +MRUDockLeftPos=-2 +MRUDockTopPos=-2 +MRUDockRightPos=541 +MRUDockBottomPos=26 +MRUFloatStyle=8196 +MRUFloatXPos=-2147483648 +MRUFloatYPos=0 +[Hotspots] +ShowTips=1 +[ShortcutReplacers] +Size=0 +[Site Info] +EnableCache=Yes +RefreshCacheOnConnect=0 +AlwaysUploadUsing=0 +AlwaysDownloadUsing=0 +ProfileVersion=5 +SecurityType=0 +Port=21 +SSL Implicit Port=990 +Directory2=000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +SFTPProxyAddress=localhost +SFTPProxyPort=0 +SFTPWriteSize=256 +GssApiServiceName=host +GssApiProtLevel=P +GssApiIssueCCC=No +GssApiPBSZ=16000 +GssApiKerbClient=0 +GssApiUseHMS2MIT=No +PassiveMode=2 +AllowServer2ServerTransfer=0 +KeepAlive=No +AutoDetectServerType=Yes +KeepAliveTime=0 +FirewallPort=21 +FirewallType=0 +NetworkTimeout=120 +ConvertGMTTime=0 +ResolveLinks=1 +RootToInitialDir=0 +ChangeDirChangesRoot=0 +DropVmsVersion=0 +LocalHourOffset=0 +LocalMinuteOffset=0 +TemporaryProfile=0 +SSLVersion=3 +SSLReuse=0 +SSLUserCertificateMode=0 +SSLCloseOnNegotiateionFail=0 +SSLAcceptServerSelfSignedCertificates=Yes +SSLAcceptServerUnverifiedCertificates=Yes +SSLDataChannelProtected=Yes +SSLCryptographicMode=1 +AllowResume=No +EPSV4=0 +[Stats] +NumVisits=0 diff --git a/playbook/shopfloor-setup/Shopfloor/csf/Profile/WJ_Office.hep b/playbook/shopfloor-setup/Shopfloor/csf/Profile/WJ_Office.hep new file mode 100755 index 0000000..79f4546 --- /dev/null +++ b/playbook/shopfloor-setup/Shopfloor/csf/Profile/WJ_Office.hep @@ -0,0 +1,122 @@ +[PROFILE] +Session Terminal Type=2 +Session Properties=Default VT +Menu=Default VT +Auto Run Macro Delay Time=1 +Gateway Name=wjfms3.apps.wlm.geaerospace.net +Session Keep Alive Interval=600 +PC Keyboard Type=5 +Tabstops='8 16 24 32 40 48 56 64 72 ' +VT BG Colors='1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 8 7 7 9 15 14 10 2 10 ' +Default VT Recv Path=$MYDOCUMENTS$\ +SSL/TLS Version=3 +SSL/TLS Current User Certificate=1 +Window Title=%s - %p (%h) +WinInfo Default Restored=0 0 0 0 22 22 892 619 0 0 0=20 10 0 0 400 0 0 0 0 3 2 2 0 HE_Bitmap| 0 0 +WinInfo Default Maximized=0 0 0 0 -8 -8 1634 1066 0 0 0=30 15 0 0 700 0 0 0 1 3 2 2 0 HE_Bitmap| 0 0 +Print Header2=Page: %# Document Name: %F +QuickKey=Super Key F6 +Never Run Profile=Off +dwVTFlags1=29369344 +VT FG Colors='7 7 9 15 14 10 2 10 0 1 2 3 4 5 15 7 8 9 10 11 12 13 14 15 15 0 0 1 0 0 0 0 0 0 0 ' +Connect Timeout=60 +Disable Secure Connection Warning=Yes +Profile File Version=200 +Cryptographic Mode=1 +Display RowCol Indicator=Off +VT Scrollback Save Attribs=Off +VT True Display Mode=1 +Status Line=1 +Kerberos Use Hummingbird Kerberos=On +Keyboard=Office +Auto Start Quick-Key=Office.ebs +On Disconnect=0 +Allow sleep while connected=0 +Toolbar Scheme Name=Default VT +Save Profile on Window Close=Off +Toolbar Scheme Modified=Default VT:30026745--1815298816 +Save Toolbar Info on Exit=Off +Save Mode=0 +[Session Options] +Last Selected Page=4000 +[HEBar-59440] +[Toolbars Layout-Summary] +Bars=3 +ScreenCX=1600 +ScreenCY=1200 +[Toolbars] +ToolBarVersion=5 +bar0=Default VT ToolBar.tbv +[Toolbars Layout-Bar0] +BarID=59393 +Visible=0 +[Toolbars Layout-Bar1] +BarID=59419 +Bars=3 +Bar#0=0 +Bar#1=59440 +Bar#2=0 +[Toolbars Layout-Bar2] +BarID=59440 +XPos=-2 +YPos=-2 +Docking=1 +MRUDockID=59419 +MRUDockLeftPos=-2 +MRUDockTopPos=-2 +MRUDockRightPos=541 +MRUDockBottomPos=26 +MRUFloatStyle=8196 +MRUFloatXPos=-2147483648 +MRUFloatYPos=0 +[Hotspots] +ShowTips=1 +[ShortcutReplacers] +Size=0 +[Site Info] +EnableCache=Yes +RefreshCacheOnConnect=0 +AlwaysUploadUsing=0 +AlwaysDownloadUsing=0 +ProfileVersion=5 +SecurityType=0 +Port=21 +SSL Implicit Port=990 +Directory2=000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +SFTPProxyAddress=localhost +SFTPProxyPort=0 +SFTPWriteSize=256 +GssApiServiceName=host +GssApiProtLevel=P +GssApiIssueCCC=No +GssApiPBSZ=16000 +GssApiKerbClient=0 +GssApiUseHMS2MIT=No +PassiveMode=2 +AllowServer2ServerTransfer=0 +KeepAlive=No +AutoDetectServerType=Yes +KeepAliveTime=0 +FirewallPort=21 +FirewallType=0 +NetworkTimeout=120 +ConvertGMTTime=0 +ResolveLinks=1 +RootToInitialDir=0 +ChangeDirChangesRoot=0 +DropVmsVersion=0 +LocalHourOffset=0 +LocalMinuteOffset=0 +TemporaryProfile=0 +SSLVersion=3 +SSLReuse=0 +SSLUserCertificateMode=0 +SSLCloseOnNegotiateionFail=0 +SSLAcceptServerSelfSignedCertificates=Yes +SSLAcceptServerUnverifiedCertificates=Yes +SSLDataChannelProtected=Yes +SSLCryptographicMode=1 +AllowResume=No +EPSV4=0 +[Stats] +NumVisits=0 diff --git a/playbook/shopfloor-setup/Shopfloor/csf/Profile/mmcs.hep b/playbook/shopfloor-setup/Shopfloor/csf/Profile/mmcs.hep new file mode 100755 index 0000000..d569cb6 --- /dev/null +++ b/playbook/shopfloor-setup/Shopfloor/csf/Profile/mmcs.hep @@ -0,0 +1,128 @@ +[PROFILE] +Session Terminal Type=2 +Never Run Profile=Off +Session Properties=Default VT +Menu=Default VT +Auto Run Macro Delay Time=1 +Gateway Name=wjfms3.apps.wlm.geaerospace.net +Session Keep Alive Interval=600 +dwVTFlags1=29369344 +PC Keyboard Type=5 +Keyboard=ATM-VT420 +Tabstops='8 16 24 32 40 48 56 64 72 80 88 96 104 112 120 128 136 144 152 160 168 176 184 192 200 208 216 224 232 240 248 256 264 272 280 288 296 ' +Default VT Recv Path=$MYDOCUMENTS$\ +SSL/TLS Version=3 +SSL/TLS Current User Certificate=1 +Window Title=%s - %p (%h) +WinInfo Default Restored=0 0 0 0 10 10 757 491 0 0 0=16 9 0 0 400 0 0 0 0 3 2 2 0 HE_Bitmap| 0 0 +WinInfo Default Maximized=0 0 0 0 -8 -8 1382 744 0 0 0=26 13 0 0 400 0 0 0 0 3 2 2 0 HE_Bitmap| 0 0 +Window State=2 +Use Specific PRTSCR Printer=On +PrtScr DevMode=1025,1024,156,1497,33565,1,256,2794,1240,0,1,4,300,1,1,300,1,0,0,1,0,0,0,0,ZDesigner TLP 3842,User defined +PrtScr Driver=winspool +PrtScr Device=ZDesigner TLP 3842 +PrtScr Output=LPT1: +Print Header2=Page: %# Document Name: %F +Use Specific VT Printer=On +VT DevMode=1025,1024,156,1497,323347,1,256,190,914,0,1,4,300,1,1,300,1,0,0,1,0,0,0,0,ZDesigner TLP 3842,User defined +VT Driver=winspool +VT Device=ZDesigner TLP 3842 +VT Output=USB001 +Printer Target=1 +VT Printer Bypass Windows Print Driver=Off +VT Printer Use Default Font=On +VT Printer Margins Left=320 +VT Printer Margins Right=0 +VT Printer Margins Bottom=0 +VT Printer Margins Top=0 +QuickKey=ID +Auto Start Quick-Key=mmcs.ebs +VT Auto Wrap=On +Profile File Version=200 +Kerberos Use Hummingbird Kerberos=On +Cryptographic Mode=1 +Save Mode=0 +[HEBar-59440] +sizeFloatCX=534 +sizeFloatCY=28 +[Toolbars Layout-Summary] +Bars=3 +ScreenCX=1366 +ScreenCY=768 +[Session Options] +Last Selected Page=4000 +[Toolbars] +ToolBarVersion=5 +bar0=Default VT ToolBar.tbv +[Toolbars Layout-Bar0] +BarID=59393 +[Toolbars Layout-Bar1] +BarID=59419 +Bars=3 +Bar#0=0 +Bar#1=59440 +Bar#2=0 +[Toolbars Layout-Bar2] +BarID=59440 +XPos=-2 +YPos=-2 +Docking=1 +MRUDockID=59419 +MRUDockLeftPos=-2 +MRUDockTopPos=-2 +MRUDockRightPos=541 +MRUDockBottomPos=26 +MRUFloatStyle=8196 +MRUFloatXPos=-1 +MRUFloatYPos=0 +[Hotspots] +ShowTips=1 +[ShortcutReplacers] +Size=0 +[Site Info] +EnableCache=Yes +RefreshCacheOnConnect=0 +AlwaysUploadUsing=0 +AlwaysDownloadUsing=0 +ProfileVersion=5 +SecurityType=0 +Port=21 +SSL Implicit Port=990 +Directory2=000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +SFTPProxyAddress=localhost +SFTPProxyPort=0 +SFTPWriteSize=256 +GssApiServiceName=host +GssApiProtLevel=P +GssApiIssueCCC=No +GssApiPBSZ=16000 +GssApiKerbClient=0 +GssApiUseHMS2MIT=No +PassiveMode=2 +AllowServer2ServerTransfer=0 +KeepAlive=No +AutoDetectServerType=Yes +KeepAliveTime=0 +FirewallPort=21 +FirewallType=0 +NetworkTimeout=120 +ConvertGMTTime=0 +ResolveLinks=1 +RootToInitialDir=0 +ChangeDirChangesRoot=0 +DropVmsVersion=0 +LocalHourOffset=0 +LocalMinuteOffset=0 +TemporaryProfile=0 +SSLVersion=1 +SSLReuse=0 +SSLUserCertificateMode=0 +SSLCloseOnNegotiateionFail=0 +SSLAcceptServerSelfSignedCertificates=Yes +SSLAcceptServerUnverifiedCertificates=Yes +SSLDataChannelProtected=Yes +SSLCryptographicMode=1 +AllowResume=No +EPSV4=0 +[Stats] +NumVisits=0 diff --git a/playbook/shopfloor-setup/Shopfloor/csf/WJ Shopfloor.lnk b/playbook/shopfloor-setup/Shopfloor/csf/WJ Shopfloor.lnk new file mode 100755 index 0000000..1469565 Binary files /dev/null and b/playbook/shopfloor-setup/Shopfloor/csf/WJ Shopfloor.lnk differ diff --git a/playbook/shopfloor-setup/Shopfloor/csf/WJ_Office.lnk b/playbook/shopfloor-setup/Shopfloor/csf/WJ_Office.lnk new file mode 100755 index 0000000..c5103ea Binary files /dev/null and b/playbook/shopfloor-setup/Shopfloor/csf/WJ_Office.lnk differ diff --git a/playbook/shopfloor-setup/Shopfloor/csf/mmcs.lnk b/playbook/shopfloor-setup/Shopfloor/csf/mmcs.lnk new file mode 100755 index 0000000..02046e8 Binary files /dev/null and b/playbook/shopfloor-setup/Shopfloor/csf/mmcs.lnk differ diff --git a/playbook/shopfloor-setup/Standard/01-eDNC.ps1 b/playbook/shopfloor-setup/Standard/01-eDNC.ps1 new file mode 100644 index 0000000..58a94f6 --- /dev/null +++ b/playbook/shopfloor-setup/Standard/01-eDNC.ps1 @@ -0,0 +1,84 @@ +# 01-eDNC.ps1 — Install eDNC and MarkZebra, deploy custom eMxInfo.txt (Standard) + +Write-Host "=== eDNC / MarkZebra Setup ===" + +$edncDir = "C:\Enrollment\shopfloor-setup\Standard\eDNC" + +if (-not (Test-Path $edncDir)) { + Write-Warning "eDNC folder not found at $edncDir — skipping." + exit 0 +} + +# --- Find installers --- +$edncMsi = Get-ChildItem -Path $edncDir -Filter "eDNC-*.msi" | Select-Object -First 1 +$markMsi = Get-ChildItem -Path $edncDir -Filter "MarkZebra.msi" | Select-Object -First 1 +$emxInfo = Join-Path $edncDir "eMxInfo.txt" + +# --- 1. Install eDNC --- +if ($edncMsi) { + Write-Host "Installing eDNC: $($edncMsi.Name)..." + $p = Start-Process -FilePath "msiexec.exe" -ArgumentList "/i `"$($edncMsi.FullName)`" /qn /norestart LAUNCHNTLARS=false" -Wait -PassThru + Write-Host " eDNC exit code: $($p.ExitCode)" +} else { + Write-Warning "eDNC installer not found in $edncDir (expected eDNC-*.msi)" +} + +# --- 2. Install MarkZebra --- +if ($markMsi) { + Write-Host "Installing MarkZebra: $($markMsi.Name)..." + $p = Start-Process -FilePath "msiexec.exe" -ArgumentList "/i `"$($markMsi.FullName)`" /qn /norestart LAUNCHNTLARS=false" -Wait -PassThru + Write-Host " MarkZebra exit code: $($p.ExitCode)" +} else { + Write-Warning "MarkZebra installer not found in $edncDir (expected MarkZebra.msi)" +} + +# --- 3. Mirror x86 installs to 64-bit Program Files (app uses hardcoded paths) --- +# MarkZebra.exe references \Mark\, mxTransactionDll.dll references \Dnc\Server Files\ +$copies = @( + @{ Src = "C:\Program Files (x86)\Mark"; Dst = "C:\Program Files\Mark" }, + @{ Src = "C:\Program Files (x86)\Dnc"; Dst = "C:\Program Files\Dnc" } +) +foreach ($c in $copies) { + if (Test-Path $c.Src) { + if (-not (Test-Path $c.Dst)) { + New-Item -Path $c.Dst -ItemType Directory -Force | Out-Null + } + Copy-Item -Path "$($c.Src)\*" -Destination $c.Dst -Recurse -Force + Write-Host " Copied $($c.Src) -> $($c.Dst)" + } +} + +# --- 4. Set DNC site and MarkZebra config --- +$regBase = "HKLM\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC" +reg add "$regBase\General" /v Site /t REG_SZ /d WestJefferson /f | Out-Null +Write-Host " DNC site set to WestJefferson." + +reg add "$regBase\Mark" /v "Port Id" /t REG_SZ /d COM1 /f | Out-Null +reg add "$regBase\Mark" /v "Baud" /t REG_SZ /d 9600 /f | Out-Null +reg add "$regBase\Mark" /v "Parity" /t REG_SZ /d None /f | Out-Null +reg add "$regBase\Mark" /v "Data Bits" /t REG_SZ /d 8 /f | Out-Null +reg add "$regBase\Mark" /v "Stop Bits" /t REG_SZ /d 1 /f | Out-Null +reg add "$regBase\Mark" /v "Message Type" /t REG_SZ /d V /f | Out-Null +reg add "$regBase\Mark" /v "Debug" /t REG_SZ /d ON /f | Out-Null +reg add "$regBase\Mark" /v "MarkerType" /t REG_SZ /d Mark2D /f | Out-Null +reg add "$regBase\Mark" /v "DncPatterns" /t REG_SZ /d NO /f | Out-Null +Set-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\Mark" -Name "CageCode" -Value "" -Force +Write-Host " MarkZebra registry configured." + +# --- 5. Deploy custom eMxInfo.txt to both Program Files paths --- +if (Test-Path $emxInfo) { + $dest86 = "C:\Program Files (x86)\DNC\Server Files" + $dest64 = "C:\Program Files\DNC\Server Files" + + foreach ($dest in @($dest86, $dest64)) { + if (-not (Test-Path $dest)) { + New-Item -Path $dest -ItemType Directory -Force | Out-Null + } + Copy-Item -Path $emxInfo -Destination (Join-Path $dest "eMxInfo.txt") -Force + Write-Host " eMxInfo.txt -> $dest" + } +} else { + Write-Warning "eMxInfo.txt not found at $emxInfo" +} + +Write-Host "=== eDNC / MarkZebra Setup Complete ===" diff --git a/playbook/shopfloor-setup/WaxAndTrace/01-Setup-WaxAndTrace.ps1 b/playbook/shopfloor-setup/WaxAndTrace/01-Setup-WaxAndTrace.ps1 new file mode 100644 index 0000000..3851da3 --- /dev/null +++ b/playbook/shopfloor-setup/WaxAndTrace/01-Setup-WaxAndTrace.ps1 @@ -0,0 +1,14 @@ +# 01-Setup-WaxAndTrace.ps1 — Wax and Trace-specific setup (runs after Shopfloor baseline) + +Write-Host "=== Wax and Trace Setup ===" + +# --- Add Wax and Trace credentials --- +# cmdkey /generic:wax-server /user:domain\waxuser /pass:password + +# --- Install Wax and Trace applications --- +# Start-Process msiexec.exe -ArgumentList '/i "C:\Enrollment\shopfloor-setup\WaxAndTrace\WaxApp.msi" /qn' -Wait + +# --- Wax and Trace configuration --- +# Set-ItemProperty -Path "HKLM:\SOFTWARE\CompanyName" -Name "PCType" -Value "WaxAndTrace" + +Write-Host "=== Wax and Trace Setup Complete ===" diff --git a/playbook/shopfloor-setup/backup_lockdown.bat b/playbook/shopfloor-setup/backup_lockdown.bat new file mode 100644 index 0000000..3d1781a --- /dev/null +++ b/playbook/shopfloor-setup/backup_lockdown.bat @@ -0,0 +1,54 @@ +@echo off +title Shopfloor Backup Lockdown + +:: Self-elevate to administrator +net session >nul 2>&1 +if %errorlevel% neq 0 ( + echo Requesting administrator privileges... + powershell -Command "Start-Process '%~f0' -Verb RunAs" + exit /b +) + +echo. +echo ======================================== +echo Shopfloor Backup Lockdown +echo ======================================== +echo. + +:: Run SFLD autologon script first +echo Running SFLD autologon script... +"C:\Program Files\PowerShell\7\pwsh.exe" -NoProfile -ExecutionPolicy Bypass -File "C:\Program Files\Sysinternals\sfld_autologon.ps1" + +echo. +echo Waiting 10 seconds... +ping -n 11 127.0.0.1 >nul + +:: Discover the EnterpriseMgmt enrollment GUID +for /f "delims=" %%G in ('powershell -NoProfile -Command "$t = Get-ScheduledTask | Where-Object { $_.TaskPath -match '\\Microsoft\\EnterpriseMgmt\\' -and $_.TaskName -match 'Schedule #1' }; if ($t) { $t.TaskPath -replace '.*EnterpriseMgmt\\([^\\]+)\\.*','$1' | Select-Object -First 1 } else { '' }"') do set GUID=%%G + +if not defined GUID ( + echo ERROR: No EnterpriseMgmt enrollment GUID found. + echo The device may not be enrolled in MDM yet. + pause + exit /b 1 +) + +echo Enrollment GUID: %GUID% +echo. + +echo Running EnterpriseMgmt Schedule #1... +schtasks /run /tn "\Microsoft\EnterpriseMgmt\%GUID%\Schedule #1 created by enrollment client" +echo Waiting 30 seconds... +ping -n 31 127.0.0.1 >nul + +echo Running EnterpriseMgmt Schedule #2... +schtasks /run /tn "\Microsoft\EnterpriseMgmt\%GUID%\Schedule #2 created by enrollment client" +echo Waiting 90 seconds... +ping -n 91 127.0.0.1 >nul + +echo Running EnterpriseMgmt Schedule #3... +schtasks /run /tn "\Microsoft\EnterpriseMgmt\%GUID%\Schedule #3 created by enrollment client" + +echo. +echo Lockdown complete. +pause diff --git a/playbook/startnet.cmd b/playbook/startnet.cmd index 0116090..3439fc2 100644 --- a/playbook/startnet.cmd +++ b/playbook/startnet.cmd @@ -86,7 +86,7 @@ echo 2. Wax and Trace echo 3. Keyence echo 4. Genspect echo 5. Display -echo 6. Shopfloor (General) +echo 6. Standard echo. set PCTYPE= set /p pctype_choice=Enter your choice (1-6): @@ -95,7 +95,7 @@ if "%pctype_choice%"=="2" set PCTYPE=WaxAndTrace if "%pctype_choice%"=="3" set PCTYPE=Keyence if "%pctype_choice%"=="4" set PCTYPE=Genspect if "%pctype_choice%"=="5" set PCTYPE=Display -if "%pctype_choice%"=="6" set PCTYPE=Shopfloor +if "%pctype_choice%"=="6" set PCTYPE=Standard if "%PCTYPE%"=="" goto pctype_menu REM --- Display sub-type selection --- @@ -245,13 +245,13 @@ if not "%DISPLAYTYPE%"=="" echo %DISPLAYTYPE%> W:\Enrollment\display-type.txt copy /Y "Y:\shopfloor-setup\Run-ShopfloorSetup.ps1" "W:\Enrollment\Run-ShopfloorSetup.ps1" REM --- Always copy Shopfloor baseline scripts --- mkdir W:\Enrollment\shopfloor-setup 2>NUL +copy /Y "Y:\shopfloor-setup\backup_lockdown.bat" "W:\Enrollment\shopfloor-setup\backup_lockdown.bat" if exist "Y:\shopfloor-setup\Shopfloor" ( mkdir W:\Enrollment\shopfloor-setup\Shopfloor 2>NUL xcopy /E /Y /I "Y:\shopfloor-setup\Shopfloor" "W:\Enrollment\shopfloor-setup\Shopfloor\" echo Copied Shopfloor baseline setup files. ) REM --- Copy type-specific scripts on top of baseline --- -if "%PCTYPE%"=="Shopfloor" goto pctype_done if exist "Y:\shopfloor-setup\%PCTYPE%" ( mkdir "W:\Enrollment\shopfloor-setup\%PCTYPE%" 2>NUL xcopy /E /Y /I "Y:\shopfloor-setup\%PCTYPE%" "W:\Enrollment\shopfloor-setup\%PCTYPE%\" diff --git a/sync_hardware_models.py b/sync_hardware_models.py new file mode 100644 index 0000000..1470a20 --- /dev/null +++ b/sync_hardware_models.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +"""Sync HardwareDriver.json and user_selections.json across all PXE image types. + +Reads all HardwareDriver.json files, builds a unified driver catalog, +then updates each image to include all known hardware models. +Run after adding new driver packs to the shared Out-of-box Drivers directory. +""" +import json +import os +import sys +from pathlib import Path +from collections import OrderedDict + +WINPEAPPS = Path("/srv/samba/winpeapps") +SHARED_DRIVERS = WINPEAPPS / "_shared" / "Out-of-box Drivers" + + +def normalize_entry(entry): + """Normalize a HardwareDriver.json entry to a consistent format.""" + norm = {} + norm["manufacturer"] = entry.get("manufacturer", "Dell") + norm["product"] = entry.get("product") or entry.get("manufacturerfriendlyname", "Dell") + norm["family"] = entry.get("family", "") + norm["modelswminame"] = entry.get("modelswminame") or entry.get("models", "") + norm["modelsfriendlyname"] = entry.get("modelsfriendlyname", "") + norm["fileName"] = entry.get("fileName") or entry.get("FileName", "") + norm["destinationDir"] = entry.get("destinationDir") or entry.get("DestinationDir", "") + norm["url"] = entry.get("url", "") + norm["hash"] = entry.get("hash", "") + norm["size"] = entry.get("size", 0) + norm["modifiedDate"] = entry.get("modifiedDate", "0001-01-01T00:00:00") + norm["osId"] = entry.get("osId", "") + norm["imagedisk"] = entry.get("imagedisk", 0) + return norm + + +def merge_os_ids(a, b): + """Merge two osId strings (e.g., '18' + '20,21' -> '18,20,21').""" + ids = set() + for oid in [a, b]: + for part in str(oid).split(","): + part = part.strip() + if part: + ids.add(part) + return ",".join(sorted(ids, key=lambda x: int(x) if x.isdigit() else 0)) + + +def check_driver_exists(entry): + """Check if the driver zip actually exists in the shared directory.""" + dest = entry["destinationDir"] + dest = dest.replace("*destinationdir*", "") + dest = dest.lstrip("\\") + dest = dest.replace("\\", "/") + # Strip leading path components that are already in SHARED_DRIVERS + for prefix in ["Deploy/Out-of-box Drivers/", "Out-of-box Drivers/"]: + if dest.startswith(prefix): + dest = dest[len(prefix):] + break + dest = dest.lstrip("/") + zip_path = SHARED_DRIVERS / dest / entry["fileName"] + return zip_path.exists() + + +def main(): + print("=== PXE Hardware Model Sync ===") + print() + + # Step 1: Build unified catalog from all images + print("Reading driver catalogs...") + catalog = OrderedDict() + + image_dirs = sorted( + [d for d in WINPEAPPS.iterdir() if d.is_dir() and not d.name.startswith("_")] + ) + + for img_dir in image_dirs: + hw_file = img_dir / "Deploy" / "Control" / "HardwareDriver.json" + if not hw_file.exists(): + continue + with open(hw_file) as f: + entries = json.load(f) + print(" Read {} entries from {}".format(len(entries), img_dir.name)) + for entry in entries: + norm = normalize_entry(entry) + key = (norm["family"], norm["fileName"]) + if key in catalog: + catalog[key]["osId"] = merge_os_ids( + catalog[key]["osId"], norm["osId"] + ) + # Prefer longer/more complete model names + if len(norm["modelswminame"]) > len(catalog[key]["modelswminame"]): + catalog[key]["modelswminame"] = norm["modelswminame"] + if len(norm["modelsfriendlyname"]) > len( + catalog[key]["modelsfriendlyname"] + ): + catalog[key]["modelsfriendlyname"] = norm["modelsfriendlyname"] + else: + catalog[key] = norm + + unified = list(catalog.values()) + print() + print("Unified catalog: {} unique driver entries".format(len(unified))) + + # Step 2: Check which drivers actually exist on disk + missing = [] + found = 0 + for entry in unified: + if check_driver_exists(entry): + found += 1 + else: + missing.append( + " {}: {}".format(entry["family"], entry["fileName"]) + ) + + print(" {} drivers found on disk".format(found)) + if missing: + print(" WARNING: {} driver zips NOT found on disk:".format(len(missing))) + for m in missing[:15]: + print(m) + if len(missing) > 15: + print(" ... and {} more".format(len(missing) - 15)) + print(" (Entries still included - PESetup may download them)") + + # Step 3: Build unified model selection from all driver entries + models = [] + seen = set() + for entry in unified: + friendly_names = [ + n.strip() + for n in entry["modelsfriendlyname"].split(",") + if n.strip() + ] + family = entry["family"] + for name in friendly_names: + key = (name, family) + if key not in seen: + seen.add(key) + models.append({"Model": name, "Id": family}) + models.sort(key=lambda x: x["Model"]) + print() + print("Unified model selection: {} models".format(len(models))) + + # Step 4: Update each image + print() + print("Updating images...") + for img_dir in image_dirs: + hw_file = img_dir / "Deploy" / "Control" / "HardwareDriver.json" + us_file = img_dir / "Tools" / "user_selections.json" + if not hw_file.exists() or not us_file.exists(): + continue + + # Write unified HardwareDriver.json + with open(hw_file, "w") as f: + json.dump(unified, f, indent=2) + f.write("\n") + + # Update user_selections.json (preserve OperatingSystemSelection etc.) + with open(us_file) as f: + user_sel = json.load(f) + old_count = len(user_sel[0].get("HardwareModelSelection", [])) + user_sel[0]["HardwareModelSelection"] = models + with open(us_file, "w") as f: + json.dump(user_sel, f, indent=2) + f.write("\n") + + print( + " {}: {} -> {} models, {} driver entries".format( + img_dir.name, old_count, len(models), len(unified) + ) + ) + + print() + print("Done!") + + +if __name__ == "__main__": + main() diff --git a/webapp/app.py b/webapp/app.py index 6ba685d..8eafa78 100644 --- a/webapp/app.py +++ b/webapp/app.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 """Flask web application for managing a GE Aerospace PXE server.""" +import json import logging import os import secrets @@ -51,14 +52,15 @@ def audit(action, detail=""): SAMBA_SHARE = os.environ.get("SAMBA_SHARE", "/srv/samba/winpeapps") CLONEZILLA_SHARE = os.environ.get("CLONEZILLA_SHARE", "/srv/samba/clonezilla") BLANCCO_REPORTS = os.environ.get("BLANCCO_REPORTS", "/srv/samba/blancco-reports") +ENROLLMENT_SHARE = os.environ.get("ENROLLMENT_SHARE", "/srv/samba/enrollment") UPLOAD_DIR = os.environ.get("UPLOAD_DIR", "/home/pxe/image-upload") SHARED_DIR = os.path.join(SAMBA_SHARE, "_shared") # Subdirs inside Deploy/ shared across ALL image types SHARED_DEPLOY_GLOBAL = ["Out-of-box Drivers"] # Subdirs inside Deploy/ shared within the same image family (by prefix) SHARED_DEPLOY_SCOPED = { - "gea-": ["Operating Systems"], - "ge-": ["Operating Systems"], + "gea-": ["Operating Systems", "Packages"], + "ge-": ["Operating Systems", "Packages"], } # Sibling dirs at image root shared within the same image family SHARED_ROOT_DIRS = { @@ -149,6 +151,164 @@ def unattend_path(image_type): return os.path.join(deploy_path(image_type), "FlatUnattendW10.xml") +def control_path(image_type): + """Return the Deploy/Control directory for an image type.""" + return os.path.join(deploy_path(image_type), "Control") + + +def tools_path(image_type): + """Return the Tools directory for an image type.""" + return os.path.join(SAMBA_SHARE, image_type, "Tools") + + +def _load_json(filepath): + """Parse a JSON file and return its contents, or [] on failure.""" + try: + with open(filepath, "r", encoding="utf-8-sig") as fh: + return json.load(fh) + except (OSError, json.JSONDecodeError): + return [] + + +def _save_json(filepath, data): + """Write data as pretty-printed JSON.""" + os.makedirs(os.path.dirname(filepath), exist_ok=True) + with open(filepath, "w", encoding="utf-8") as fh: + json.dump(data, fh, indent=2, ensure_ascii=False) + fh.write("\n") + + +def _resolve_destination(dest_dir, image_type): + """Convert a Windows *destinationdir* path to a Linux filesystem path. + + Replaces the ``*destinationdir*`` placeholder and backslashes, then + prepends ``SAMBA_SHARE/image_type/`` and resolves symlinks. + """ + if not dest_dir: + return "" + # Replace the placeholder (case-insensitive) + path = dest_dir + lower = path.lower() + idx = lower.find("*destinationdir*") + if idx != -1: + path = path[idx + len("*destinationdir*"):] + # Backslash → forward slash, strip leading slash + path = path.replace("\\", "/").lstrip("/") + full = os.path.join(SAMBA_SHARE, image_type, path) + # Resolve symlinks so shared dirs are found + try: + full = os.path.realpath(full) + except OSError: + pass + return full + + +def _load_image_config(image_type): + """Load all JSON configs for an image and check on-disk presence.""" + ctrl = control_path(image_type) + tools = tools_path(image_type) + + # --- Drivers (merge HardwareDriver.json + hw_drivers.json) --- + hw_driver_file = os.path.join(ctrl, "HardwareDriver.json") + hw_drivers_extra = os.path.join(ctrl, "hw_drivers.json") + drivers_raw = _load_json(hw_driver_file) + extra_raw = _load_json(hw_drivers_extra) + + # Merge: dedup by FileName (case-insensitive) + seen_files = set() + drivers = [] + for d in drivers_raw + extra_raw: + fname = (d.get("FileName") or d.get("fileName") or "").lower() + if fname and fname in seen_files: + continue + if fname: + seen_files.add(fname) + drivers.append(d) + + # Check disk presence for each driver + for d in drivers: + fname = d.get("FileName") or d.get("fileName") or "" + dest = d.get("DestinationDir") or d.get("destinationDir") or "" + resolved = _resolve_destination(dest, image_type) + if resolved and fname: + d["_on_disk"] = os.path.isfile(os.path.join(resolved, fname)) + else: + d["_on_disk"] = False + + # --- Operating Systems --- + os_file = os.path.join(ctrl, "OperatingSystem.json") + operating_systems = _load_json(os_file) + for entry in operating_systems: + osv = entry.get("operatingSystemVersion", {}) + wim = osv.get("wim", {}) + dest = wim.get("DestinationDir") or wim.get("destinationDir") or "" + resolved = _resolve_destination(dest, image_type) + if resolved: + entry["_on_disk"] = os.path.isfile(os.path.join(resolved, "install.wim")) + else: + entry["_on_disk"] = False + + # --- Packages --- + pkg_file = os.path.join(ctrl, "packages.json") + packages = _load_json(pkg_file) + for p in packages: + fname = p.get("fileName") or p.get("FileName") or "" + dest = p.get("destinationDir") or p.get("DestinationDir") or "" + resolved = _resolve_destination(dest, image_type) + if resolved and fname: + p["_on_disk"] = os.path.isfile(os.path.join(resolved, fname)) + else: + p["_on_disk"] = False + + # --- Hardware Models (user_selections.json) --- + us_file = os.path.join(tools, "user_selections.json") + us_raw = _load_json(us_file) + us_data = us_raw[0] if us_raw and isinstance(us_raw, list) else {} + hardware_models = us_data.get("HardwareModelSelection", []) + os_selection = str(us_data.get("OperatingSystemSelection", "")) + + # Build a lookup: family → driver entry (for disk-presence on models) + family_lookup = {} + for d in drivers: + family = d.get("family", "") + if family: + family_lookup[family] = d + + for hm in hardware_models: + family_id = hm.get("Id", "") + matched = family_lookup.get(family_id) + hm["_on_disk"] = matched["_on_disk"] if matched else False + + # --- Orphan drivers: zip files on disk not referenced in any JSON --- + orphan_drivers = [] + oob_dir = os.path.join(deploy_path(image_type), "Out-of-box Drivers") + # Resolve symlinks + try: + oob_dir = os.path.realpath(oob_dir) + except OSError: + pass + registered_files = set() + for d in drivers: + fname = d.get("FileName") or d.get("fileName") or "" + if fname: + registered_files.add(fname.lower()) + if os.path.isdir(oob_dir): + for dirpath, _dirnames, filenames in os.walk(oob_dir): + for fn in filenames: + if fn.lower().endswith(".zip") and fn.lower() not in registered_files: + rel = os.path.relpath(os.path.join(dirpath, fn), oob_dir) + orphan_drivers.append({"fileName": fn, "relPath": rel}) + + return { + "hardware_models": hardware_models, + "drivers": drivers, + "operating_systems": operating_systems, + "packages": packages, + "orphan_drivers": orphan_drivers, + "os_selection": os_selection, + } + + def image_status(image_type): """Return a dict describing the state of an image type.""" dp = deploy_path(image_type) @@ -255,10 +415,11 @@ def _import_deploy(src_deploy, dst_deploy, target="", move=False): _replace_with_symlink(dst_item, shared_dest) continue - # Normal transfer - if os.path.exists(dst_item): - shutil.rmtree(dst_item) - _transfer_tree(src_item, dst_item) + # Normal transfer — merge to preserve existing custom content + if os.path.isdir(dst_item): + _merge_tree(src_item, dst_item, move=move) + else: + _transfer_tree(src_item, dst_item) def _replace_with_symlink(link_path, target_path): @@ -355,6 +516,21 @@ def parse_unattend(xml_path): "SkipMachineOOBE": "true", }, "firstlogon_commands": [], + "user_accounts": [], + "autologon": { + "enabled": "", + "username": "", + "password": "", + "plain_text": "true", + "logon_count": "", + }, + "intl": { + "input_locale": "", + "system_locale": "", + "ui_language": "", + "user_locale": "", + }, + "oobe_timezone": "", "raw_xml": "", } @@ -418,6 +594,19 @@ def parse_unattend(xml_path): namespaces=ns, ): comp_name = comp.get("name", "") + + # International-Core component + if "International-Core" in comp_name: + for tag, key in [ + ("InputLocale", "input_locale"), + ("SystemLocale", "system_locale"), + ("UILanguage", "ui_language"), + ("UserLocale", "user_locale"), + ]: + el = comp.find(qn(tag)) + if el is not None and el.text: + data["intl"][key] = el.text.strip() + if "OOBE" in comp_name or "Shell-Setup" in comp_name: oobe_el = comp.find(qn("OOBE")) if oobe_el is not None: @@ -426,6 +615,57 @@ def parse_unattend(xml_path): if local in data["oobe"] and child.text: data["oobe"][local] = child.text.strip() + # UserAccounts / LocalAccounts + ua = comp.find(qn("UserAccounts")) + if ua is not None: + la_container = ua.find(qn("LocalAccounts")) + if la_container is not None: + for acct in la_container.findall(qn("LocalAccount")): + name_el = acct.find(qn("Name")) + group_el = acct.find(qn("Group")) + display_el = acct.find(qn("DisplayName")) + pw_el = acct.find(qn("Password")) + pw_val = "" + pw_plain = "true" + if pw_el is not None: + v = pw_el.find(qn("Value")) + p = pw_el.find(qn("PlainText")) + if v is not None and v.text: + pw_val = v.text.strip() + if p is not None and p.text: + pw_plain = p.text.strip() + data["user_accounts"].append({ + "name": name_el.text.strip() if name_el is not None and name_el.text else "", + "password": pw_val, + "plain_text": pw_plain, + "group": group_el.text.strip() if group_el is not None and group_el.text else "Administrators", + "display_name": display_el.text.strip() if display_el is not None and display_el.text else "", + }) + + # AutoLogon + al = comp.find(qn("AutoLogon")) + if al is not None: + enabled_el = al.find(qn("Enabled")) + user_el = al.find(qn("Username")) + count_el = al.find(qn("LogonCount")) + pw_el = al.find(qn("Password")) + pw_val = "" + pw_plain = "true" + if pw_el is not None: + v = pw_el.find(qn("Value")) + p = pw_el.find(qn("PlainText")) + if v is not None and v.text: + pw_val = v.text.strip() + if p is not None and p.text: + pw_plain = p.text.strip() + data["autologon"] = { + "enabled": enabled_el.text.strip() if enabled_el is not None and enabled_el.text else "", + "username": user_el.text.strip() if user_el is not None and user_el.text else "", + "password": pw_val, + "plain_text": pw_plain, + "logon_count": count_el.text.strip() if count_el is not None and count_el.text else "", + } + # FirstLogonCommands flc = comp.find(qn("FirstLogonCommands")) if flc is not None: @@ -439,6 +679,11 @@ def parse_unattend(xml_path): "description": desc_el.text.strip() if desc_el is not None and desc_el.text else "", }) + # TimeZone (oobeSystem pass) + tz_el = comp.find(qn("TimeZone")) + if tz_el is not None and tz_el.text: + data["oobe_timezone"] = tz_el.text.strip() + return data @@ -515,6 +760,28 @@ def build_unattend_xml(form_data): # --- oobeSystem --- oobe_settings = _settings_pass(root, "oobeSystem") + + # International-Core component (before Shell-Setup) + intl = form_data.get("intl", {}) + if any(v.strip() for v in intl.values() if v): + intl_comp = etree.SubElement(oobe_settings, qn("component")) + intl_comp.set("name", "Microsoft-Windows-International-Core") + intl_comp.set("processorArchitecture", "amd64") + intl_comp.set("publicKeyToken", "31bf3856ad364e35") + intl_comp.set("language", "neutral") + intl_comp.set("versionScope", "nonSxS") + for tag, key in [ + ("InputLocale", "input_locale"), + ("SystemLocale", "system_locale"), + ("UILanguage", "ui_language"), + ("UserLocale", "user_locale"), + ]: + val = intl.get(key, "").strip() + if val: + el = etree.SubElement(intl_comp, qn(tag)) + el.text = val + + # Shell-Setup component oobe_comp = etree.SubElement(oobe_settings, qn("component")) oobe_comp.set("name", "Microsoft-Windows-Shell-Setup") oobe_comp.set("processorArchitecture", "amd64") @@ -540,6 +807,46 @@ def build_unattend_xml(form_data): child = etree.SubElement(oobe_el, qn(key)) child.text = str(val) + # UserAccounts / LocalAccounts + accounts = form_data.get("user_accounts", []) + if accounts: + ua = etree.SubElement(oobe_comp, qn("UserAccounts")) + la_container = etree.SubElement(ua, qn("LocalAccounts")) + for acct in accounts: + if not acct.get("name", "").strip(): + continue + la = etree.SubElement(la_container, qn("LocalAccount")) + la.set(qwcm("action"), "add") + pw = etree.SubElement(la, qn("Password")) + pw_val = etree.SubElement(pw, qn("Value")) + pw_val.text = acct.get("password", "") + pw_plain = etree.SubElement(pw, qn("PlainText")) + pw_plain.text = acct.get("plain_text", "true") + name_el = etree.SubElement(la, qn("Name")) + name_el.text = acct["name"].strip() + group_el = etree.SubElement(la, qn("Group")) + group_el.text = acct.get("group", "Administrators").strip() + display_el = etree.SubElement(la, qn("DisplayName")) + display_el.text = acct.get("display_name", acct["name"]).strip() + + # AutoLogon + autologon = form_data.get("autologon", {}) + if autologon.get("username", "").strip(): + al = etree.SubElement(oobe_comp, qn("AutoLogon")) + al_pw = etree.SubElement(al, qn("Password")) + al_pw_val = etree.SubElement(al_pw, qn("Value")) + al_pw_val.text = autologon.get("password", "") + al_pw_plain = etree.SubElement(al_pw, qn("PlainText")) + al_pw_plain.text = autologon.get("plain_text", "true") + al_enabled = etree.SubElement(al, qn("Enabled")) + al_enabled.text = autologon.get("enabled", "true") + al_user = etree.SubElement(al, qn("Username")) + al_user.text = autologon["username"].strip() + logon_count = autologon.get("logon_count", "").strip() + if logon_count: + al_count = etree.SubElement(al, qn("LogonCount")) + al_count.text = logon_count + # FirstLogonCommands fl_cmds = form_data.get("firstlogon_commands", []) if fl_cmds: @@ -556,6 +863,12 @@ def build_unattend_xml(form_data): desc_el = etree.SubElement(sc, qn("Description")) desc_el.text = cmd.get("description", "").strip() + # TimeZone (oobeSystem pass) + oobe_tz = form_data.get("oobe_timezone", "").strip() + if oobe_tz: + tz_el = etree.SubElement(oobe_comp, qn("TimeZone")) + tz_el.text = oobe_tz + xml_bytes = etree.tostring( root, pretty_print=True, @@ -616,6 +929,40 @@ def _extract_form_data(form): "description": fl_descs[i] if i < len(fl_descs) else "", }) + # User accounts + accounts = [] + i = 0 + while form.get(f"account_name_{i}"): + accounts.append({ + "name": form.get(f"account_name_{i}", ""), + "password": form.get(f"account_password_{i}", ""), + "plain_text": form.get(f"account_plaintext_{i}", "true"), + "group": form.get(f"account_group_{i}", "Administrators"), + "display_name": form.get(f"account_display_{i}", ""), + }) + i += 1 + data["user_accounts"] = accounts + + # AutoLogon + data["autologon"] = { + "enabled": form.get("autologon_enabled", ""), + "username": form.get("autologon_username", ""), + "password": form.get("autologon_password", ""), + "plain_text": form.get("autologon_plaintext", "true"), + "logon_count": form.get("autologon_logoncount", ""), + } + + # International settings + data["intl"] = { + "input_locale": form.get("intl_input_locale", ""), + "system_locale": form.get("intl_system_locale", ""), + "ui_language": form.get("intl_ui_language", ""), + "user_locale": form.get("intl_user_locale", ""), + } + + # OOBE TimeZone + data["oobe_timezone"] = form.get("oobe_timezone", "") + return data @@ -799,6 +1146,74 @@ def unattend_editor(image_type): ) +@app.route("/images//config") +def image_config(image_type): + if image_type not in IMAGE_TYPES: + flash("Unknown image type.", "danger") + return redirect(url_for("dashboard")) + + config = _load_image_config(image_type) + return render_template( + "image_config.html", + image_type=image_type, + friendly_name=FRIENDLY_NAMES.get(image_type, image_type), + config=config, + ) + + +@app.route("/images//config/save", methods=["POST"]) +def image_config_save(image_type): + if image_type not in IMAGE_TYPES: + flash("Unknown image type.", "danger") + return redirect(url_for("dashboard")) + + section = request.form.get("section", "") + payload = request.form.get("payload", "[]") + try: + data = json.loads(payload) + except json.JSONDecodeError: + flash("Invalid JSON payload.", "danger") + return redirect(url_for("image_config", image_type=image_type)) + + ctrl = control_path(image_type) + tools = tools_path(image_type) + + try: + if section == "hardware_models": + us_file = os.path.join(tools, "user_selections.json") + us_raw = _load_json(us_file) + us_data = us_raw[0] if us_raw and isinstance(us_raw, list) else {} + us_data["HardwareModelSelection"] = data + _save_json(us_file, [us_data]) + audit("CONFIG_SAVE", f"{image_type}/hardware_models") + + elif section == "drivers": + # Strip internal fields before saving + clean = [{k: v for k, v in d.items() if not k.startswith("_")} for d in data] + _save_json(os.path.join(ctrl, "HardwareDriver.json"), clean) + audit("CONFIG_SAVE", f"{image_type}/drivers") + + elif section == "operating_systems": + clean = [{k: v for k, v in d.items() if not k.startswith("_")} for d in data] + _save_json(os.path.join(ctrl, "OperatingSystem.json"), clean) + audit("CONFIG_SAVE", f"{image_type}/operating_systems") + + elif section == "packages": + clean = [{k: v for k, v in d.items() if not k.startswith("_")} for d in data] + _save_json(os.path.join(ctrl, "packages.json"), clean) + audit("CONFIG_SAVE", f"{image_type}/packages") + + else: + flash(f"Unknown section: {section}", "danger") + return redirect(url_for("image_config", image_type=image_type)) + + flash(f"Saved {section.replace('_', ' ')} successfully.", "success") + except Exception as exc: + flash(f"Failed to save {section}: {exc}", "danger") + + return redirect(url_for("image_config", image_type=image_type)) + + # --------------------------------------------------------------------------- # Routes — Clonezilla Backups # --------------------------------------------------------------------------- @@ -922,6 +1337,78 @@ def blancco_delete_report(filename): return redirect(url_for("blancco_reports")) +# --------------------------------------------------------------------------- +# Routes — Enrollment Packages +# --------------------------------------------------------------------------- + +@app.route("/enrollment") +def enrollment(): + packages = [] + if os.path.isdir(ENROLLMENT_SHARE): + for f in sorted(os.listdir(ENROLLMENT_SHARE)): + fpath = os.path.join(ENROLLMENT_SHARE, f) + if os.path.isfile(fpath) and f.lower().endswith(".ppkg"): + stat = os.stat(fpath) + packages.append({ + "filename": f, + "size": stat.st_size, + "modified": stat.st_mtime, + }) + return render_template( + "enrollment.html", + packages=packages, + image_types=IMAGE_TYPES, + friendly_names=FRIENDLY_NAMES, + ) + + +@app.route("/enrollment/upload", methods=["POST"]) +def enrollment_upload(): + if "ppkg_file" not in request.files: + flash("No file selected.", "danger") + return redirect(url_for("enrollment")) + + f = request.files["ppkg_file"] + if not f.filename: + flash("No file selected.", "danger") + return redirect(url_for("enrollment")) + + filename = secure_filename(f.filename) + if not filename.lower().endswith(".ppkg"): + flash("Only .ppkg files are accepted.", "danger") + return redirect(url_for("enrollment")) + + os.makedirs(ENROLLMENT_SHARE, exist_ok=True) + dest = os.path.join(ENROLLMENT_SHARE, filename) + f.save(dest) + audit("ENROLLMENT_UPLOAD", filename) + flash(f"Uploaded {filename} successfully.", "success") + return redirect(url_for("enrollment")) + + +@app.route("/enrollment/download/") +def enrollment_download(filename): + filename = secure_filename(filename) + fpath = os.path.join(ENROLLMENT_SHARE, filename) + if not os.path.isfile(fpath): + flash(f"Package not found: {filename}", "danger") + return redirect(url_for("enrollment")) + return send_file(fpath, as_attachment=True) + + +@app.route("/enrollment/delete/", methods=["POST"]) +def enrollment_delete(filename): + filename = secure_filename(filename) + fpath = os.path.join(ENROLLMENT_SHARE, filename) + if os.path.isfile(fpath): + os.remove(fpath) + audit("ENROLLMENT_DELETE", filename) + flash(f"Deleted {filename}.", "success") + else: + flash(f"Package not found: {filename}", "danger") + return redirect(url_for("enrollment")) + + # --------------------------------------------------------------------------- # Routes — startnet.cmd Editor (WIM) # --------------------------------------------------------------------------- diff --git a/webapp/static/app.js b/webapp/static/app.js index 115ab91..9db19a5 100644 --- a/webapp/static/app.js +++ b/webapp/static/app.js @@ -150,6 +150,71 @@ document.addEventListener('DOMContentLoaded', function () { }); } + // ----------------------------------------------------------------------- + // Add User Account + // ----------------------------------------------------------------------- + var addUserAccountBtn = document.getElementById('addUserAccount'); + if (addUserAccountBtn) { + addUserAccountBtn.addEventListener('click', function () { + var tbody = document.querySelector('#userAccountsTable tbody'); + var idx = tbody.querySelectorAll('tr').length; + var tr = document.createElement('tr'); + tr.innerHTML = + '' + (idx + 1) + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + tbody.appendChild(tr); + var emptyEl = document.getElementById('userAccountsEmpty'); + if (emptyEl) emptyEl.style.display = 'none'; + tr.querySelector('input[name^="account_name_"]').focus(); + }); + } + + // ----------------------------------------------------------------------- + // Remove User Account row + renumber indices (delegated) + // ----------------------------------------------------------------------- + document.addEventListener('click', function (e) { + var btn = e.target.closest('.remove-account-row'); + if (!btn) return; + var row = btn.closest('tr'); + var tbody = row.parentElement; + row.remove(); + // Renumber order and field name indices + var rows = tbody.querySelectorAll('tr'); + rows.forEach(function (r, i) { + var orderCell = r.querySelector('.order-num'); + if (orderCell) orderCell.textContent = i + 1; + // Rename inputs to match new index + r.querySelectorAll('input').forEach(function (inp) { + var name = inp.getAttribute('name'); + if (name) { + inp.setAttribute('name', name.replace(/_\d+$/, '_' + i)); + } + }); + }); + var emptyEl = document.getElementById('userAccountsEmpty'); + if (emptyEl) emptyEl.style.display = rows.length > 0 ? 'none' : ''; + }); + + // ----------------------------------------------------------------------- + // AutoLogon Enabled toggle — keep hidden input in sync + // ----------------------------------------------------------------------- + var autologonToggle = document.getElementById('autologonEnabledToggle'); + if (autologonToggle) { + autologonToggle.addEventListener('change', function () { + var hidden = document.getElementById('autologon_enabled_val'); + if (hidden) { + hidden.value = this.checked ? 'true' : 'false'; + } + }); + } + // ----------------------------------------------------------------------- // OOBE toggle switches — keep hidden input in sync // ----------------------------------------------------------------------- @@ -298,5 +363,107 @@ document.addEventListener('DOMContentLoaded', function () { toggleEmpty('driverPathsTable', 'driverPathsEmpty'); toggleEmpty('specCmdTable', 'specCmdEmpty'); toggleEmpty('flCmdTable', 'flCmdEmpty'); + toggleEmpty('userAccountsTable', 'userAccountsEmpty'); + + // ======================================================================= + // Image Configuration Editor handlers + // ======================================================================= + + // ----------------------------------------------------------------------- + // Generic: collect table rows into JSON and submit form + // ----------------------------------------------------------------------- + function collectAndSubmit(tableId, hiddenId, formId, extractor) { + var tbody = document.querySelector('#' + tableId + ' tbody'); + if (!tbody) return; + var rows = tbody.querySelectorAll('tr'); + var data = []; + rows.forEach(function (row) { + var item = extractor(row); + if (item) data.push(item); + }); + document.getElementById(hiddenId).value = JSON.stringify(data); + document.getElementById(formId).submit(); + } + + // Helper: extract JSON from data-json attr, stripping internal _fields + function extractDataJson(row) { + var raw = row.getAttribute('data-json'); + if (!raw) return null; + try { + var obj = JSON.parse(raw); + Object.keys(obj).forEach(function (k) { + if (k.charAt(0) === '_') delete obj[k]; + }); + return obj; + } catch (e) { return null; } + } + + // ----------------------------------------------------------------------- + // Save: Hardware Models + // ----------------------------------------------------------------------- + var saveHwModelsBtn = document.getElementById('saveHwModels'); + if (saveHwModelsBtn) { + saveHwModelsBtn.addEventListener('click', function () { + collectAndSubmit('hwModelsTable', 'hwModelsData', 'hwModelsForm', function (row) { + var m = row.querySelector('[data-field="Model"]'); + var f = row.querySelector('[data-field="Id"]'); + if (!m || !f) return null; + return { Model: m.value, Id: f.value }; + }); + }); + } + + // ----------------------------------------------------------------------- + // Save: Drivers + // ----------------------------------------------------------------------- + var saveDriversBtn = document.getElementById('saveDrivers'); + if (saveDriversBtn) { + saveDriversBtn.addEventListener('click', function () { + collectAndSubmit('driversTable', 'driversData', 'driversForm', extractDataJson); + }); + } + + // ----------------------------------------------------------------------- + // Save: Operating Systems + // ----------------------------------------------------------------------- + var saveOsBtn = document.getElementById('saveOs'); + if (saveOsBtn) { + saveOsBtn.addEventListener('click', function () { + collectAndSubmit('osTable', 'osData', 'osForm', extractDataJson); + }); + } + + // ----------------------------------------------------------------------- + // Save: Packages + // ----------------------------------------------------------------------- + var savePackagesBtn = document.getElementById('savePackages'); + if (savePackagesBtn) { + savePackagesBtn.addEventListener('click', function () { + collectAndSubmit('packagesTable', 'packagesData', 'packagesForm', extractDataJson); + }); + } + + // ----------------------------------------------------------------------- + // Add Hardware Model row + // ----------------------------------------------------------------------- + var addHwModelBtn = document.getElementById('addHwModel'); + if (addHwModelBtn) { + addHwModelBtn.addEventListener('click', function () { + var tbody = document.querySelector('#hwModelsTable tbody'); + var idx = tbody.querySelectorAll('tr').length + 1; + var tr = document.createElement('tr'); + tr.innerHTML = + '' + idx + '' + + '' + + '' + + 'New' + + ''; + tbody.appendChild(tr); + var empty = document.getElementById('hwModelsEmpty'); + if (empty) empty.style.display = 'none'; + tr.querySelector('input').focus(); + }); + } }); + diff --git a/webapp/templates/audit.html b/webapp/templates/audit.html index bd02494..e35c9b1 100644 --- a/webapp/templates/audit.html +++ b/webapp/templates/audit.html @@ -3,13 +3,13 @@ {% block content %}
-

Audit Log

+

Audit Log

{{ entries|length }} entries
- Activity History + Activity History
{% if entries %} @@ -52,7 +52,6 @@
{% else %}
-

No audit log entries yet.

Actions like image imports, unattend edits, and backup operations will be logged here.

diff --git a/webapp/templates/backups.html b/webapp/templates/backups.html index e887678..28b609b 100644 --- a/webapp/templates/backups.html +++ b/webapp/templates/backups.html @@ -3,15 +3,15 @@ {% block content %}
-

Clonezilla Backups

+

Clonezilla Backups

- Machine Backups + Machine Backups {{ backups|length }}
@@ -36,12 +36,12 @@ - + Download @@ -50,7 +50,6 @@ {% else %}
-

No backups found. Upload a Clonezilla backup .zip to get started.

{% endif %} @@ -59,7 +58,7 @@
-
Backup Naming Convention
+
Backup Naming Convention

Name backup files with the machine number (e.g., 6501.zip). The Samba share \\pxe-server\clonezilla is also available on the network for direct Clonezilla save/restore operations. @@ -74,7 +73,7 @@

@@ -103,7 +102,7 @@
diff --git a/webapp/templates/base.html b/webapp/templates/base.html index cf90eb2..f9fff21 100644 --- a/webapp/templates/base.html +++ b/webapp/templates/base.html @@ -7,7 +7,6 @@ {% block title %}PXE Server Manager{% endblock %} - +{% endblock %} + +{% block content %} +
+
+

{{ friendly_name }}

+ + Image Configuration + — OS Selection: {{ config.os_selection or 'Not set' }} + +
+ + Edit Unattend + +
+ +{# ==================== SECTION 1: Hardware Models ==================== #} +
+
+ Hardware Models + {{ config.hardware_models|length }} + +
+ + +
+
+
+
+ + + +
+ + + + + + + + + + + + {% for hm in config.hardware_models %} + + + + + + + + {% endfor %} + +
#ModelDriver Family IDOn Disk
{{ loop.index }} + {% if hm._on_disk %} + Yes + {% else %} + No + {% endif %} + + +
+ {% if not config.hardware_models %} +
+ No hardware models configured. +
+ {% endif %} +
+
+ +{# ==================== SECTION 2: Driver Packs ==================== #} +
+
+ Driver Packs + {{ config.drivers|length }} + +
+ +
+
+
+
+ + + +
+
+ + + + + + + + + + + + + + {% for drv in config.drivers %} + + + + + + + + + + {% endfor %} + +
#FamilyModelsFile NameOS IDsOn Disk
{{ loop.index }}{{ drv.family }}{{ drv.models }} + {{ drv.FileName or drv.get('fileName','') }} + {{ drv.osId }} + {% if drv._on_disk %} + Yes + {% else %} + No + {% endif %} + + +
+
+ {% if not config.drivers %} +
+ No driver packs configured. +
+ {% endif %} +
+ + {# Orphan drivers sub-section #} + {% if config.orphan_drivers %} + + {% endif %} +
+ +{# ==================== SECTION 3: Operating Systems ==================== #} +
+
+ Operating Systems + {{ config.operating_systems|length }} + +
+ +
+
+
+
+ + + +
+ + + + + + + + + + + + + + + {% for entry in config.operating_systems %} + {% set osv = entry.operatingSystemVersion %} + + + + + + + + + + + {% endfor %} + +
#Product NameVersionBuildIDActiveWIM On Disk
{{ loop.index }}{{ osv.productName }}{{ osv.versionNumber }}{{ osv.buildNumber }}{{ osv.id }} + {% if osv.isActive %} + Active + {% else %} + Inactive + {% endif %} + + {% if entry._on_disk %} + Yes + {% else %} + No + {% endif %} + + +
+ {% if not config.operating_systems %} +
+ No operating systems configured. +
+ {% endif %} +
+
+ +{# ==================== SECTION 4: Packages ==================== #} +
+
+ Packages + {{ config.packages|length }} + +
+ +
+
+
+
+ + + +
+
+ + + + + + + + + + + + + + + {% for pkg in config.packages %} + + + + + + + + + + + {% endfor %} + +
#NameCommentFileOS IDsEnabledOn Disk
{{ loop.index }}{{ pkg.name }}{{ pkg.comment }} + {{ pkg.fileName or pkg.get('FileName','') }} + {{ pkg.osId }} + {% if pkg.enabled %} + Yes + {% else %} + No + {% endif %} + + {% if pkg._on_disk %} + Yes + {% else %} + No + {% endif %} + + +
+
+ {% if not config.packages %} +
+ No packages configured. +
+ {% endif %} +
+
+{% endblock %} diff --git a/webapp/templates/import.html b/webapp/templates/import.html index 6c2a570..7db92f9 100644 --- a/webapp/templates/import.html +++ b/webapp/templates/import.html @@ -10,7 +10,7 @@
- Import from Network Upload + Import from Network Upload
{% if upload_sources %} @@ -44,7 +44,6 @@