Display: install the ShopDB enforce client once AESFMA is reachable

A PXE-imaged display 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

Not a broken configuration - nothing had ever tried. Install-ShopdbKiosk.ps1
downloads itself from {BaseUrl}/installers/kiosk over HTTPS, and ShopDB is only
reachable after the bay joins the AESFMA wifi SSID, so it cannot run during
imaging. Nothing was arranged to run it afterwards.

09-Setup-Display.ps1 now registers 'ShopDB Kiosk Bootstrap' as a SYSTEM task at
boot and every 15 minutes. Install-ShopdbKiosk-WhenOnline.ps1 does nothing until
ShopDB answers, then runs the vendor installer once, verifies BaseUrl and the
enforce task exist, deletes the staged key and unregisters itself. A bay cabled
up days later still self-configures.

It deliberately does not reimplement the installer - it waits, invokes, verifies
and cleans up, so the vendor script can be replaced wholesale without touching
this.

The key file is LABELLED (collector=, fetch=) rather than positional. The two
tokens are not interchangeable and a mix-up is silent: a fetch token in the
collector slot leaves asset reporting broken while everything looks configured.
A bare unlabelled line is ignored rather than guessed at. Missing keys are not
fatal - the fetch token is unnecessary on an IP-allowlisted subnet, and the
installer skips the asset-report task rather than failing.

Staged on the share: the bootstrap, the vendor installer under kiosk\, and the
updated display setup. The key itself is NOT staged yet - see the commit
discussion; it needs to be labelled with which scope it carries first.
This commit is contained in:
cproudlock
2026-08-06 15:58:15 -04:00
parent c4aeaaaa17
commit 7ed30f9b85
2 changed files with 214 additions and 0 deletions

View File

@@ -52,6 +52,40 @@ Write-Host " Edge RelaunchNotificationPeriod=1h"
Write-Host " Edge RelaunchHeadsUpPeriod=1m"
Write-Host " Edge RelaunchWindow=02:00-04:00"
# --- Arm the ShopDB kiosk bootstrap -------------------------------------
# A PXE-imaged display ends up with no GE-Enforce client: the installer pulls
# itself from {BaseUrl}/installers/kiosk over HTTPS, and ShopDB is unreachable
# until the bay joins the AESFMA wifi SSID. So it cannot run now.
#
# Register a SYSTEM task that does nothing until ShopDB answers, then runs the
# vendor installer once and unregisters itself. At boot and every 15 minutes,
# so a bay cabled up days later still self-configures.
Write-Host '=== Arming ShopDB kiosk bootstrap ==='
$bootstrap = Join-Path $PSScriptRoot 'Install-ShopdbKiosk-WhenOnline.ps1'
$taskName = 'ShopDB Kiosk Bootstrap'
if (-not (Test-Path $bootstrap)) {
Write-Warning " $bootstrap not found - kiosk will NOT self-configure. Tell the PXE admin."
} else {
try {
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument ('-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "{0}" -TaskName "{1}"' -f $bootstrap, $taskName)
$trigBoot = New-ScheduledTaskTrigger -AtStartup
$trigRep = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(5) `
-RepetitionInterval (New-TimeSpan -Minutes 15)
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
-StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 30)
Register-ScheduledTask -TaskName $taskName -Action $action `
-Trigger @($trigBoot, $trigRep) -Principal $principal -Settings $settings `
-Force -ErrorAction Stop | Out-Null
Write-Host " Registered '$taskName' (at boot + every 15 min, SYSTEM)"
Write-Host " It waits for ShopDB, installs the enforce client, then removes itself."
} catch {
Write-Warning " Failed to register '$taskName': $_"
}
}
if (Get-Command Send-PxeStatus -ErrorAction SilentlyContinue) {
Send-PxeStatus -Stage '09-Setup-Display: complete' -StageIndex 4 -StageTotal 8
}

View File

@@ -0,0 +1,180 @@
<#
.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
<root>\kiosk\Install-ShopdbKiosk.ps1
<root>\kiosk\shopdb-key.txt (optional) line1 collector.ingest PAT,
line2 geenforce.fetch PAT
.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',
[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 $EnrollmentRoot 'kiosk\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 $EnrollmentRoot 'kiosk\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."
}