Files
pxe-server/playbook/shopfloor-setup/common/scripts/Set-ShopdbCollectorKey.ps1
cproudlock 54cbe6b5d6 Bring the share's common scripts under version control
Sixteen files that run on every shopfloor PC existed only on the SFLD share.
The cost showed up while debugging the NTLARS backup: the script that posts to
ShopDB could not be read, reviewed or diffed, so its behaviour was inferred
from log output for most of a day. It turned out to hold a silent fallback that
had been governing the whole fleet for months.

Imported as-is from tsgwp00525-v2, no edits:

  lib/ShopdbBackupClient.psm1        the shared backup client
  scripts/Backup-NtlarsSettings.ps1  converted to use it
  scripts/Set-ShopdbCollectorKey.ps1 collector credential delivery
  scripts/Test-RegExport.ps1         exercises the .reg codec with mocks
  scripts/Set-EventSaver*.ps1        kiosk power / screensaver / disable
  scripts/Setup-OpenText.*           OpenText install + toolbar
  scripts/Migrate-PCType.ps1, Select-KioskType.ps1, Set-FmsHostsEntry.ps1,
  scripts/ensure-vnc-firewall.ps1, Install-AcroReader.cmd, Install-Oracle11r2.cmd

lib/Install-FromManifest.ps1 is also updated from the share, which was 37 lines
AHEAD of this repo and purely additive: the Add-EnforceResult reporting added
during the kiosk API cutover, done live and never committed back. Nothing was
removed.

Checked for embedded secrets before committing; there are none.
Set-ShopdbCollectorKey deliberately reads its token from a sibling file on the
share rather than holding it, so the script is safe to track.

The share remains what actually runs. This makes it reviewable, and makes the
next drift visible as a diff rather than a surprise.
2026-08-11 12:36:38 -04:00

138 lines
5.4 KiB
PowerShell

