Files
pxe-server/playbook/shopfloor-setup/gea-shopfloor-display/Install-ShopdbKiosk-WhenOnline.ps1
cproudlock d5cf255443 Kiosk bootstrap: look for the installer where staging actually puts it
The bootstrap looked for C:\Enrollment\kiosk\Install-ShopdbKiosk.ps1, but
startnet copies the type-specific tree to
C:\Enrollment\shopfloor-setup\gea-shopfloor-display\ - so the payload lands
beside the bootstrap, not under C:\Enrollment.

As written it would have logged "Installer not staged" on every 15-minute cycle
and never installed anything, with no error anywhere. Exactly the silent-failure
shape this whole day has been about, and it would have looked like the API being
unreachable.

Resolves via a -KioskRoot parameter defaulting to kiosk\ next to the script, so
the bootstrap and its payload travel together regardless of where the tree is
staged. display-type.txt still comes from EnrollmentRoot, which is correct - that
one really is at C:\Enrollment.
2026-08-06 16:12:41 -04:00

190 lines
7.9 KiB
PowerShell

<#
.SYNOPSIS
Run the ShopDB kiosk installer as soon as ShopDB becomes reachable.
.DESCRIPTION
A PXE-imaged display currently ends up with NO GE-Enforce client at all.
Confirmed on 579C144, 2026-08-06:
is the client installed? NOT FOUND
scheduled tasks that would run it? NONE
The client is delivered by Install-ShopdbKiosk.ps1, which downloads itself
from {BaseUrl}/installers/kiosk over HTTPS - and ShopDB is only reachable
once the bay has joined the AESFMA wifi SSID. So the installer cannot run
during imaging, and nothing was arranged to run it afterwards.
This closes that gap. Imaging stages the installer and the key; this waiter
runs as SYSTEM on a schedule, does nothing until ShopDB answers, then runs
the installer once and removes itself.
Deliberately does NOT reimplement the installer. It waits, invokes, verifies
and cleans up - the vendor script keeps doing the actual work, so it can be
replaced wholesale without touching this.
.PARAMETER EnrollmentRoot
Where imaging staged things. Expects:
<root>\display-type.txt Dashboard | Lobby | 3DPrintRoom
and, beside this script (see -KioskRoot):
kiosk\Install-ShopdbKiosk.ps1
kiosk\shopdb-key.txt (optional) labelled: collector=..., fetch=...
.PARAMETER TaskName
The scheduled task to remove once installation succeeds.
.NOTES
The key file is deleted after a successful install. The installer writes the
secrets into HKLM:\SOFTWARE\GE\ShopDB, which is ACLed to SYSTEM and
Administrators - a better home than a file readable by any local user until
lockdown. Bounding that exposure to the imaging window is the point.
#>
[CmdletBinding()]
param(
[string]$EnrollmentRoot = 'C:\Enrollment',
# Where the staged installer and key live. Defaults to kiosk\ NEXT TO THIS
# SCRIPT, not under EnrollmentRoot: startnet copies the whole type-specific
# tree to
# C:\Enrollment\shopfloor-setup\gea-shopfloor-display\
# so the payload arrives beside this file, not at C:\Enrollment\kiosk.
# Getting this wrong is silent - the bootstrap just logs "not staged" every
# cycle and never installs anything.
[string]$KioskRoot = (Join-Path $PSScriptRoot 'kiosk'),
[string]$BaseUrl = 'https://tsgwp00525.wjs.geaerospace.net/shopdb',
[string]$TaskName = 'ShopDB Kiosk Bootstrap',
[int]$TimeoutSeconds = 30
)
$ErrorActionPreference = 'Continue'
$logDir = 'C:\Logs\ShopDB'
$log = Join-Path $logDir 'kiosk-bootstrap.log'
New-Item -ItemType Directory -Path $logDir -Force -EA SilentlyContinue | Out-Null
function Log { param([string]$m)
$line = "{0} {1}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $m
Write-Host $line
Add-Content -Path $log -Value $line -EA SilentlyContinue
}
function Remove-Self {
try {
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -EA Stop
Log "Unregistered '$TaskName' - nothing further to do."
} catch { Log "Could not unregister '$TaskName': $_" }
}
Log "=== kiosk bootstrap ==="
# --- already done? ------------------------------------------------------
# Both conditions, not just the registry key: BaseUrl alone can be present from
# a partial run, and the enforce task is what actually proves an install.
$reg = 'HKLM:\SOFTWARE\GE\ShopDB'
$haveCfg = (Test-Path $reg) -and ((Get-ItemProperty $reg -EA SilentlyContinue).BaseUrl)
$haveTask = [bool](Get-ScheduledTask -EA SilentlyContinue |
Where-Object { $_.TaskName -match '(?i)shopdb.*enforce' })
if ($haveCfg -and $haveTask) {
Log "Already installed (BaseUrl set, enforce task present)."
Remove-Self
return
}
# --- what kind of display is this? --------------------------------------
$dtFile = Join-Path $EnrollmentRoot 'display-type.txt'
if (-not (Test-Path $dtFile)) {
Log "No $dtFile - cannot choose a DisplayType. Leaving the task armed."
return
}
$displayType = (Get-Content $dtFile -First 1).Trim()
if ($displayType -notin @('Dashboard','Lobby','3DPrintRoom')) {
Log "display-type.txt says '$displayType', which the installer will reject. Leaving armed."
return
}
Log "DisplayType: $displayType"
$installer = Join-Path $KioskRoot 'Install-ShopdbKiosk.ps1'
if (-not (Test-Path $installer)) {
Log "Installer not staged at $installer. Leaving armed."
return
}
# --- is ShopDB reachable yet? -------------------------------------------
# Expected to fail until the bay joins AESFMA. That is the whole reason this
# script exists, so a failure here is logged quietly and retried, not raised.
$probe = "$($BaseUrl.TrimEnd('/'))/api/docs"
try {
$r = Invoke-WebRequest -Uri $probe -UseBasicParsing -TimeoutSec $TimeoutSeconds -EA Stop
Log "ShopDB reachable (HTTP $($r.StatusCode))."
} catch {
$code = try { $_.Exception.Response.StatusCode.value__ } catch { $null }
if ($code) {
# Answered at all = reachable. 401/403 just means no token on this probe.
Log "ShopDB reachable (HTTP $code)."
} else {
Log "Not reachable yet - waiting for AESFMA. ($($_.Exception.Message))"
return
}
}
# --- keys ----------------------------------------------------------------
$keyFile = Join-Path $KioskRoot 'shopdb-key.txt'
$collectorKey = ''
$fetchToken = ''
if (Test-Path $keyFile) {
# LABELLED format, because the two tokens are not interchangeable and a
# positional mix-up is silent: a fetch token in the collector slot leaves
# asset reporting broken while everything looks configured.
#
# collector=<collector.ingest PAT> always needed for asset reporting
# fetch=<geenforce.fetch PAT> optional when the subnet is allowlisted
#
# A bare single line is REJECTED rather than guessed at.
foreach ($line in (Get-Content $keyFile -EA SilentlyContinue)) {
$t = $line.Trim()
if (-not $t -or $t.StartsWith('#')) { continue }
if ($t -match '^(?i)collector\s*=\s*(.+)$') { $collectorKey = $Matches[1].Trim() }
elseif ($t -match '^(?i)fetch\s*=\s*(.+)$') { $fetchToken = $Matches[1].Trim() }
else {
Log "Key file line is not labelled 'collector=' or 'fetch=' - ignoring it rather than guessing which token it is."
}
}
Log ("Key file present (collector={0}, fetch={1})" -f `
$(if ($collectorKey) { 'yes' } else { 'no' }), $(if ($fetchToken) { 'yes' } else { 'no' }))
} else {
# Not fatal. The fetch token is unnecessary when the kiosk subnet is
# IP-allowlisted; only the asset-report task needs the collector key, and
# the installer skips that task rather than failing.
Log "No key file - installing without tokens (allowlisted subnets still work; no asset reporting)."
}
# --- run the vendor installer -------------------------------------------
$args = @{ DisplayType = $displayType; BaseUrl = $BaseUrl }
if ($collectorKey) { $args.CollectorKey = $collectorKey }
if ($fetchToken) { $args.ShopdbToken = $fetchToken }
Log "Running $installer ..."
try {
& $installer @args 2>&1 | ForEach-Object { Log " $_" }
} catch {
Log "Installer threw: $_"
return
}
# --- verify, then clean up ----------------------------------------------
$haveCfg = (Test-Path $reg) -and ((Get-ItemProperty $reg -EA SilentlyContinue).BaseUrl)
$haveTask = [bool](Get-ScheduledTask -EA SilentlyContinue |
Where-Object { $_.TaskName -match '(?i)shopdb.*enforce' })
if ($haveCfg -and $haveTask) {
Log "Install verified: BaseUrl set and enforce task registered."
if (Test-Path $keyFile) {
# The installer has moved the secrets into HKLM (SYSTEM/Admins ACL).
# The staged copy is readable by any local user until lockdown, so it
# goes now rather than lingering.
Remove-Item $keyFile -Force -EA SilentlyContinue
Log "Removed staged key file."
}
Remove-Self
} else {
Log "Installer ran but verification FAILED (BaseUrl=$haveCfg, enforceTask=$haveTask). Leaving armed to retry."
}