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