From 821a9273655c81d1b26402ff629e4f3deb3f86fe Mon Sep 17 00:00:00 2001 From: cproudlock Date: Fri, 16 Oct 2026 12:37:50 -0400 Subject: [PATCH] Engineering Layer: survive the C:\Deploy cleanup; the employee gets the icon Engineer builds left the employee without "Configure my Engineering Layer". First logon ran C:\Deploy\Applications\extra_engineering\ Install-EngineeringLayer.ps1, and the script itself lived in C:\Deploy: std/eng have no autologon, so a PC that rebooted before the employee signed in had lost C:\Deploy to the orchestrator's DeleteDeployDir task, and nothing installed, silently. - engineer unattend, specialize Order 37 (free slot): run the script with -StageOnly, copying the payload and the script to C:\PreInstall\engineering while C:\Deploy exists (Path 119 chars) - first logon Order 11: run it from C:\PreInstall\engineering, falling back to C:\Deploy if staging did not happen - the script (now in the repo, playbook/engineering/; it lived only on the server): after the install, restore the Public desktop icon if it is missing and the PC is not configured yet (no C:\TMP\DUU_Complete.log), and register Active Setup so each later user gets the icon on their own desktop when the Public one is gone. v3.1 puts the icon on the Public desktop; clicking it (configure, from GE's V: share) deletes it for all - playbook deploys the script to gea-engineer extra_engineering; the 650 MB payload stays site content (documented in SITE-CUSTOMIZATION.md) Tested in the win11 VM with the exact unattend strings and a stand-in installer that skips the icon: specialize staged payload and script; C:\Deploy deleted; first logon installed from the staged copy, restored the icon and registered Active Setup. The per-user step as a new user: icon on their desktop when the PC is unconfigured and the Public icon is gone; none when configured or when the Public icon is there. Lint clean. Not deployed: unattends go out only through scripts/deploy-unattend.sh. --- docs/SITE-CUSTOMIZATION.md | 29 ++- playbook/FlatUnattendW10-engineer.xml | 27 ++- .../engineering/Install-EngineeringLayer.ps1 | 175 ++++++++++++++++++ playbook/pxe_server_setup.yml | 15 ++ 4 files changed, 237 insertions(+), 9 deletions(-) create mode 100644 playbook/engineering/Install-EngineeringLayer.ps1 diff --git a/docs/SITE-CUSTOMIZATION.md b/docs/SITE-CUSTOMIZATION.md index 27d09c7..8746b50 100644 --- a/docs/SITE-CUSTOMIZATION.md +++ b/docs/SITE-CUSTOMIZATION.md @@ -100,6 +100,31 @@ The repo defines seven image types (see `playbook/pxe_server_setup.yml`, Trim or extend this list and the matching `shopfloor_types` filter as needed. +### Engineering Layer (gea-engineer) + +Handled by the repo; nothing to work out per site except the payload. + +- `playbook/engineering/Install-EngineeringLayer.ps1` is deployed by the + playbook to `gea-engineer/Deploy/Applications/extra_engineering/`. +- `FlatUnattendW10-engineer.xml` runs it twice: + - specialize Order 37 (`-StageOnly`) copies the payload and the script to + `C:\PreInstall\engineering` while `C:\Deploy` exists; + - first logon Order 11 installs from that copy. The bulk-enrollment + `DeleteDeployDir` task removes `C:\Deploy` on the next boot, so a PC + that reboots before the employee signs in would otherwise install + nothing and show no icon. +- After the install it makes sure the "Configure my Engineering Layer" + icon is on the Public desktop (until the PC is configured), and registers + Active Setup so a later user gets the icon if someone else already used + it. The employee clicks it once, on the GE network: it maps V: and runs + `install_local_new.cmd` from GE's share, which removes the icon. +- Log on the PC: `C:\Logs\Engineering\install-engineering-layer.log`. + +The site supplies the payload beside the script, in +`gea-engineer/Deploy/Applications/extra_engineering/`: +`engineering_layer/Engineering_Install_v3.1.exe` and `prereqs/` +(.NET 3.5 sxs cabs, VC++ redists), about 650 MB. + ### PC types (shopfloor sub-types) `playbook/shopfloor-setup/` contains per-PC-type install logic. The current @@ -182,7 +207,9 @@ Blob storage account. - `\\172.16.9.1\image-upload` as the destination - `C:\ProgramData\GEAerospace\MediaCreator\Cache\` as the source -Update both for a different site. +Update both for a different site (`-Server`, `-CachePath`; `-WebPort` if the +webapp is not on 9009). The script checks every file on the server through +the webapp before the Image Import page will accept the upload. ## Recommended forward state diff --git a/playbook/FlatUnattendW10-engineer.xml b/playbook/FlatUnattendW10-engineer.xml index f66f511..30de53f 100644 --- a/playbook/FlatUnattendW10-engineer.xml +++ b/playbook/FlatUnattendW10-engineer.xml @@ -258,6 +258,16 @@ cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v EnableDynamicContentInWSB /t REG_DWORD /d 0 /f Disable Search Highlights + + 37 + powershell.exe -NoP -EP Bypass -File "C:\Deploy\Applications\extra_engineering\Install-EngineeringLayer.ps1" -StageOnly + + Engineering Layer: stage payload out of C:\Deploy + 39 @@ -382,14 +392,15 @@ 11 - powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\Deploy\Applications\extra_engineering\Install-EngineeringLayer.ps1" - - Engineering Layer: stage payload out of C:\Deploy then install + cmd /c if exist "C:\PreInstall\engineering\Install-EngineeringLayer.ps1" (powershell.exe -NoP -EP Bypass -File "C:\PreInstall\engineering\Install-EngineeringLayer.ps1") else (powershell.exe -NoP -EP Bypass -File "C:\Deploy\Applications\extra_engineering\Install-EngineeringLayer.ps1") + + Engineering Layer: install from the staged copy powershell.exe -NoP -EP Bypass -File C:\ProgramData\PxeStatus\Send-ImagingStage.ps1 -PCType gea-engineer -StageIndex 5 -Stage "Setup complete - enrollment continues on the PC" -Status succeeded diff --git a/playbook/engineering/Install-EngineeringLayer.ps1 b/playbook/engineering/Install-EngineeringLayer.ps1 new file mode 100644 index 0000000..7a443bf --- /dev/null +++ b/playbook/engineering/Install-EngineeringLayer.ps1 @@ -0,0 +1,175 @@ +# Install-EngineeringLayer.ps1 - install the Engineering Layer before the MCL +# orchestrator's cleanup removes C:\Deploy, and make sure the employee gets +# the "Configure my Engineering Layer" icon. +# +# THE RACE THIS SOLVES +# The bulk-enrollment orchestrator registers a DeleteDeployDir startup task that +# does "rmdir /s /q C:\Drivers & rmdir /s /q C:\GEA & rmdir /s /q C:\Deploy" on +# the NEXT boot. The engineering payload (529 MB installer + .NET 3.5 sxs + VC++ +# redists, 653 MB total) lives under C:\Deploy\Applications\extra_engineering. +# Standard/Engineer builds have no autologon, so a PC that reboots between OOBE +# and the employee's first sign-in loses C:\Deploy before the first-logon +# install - and with it this script, which used to be called from there too. +# Then nothing installed, no error anywhere, and no icon. +# +# So, in two steps (both from FlatUnattendW10-engineer.xml): +# specialize -StageOnly : copy the payload AND this script to +# C:\PreInstall\engineering while C:\Deploy certainly exists. +# first logon (no switch, run from C:\PreInstall\engineering): install +# from the staged copy. C:\Deploy is not needed any more. +# Run from C:\Deploy (an older unattend) it still stages first, as before. +# +# THE ICON +# Engineering_Install_v3.1 copies "Configure my Engineering Layer.lnk" to +# C:\Users\Public\Desktop. The employee clicks it once (on the GE network: it +# maps V: and runs install_local_new.cmd from there), which writes +# C:\TMP\DUU_Complete.log and deletes the Public icon. After the install this +# script puts the icon back if it is missing and the PC is not configured +# yet, and registers an Active Setup component: at each user's first sign-in +# Windows runs Ensure-EngineeringIcon.ps1 in that user's session, which puts +# the icon on that user's desktop if the PC is still not configured and the +# Public icon is gone (someone else clicked it, or it never appeared). +# +# Idempotent. Safe to re-run: it skips the copy when the staged payload is +# already complete and skips the install when the marker is present (the +# icon check still runs). + +[CmdletBinding()] +param( + [string]$Source = 'C:\Deploy\Applications\extra_engineering', + # C:\PreInstall is already the convention for payload that must outlive + # imaging - the unattend installs PowerShell 7 from C:\PreInstall\installers. + [string]$StagingRoot = 'C:\PreInstall\engineering', + [string]$LogDir = 'C:\Logs\Engineering', + [string]$Installer = 'engineering_layer\Engineering_Install_v3.1.exe', + [switch]$StageOnly +) + +$ErrorActionPreference = 'Continue' +New-Item -ItemType Directory -Force -Path $LogDir | Out-Null +$log = Join-Path $LogDir 'install-engineering-layer.log' +$marker = Join-Path $StagingRoot '.installed' + +$IconName = 'Configure my Engineering Layer.lnk' +$IconSource = 'C:\TMP\Engineering_Install\Imaging\packages\' + $IconName +$PublicIcon = 'C:\Users\Public\Desktop\' + $IconName +$Configured = 'C:\TMP\DUU_Complete.log' # written by install_local_new.cmd +$ActiveSetup = 'HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\GEA-EngineeringLayerIcon' + +function Write-EngLog { + param([string]$Message, [string]$Level = 'INFO') + $line = '[{0}] [{1}] [{2}] {3}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $env:USERNAME, $Message + Add-Content -Path $log -Value $line -ErrorAction SilentlyContinue + Write-Host $line +} + +# Icon for whoever signs in next. Called after every install attempt that +# left the layer installed (fresh or already marked). +function Set-EngineeringIcon { + if (Test-Path -LiteralPath $Configured) { + Write-EngLog 'PC already configured (DUU_Complete.log) - no icon needed.' + return + } + if (-not (Test-Path -LiteralPath $IconSource)) { + Write-EngLog "Icon source missing: $IconSource (installer layout changed?)" 'ERROR' + return + } + if (Test-Path -LiteralPath $PublicIcon) { + Write-EngLog 'Public desktop icon present.' + } else { + Copy-Item -LiteralPath $IconSource -Destination $PublicIcon -Force -ErrorAction SilentlyContinue + if (Test-Path -LiteralPath $PublicIcon) { Write-EngLog 'Public desktop icon was missing - restored.' 'WARN' } + else { Write-EngLog "Could not restore $PublicIcon" 'ERROR' } + } + + # Per-user fallback, run by Windows once per user at first sign-in. + $ensure = Join-Path $StagingRoot 'Ensure-EngineeringIcon.ps1' + $body = @( + '# Written by Install-EngineeringLayer.ps1; run once per user by Active Setup.' + '# Puts "Configure my Engineering Layer" on this user''s desktop when the PC' + '# is not configured yet and the Public desktop icon is gone.' + "`$src = '$IconSource'" + "if (Test-Path -LiteralPath '$Configured') { exit 0 }" + "if (Test-Path -LiteralPath '$PublicIcon') { exit 0 }" + 'if (-not (Test-Path -LiteralPath $src)) { exit 0 }' + '$desk = [Environment]::GetFolderPath(''Desktop'')' + 'if ($desk) { Copy-Item -LiteralPath $src -Destination (Join-Path $desk (Split-Path $src -Leaf)) -Force -ErrorAction SilentlyContinue }' + 'exit 0' + ) + Set-Content -Path $ensure -Value $body -Encoding ascii -Force + New-Item -Path $ActiveSetup -Force | Out-Null + Set-ItemProperty -Path $ActiveSetup -Name '(Default)' -Value 'GE Aerospace Engineering Layer icon' + Set-ItemProperty -Path $ActiveSetup -Name 'Version' -Value '1,0' + Set-ItemProperty -Path $ActiveSetup -Name 'IsInstalled' -Value 1 -Type DWord + Set-ItemProperty -Path $ActiveSetup -Name 'StubPath' -Value ('powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File "' + $ensure + '"') + Write-EngLog 'Active Setup registered: each new user gets the icon if the PC is still unconfigured.' +} + +Write-EngLog '=== Install-EngineeringLayer starting ===' +Write-EngLog "Source: $Source" +Write-EngLog "Staged: $StagingRoot" +Write-EngLog "Running from: $PSCommandPath" + +if (Test-Path -LiteralPath $marker) { + Write-EngLog 'Marker present - Engineering Layer already installed.' + if (-not $StageOnly) { Set-EngineeringIcon } + exit 0 +} + +# --- stage the payload out of C:\Deploy ------------------------------------- +$stagedExe = Join-Path $StagingRoot $Installer + +if (Test-Path -LiteralPath $stagedExe) { + Write-EngLog 'Payload already staged outside C:\Deploy - skipping copy.' +} else { + if (-not (Test-Path -LiteralPath $Source)) { + Write-EngLog "Source not found: $Source" 'ERROR' + Write-EngLog 'C:\Deploy was removed (DeleteDeployDir) before the payload was staged. Re-stage from the share:' 'ERROR' + Write-EngLog ' \\172.16.9.1\winpeapps\gea-engineer\Deploy\Applications\extra_engineering' 'ERROR' + exit 2 + } + Write-EngLog 'Copying payload out of C:\Deploy (this is the whole point - do not skip)' + New-Item -ItemType Directory -Force -Path $StagingRoot | Out-Null + # robocopy, not Copy-Item: 653 MB, restartable, and it reports properly. + # Exit codes 0-7 are success, 8+ are failure - inverted vs everything else. + # The copy includes this script, so first logon can run it from here. + $rc = Start-Process -FilePath 'robocopy.exe' ` + -ArgumentList @("`"$Source`"", "`"$StagingRoot`"", '/E', '/R:2', '/W:5', '/NFL', '/NDL', '/NJH', '/NJS') ` + -Wait -PassThru -WindowStyle Hidden + if ($rc.ExitCode -ge 8) { + Write-EngLog "robocopy failed with exit $($rc.ExitCode) - payload NOT staged" 'ERROR' + exit 3 + } + Write-EngLog "Payload staged (robocopy exit $($rc.ExitCode))" +} + +if (-not (Test-Path -LiteralPath $stagedExe)) { + Write-EngLog "Installer missing after staging: $stagedExe" 'ERROR' + exit 4 +} + +if ($StageOnly) { + Write-EngLog 'StageOnly requested - payload is safe from cleanup, not installing.' + exit 0 +} + +# --- install ----------------------------------------------------------------- +# The prereqs (.NET 3.5 sxs + VC++ redists) ship alongside; v3.1 installs them +# itself and is idempotent, so they are staged for it rather than invoked here. +Write-EngLog "Running $Installer (this takes a while - .NET 3.5, VC++, Cygwin, DUU)" +try { + $p = Start-Process -FilePath $stagedExe -ArgumentList '/S' -Wait -PassThru + Write-EngLog "Installer exit code: $($p.ExitCode)" + if ($p.ExitCode -eq 0 -or $p.ExitCode -eq 3010) { + if ($p.ExitCode -eq 3010) { Write-EngLog 'Installer requests a reboot (3010)' 'WARN' } + Set-Content -Path $marker -Value (Get-Date -Format 'o') -Encoding ascii -Force + Write-EngLog 'Engineering Layer installed. Marker written.' + Set-EngineeringIcon + exit 0 + } + Write-EngLog "Installer returned $($p.ExitCode) - NOT marking as installed" 'ERROR' + exit 5 +} catch { + Write-EngLog "Installer failed to start: $_" 'ERROR' + exit 6 +} diff --git a/playbook/pxe_server_setup.yml b/playbook/pxe_server_setup.yml index 736a4ad..8f112be 100644 --- a/playbook/pxe_server_setup.yml +++ b/playbook/pxe_server_setup.yml @@ -1078,6 +1078,21 @@ dest: "{{ samba_share }}/_shared/Applications/extra/pxe-status/Send-ImagingStage.ps1" mode: '0644' + # Engineering Layer wrapper (tracked in the repo; the 650 MB payload it + # installs - engineering_layer/ and prereqs/ - is site content beside it). + # The engineer unattend stages it in specialize and runs it at first logon. + - name: "Engineering Layer: create gea-engineer extra_engineering" + file: + path: "{{ samba_share }}/gea-engineer/Deploy/Applications/extra_engineering" + state: directory + mode: '0755' + + - name: "Engineering Layer: deploy Install-EngineeringLayer.ps1" + copy: + src: "{{ usb_mount }}/engineering/Install-EngineeringLayer.ps1" + dest: "{{ samba_share }}/gea-engineer/Deploy/Applications/extra_engineering/Install-EngineeringLayer.ps1" + mode: '0644' + - name: "Deploy BIOS check script + manifest to winpeapps/_shared/BIOS/" # Path matches what startnet.cmd reads at WinPE boot: # net use B: \\172.16.9.1\winpeapps\_shared