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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
Reference in New Issue
Block a user