New Engineering Layer tab on the engineer image page. A site uploads Engineering_Install_v<ver>.exe (keeps prereqs) or a .zip of the whole extra_engineering payload. The upload is streamed to disk, checked (MZ header, no unsafe zip paths) and swapped in; the replaced payload is archived (two kept) with a Restore button. Install-EngineeringLayer.ps1 stays repo-owned and now installs the newest Engineering_Install_v*.exe. KB: Part 4 becomes Site Content (enrollment package, Engineering Layer, Blancco site settings, Shop Floor apps) plus a new Adding Shop Floor Apps section for preinstall.json/postppkg.json. WJ examples removed.
191 lines
9.5 KiB
PowerShell
191 lines
9.5 KiB
PowerShell
# 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',
|
|
# Relative to StagingRoot. Empty: the newest
|
|
# engineering_layer\Engineering_Install_v<version>.exe (uploaded on the
|
|
# PXE webapp, engineer image > Engineering Layer).
|
|
[string]$Installer = '',
|
|
[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 -------------------------------------
|
|
function Find-Installer([string]$Dir) {
|
|
Get-ChildItem -LiteralPath (Join-Path $Dir 'engineering_layer') -Filter 'Engineering_Install_v*.exe' -File -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Name -match '^Engineering_Install_v(\d+(\.\d+)*)\.exe$' } |
|
|
Sort-Object { $v = ($_.Name -replace '^Engineering_Install_v', '') -replace '\.exe$', ''; if ($v -notmatch '\.') { $v += '.0' }; [version]$v } -Descending |
|
|
Select-Object -First 1
|
|
}
|
|
if (-not $Installer) {
|
|
$found = Find-Installer $StagingRoot
|
|
if (-not $found) { $found = Find-Installer $Source }
|
|
$Installer = if ($found) { 'engineering_layer\' + $found.Name } else { 'engineering_layer\Engineering_Install_v3.1.exe' }
|
|
}
|
|
Write-EngLog "Installer: $Installer"
|
|
$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
|
|
}
|