# Set-ShopdbCollectorKey.ps1
#
# Delivers the ShopDB collector credential to every shopfloor PC by writing
# HKLM:\SOFTWARE\GE\ShopDB (BaseUrl + CollectorKey), the same contract the
# display kiosks already use. Anything that posts to /api/collector/* reads it
# from there - today Backup-NtlarsSettings.ps1, tomorrow whatever else reports.
#
# WHY A KEY IS NEEDED AT ALL:
# The GE-Enforce manifest/payload FETCH honours an IP allowlist, so a bay on a
# trusted subnet pulls config with no token. Collector INGEST does not: it
# accepts only a collector-scoped token. That asymmetry is deliberate - fetch
# reads config we already publish, ingest WRITES asset data, and an allowlist
# alone would let anything on the subnet post revisions.
#
# WHERE THE SECRET LIVES:
# NOT in manifest.json, and not in this script. It is read from a sibling file
# on the share (see $KEYCONFIG below), so:
# - the manifest stays free of secrets and safe to read/diff
# - rotating the token is replacing ONE file, not editing a manifest
# The file inherits the share's ACL, which grants file-level reads only to the
# SFLD user. Treat it as a secret: scope the token to collector.ingest ONLY,
# so a leak cannot read or mutate anything else.
#
# IDEMPOTENT: compares current registry values against desired and writes only
# on a difference, so DetectionMethod=Always costs one registry read per cycle
# after the first run. Rotating the key on the share re-converges the fleet on
# the next cycle with no other action.
#
# Always exits 0 so the GE-Enforce "last run result" stays clean.
param(
# Override for a one-off run; normally read from the sibling config file.
[string]$CollectorKey,
[string]$BaseUrl
)
$ErrorActionPreference = 'Continue'
$SHOPDBREG = 'HKLM:\SOFTWARE\GE\ShopDB'
$KEYCONFIG = Join-Path $PSScriptRoot '..\configs\shopdb-collector.txt'
$LOGDIR = 'C:\Logs\Shopfloor'
$LOGFILE = Join-Path $LOGDIR 'shopdb-collector-key.log'
if (-not (Test-Path $LOGDIR)) {
New-Item -ItemType Directory -Path $LOGDIR -Force -EA SilentlyContinue | Out-Null
}
function Log {
param([string]$Message)
$line = '[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Message
Add-Content -Path $LOGFILE -Value $line -EA SilentlyContinue
Write-Host $line
}
function Read-KeyConfig {
<#
LABELLED lines only:
baseurl=https://host/shopdb
collector=<collector.ingest token>
A bare line is ignored rather than guessed at - a fetch token landing in
the collector slot would leave reporting broken while looking configured.
#>
$result = @{ BaseUrl = ''; CollectorKey = '' }
if (-not (Test-Path $KEYCONFIG)) { return $result }
foreach ($line in (Get-Content $KEYCONFIG -EA SilentlyContinue)) {
$t = $line.Trim()
if (-not $t -or $t.StartsWith('#')) { continue }
if ($t -match '^(?i)collector\s*=\s*(.+)$') { $result.CollectorKey = $Matches[1].Trim() }
elseif ($t -match '^(?i)baseurl\s*=\s*(.+)$') { $result.BaseUrl = $Matches[1].Trim().TrimEnd('/') }
else { Log " ignoring unlabelled line in $(Split-Path $KEYCONFIG -Leaf)" }
}
return $result
}
Log '=== Set-ShopdbCollectorKey start ==='
$cfg = Read-KeyConfig
if (-not $CollectorKey) { $CollectorKey = $cfg.CollectorKey }
if (-not $BaseUrl) { $BaseUrl = $cfg.BaseUrl }
if (-not $CollectorKey) {
# Not an error: an unconfigured share is the normal state before a site
# issues its token. Say exactly what to do rather than failing silently.
Log "No collector key configured. Put a 'collector=<token>' line in:"
Log " $KEYCONFIG"
Log "Nothing written."
exit 0
}
# --- compare before writing ------------------------------------------------
$current = $null
try { $current = Get-ItemProperty -Path $SHOPDBREG -EA Stop } catch { }
$needKey = (-not $current) -or ($current.CollectorKey -ne $CollectorKey)
$needUrl = $BaseUrl -and ((-not $current) -or ($current.BaseUrl -ne $BaseUrl))
if (-not $needKey -and -not $needUrl) {
Log 'Registry already matches - nothing to do.'
Log '=== Set-ShopdbCollectorKey end ==='
exit 0
}
try {
if (-not (Test-Path $SHOPDBREG)) { New-Item -Path $SHOPDBREG -Force | Out-Null }
if ($needUrl) {
New-ItemProperty -Path $SHOPDBREG -Name BaseUrl -Value $BaseUrl `
-PropertyType String -Force -EA Stop | Out-Null
Log "Set BaseUrl = $BaseUrl"
}
if ($needKey) {
New-ItemProperty -Path $SHOPDBREG -Name CollectorKey -Value $CollectorKey `
-PropertyType String -Force -EA Stop | Out-Null
Log "Set CollectorKey (length $($CollectorKey.Length)) - value not logged"
}
# Lock the key down to SYSTEM + Administrators, matching what
# Install-ShopdbKiosk does on displays. Without this the value is readable
# by any interactive user, and a shopfloor PC is a shared login.
try {
$acl = Get-Acl $SHOPDBREG
$acl.SetAccessRuleProtection($true, $false)
foreach ($who in 'SYSTEM', 'Administrators') {
$acl.AddAccessRule((New-Object Security.AccessControl.RegistryAccessRule(
$who, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')))
}
Set-Acl -Path $SHOPDBREG -AclObject $acl -EA Stop
Log 'ACL set: SYSTEM + Administrators only.'
} catch {
Log "WARNING - could not tighten the ACL: $_"
}
} catch {
Log "FAILED to write ${SHOPDBREG}: $_"
}
Log '=== Set-ShopdbCollectorKey end ==='
exit 0