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.
This commit is contained in:
cproudlock
2026-10-16 12:37:50 -04:00
parent 9d75d03b2d
commit 821a927365
4 changed files with 237 additions and 9 deletions

View File

@@ -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. 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) ### PC types (shopfloor sub-types)
`playbook/shopfloor-setup/` contains per-PC-type install logic. The current `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 - `\\172.16.9.1\image-upload` as the destination
- `C:\ProgramData\GEAerospace\MediaCreator\Cache\` as the source - `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 ## Recommended forward state

View File

@@ -258,6 +258,16 @@
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v EnableDynamicContentInWSB /t REG_DWORD /d 0 /f</Path> <Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v EnableDynamicContentInWSB /t REG_DWORD /d 0 /f</Path>
<Description>Disable Search Highlights</Description> <Description>Disable Search Highlights</Description>
</RunSynchronousCommand> </RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>37</Order>
<Path>powershell.exe -NoP -EP Bypass -File "C:\Deploy\Applications\extra_engineering\Install-EngineeringLayer.ps1" -StageOnly</Path>
<!-- Copies the Engineering Layer payload and the script itself to
C:\PreInstall\engineering now, while C:\Deploy certainly exists. The
first-logon install (oobeSystem Order 11) runs from there: a PC that
reboots before the employee signs in has lost C:\Deploy to the
orchestrator's DeleteDeployDir task by then. -->
<Description>Engineering Layer: stage payload out of C:\Deploy</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add"> <RunSynchronousCommand wcm:action="add">
<Order>39</Order> <Order>39</Order>
@@ -382,14 +392,15 @@
</SynchronousCommand> </SynchronousCommand>
<SynchronousCommand wcm:action="add"> <SynchronousCommand wcm:action="add">
<Order>11</Order> <Order>11</Order>
<CommandLine>powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\Deploy\Applications\extra_engineering\Install-EngineeringLayer.ps1"</CommandLine> <CommandLine>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")</CommandLine>
<!-- Copies the 653 MB payload out of C:\Deploy to C:\PreInstall\engineering <!-- Installs from the copy specialize Order 37 staged in
BEFORE installing: the bulk-enrollment orchestrator registers a C:\PreInstall\engineering, so it works after a reboot has removed
DeleteDeployDir task that removes C:\Deploy on the next boot, and this C:\Deploy (DeleteDeployDir). Falls back to C:\Deploy if staging did not
installer (.NET 3.5, VC++, Cygwin, DUU) can reboot part way through. happen. The installer (.NET 3.5, VC++, Cygwin, DUU) can reboot part way
Idempotent, marker-gated. Description is capped at 256 chars, so the through; the script is idempotent and marker-gated, and it makes sure
detail lives here. --> the "Configure my Engineering Layer" icon reaches the employee.
<Description>Engineering Layer: stage payload out of C:\Deploy then install</Description> Description is capped at 256 chars, so the detail lives here. -->
<Description>Engineering Layer: install from the staged copy</Description>
</SynchronousCommand> </SynchronousCommand>
<SynchronousCommand wcm:action="add"> <SynchronousCommand wcm:action="add">
<CommandLine>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</CommandLine> <CommandLine>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</CommandLine>

View File

@@ -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
}

View File

@@ -1078,6 +1078,21 @@
dest: "{{ samba_share }}/_shared/Applications/extra/pxe-status/Send-ImagingStage.ps1" dest: "{{ samba_share }}/_shared/Applications/extra/pxe-status/Send-ImagingStage.ps1"
mode: '0644' 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/" - name: "Deploy BIOS check script + manifest to winpeapps/_shared/BIOS/"
# Path matches what startnet.cmd reads at WinPE boot: # Path matches what startnet.cmd reads at WinPE boot:
# net use B: \\172.16.9.1\winpeapps\_shared # net use B: \\172.16.9.1\winpeapps\_shared