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.
204 lines
8.9 KiB
PowerShell
204 lines
8.9 KiB
PowerShell
# Backup-NtlarsSettings.ps1
|
|
#
|
|
# Captures this PC's LIVE NTLARS/DNC registry settings and posts them to ShopDB,
|
|
# which files them against the MACHINE (not this PC) and keeps a revision
|
|
# history. A tech can then re-download the .reg from the machine's page instead
|
|
# of hunting for a per-machine file on the share.
|
|
#
|
|
# Runs from the SFLD share every GE-Enforce cycle as a Type=PS1 manifest entry
|
|
# with DetectionMethod=Always. Updating this file on the share changes fleet
|
|
# behaviour on the next cycle - there is no local copy to heal.
|
|
#
|
|
# WHY WOW6432Node IS EXPLICIT:
|
|
# NTLARS is a 32-bit app, so its settings physically live under
|
|
# HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC. GE-Enforce runs this
|
|
# script in 64-bit PowerShell, where HKLM:\SOFTWARE\GE Aircraft Engines\DNC
|
|
# is a DIFFERENT (usually absent) key. Reading the unredirected path would
|
|
# find nothing and back up an empty config - silently, every cycle. The path
|
|
# below is therefore spelled out and never abbreviated.
|
|
#
|
|
# Note the asymmetry with what NTLARS itself writes: its Save... button
|
|
# exports WITHOUT the WOW6432Node segment. ShopDB accepts either dialect and
|
|
# stores a dialect-neutral projection, so this script does not need to care.
|
|
#
|
|
# THROTTLE, LOGGING, CREDENTIALS:
|
|
# All handled by common\lib\ShopdbBackupClient.psm1, which every backup kind
|
|
# shares. GE-Enforce fires this every cycle, so the module holds the marker
|
|
# file, exits early until backups_intervalhours has elapsed, and logs a
|
|
# no-change outcome ONCE rather than every five minutes. The interval comes
|
|
# from ShopDB, so cadence is changed centrally rather than by editing this
|
|
# file on the share.
|
|
#
|
|
# Always exits 0 so the GE-Enforce "last run result" stays clean. Failures are
|
|
# logged, never thrown.
|
|
|
|
param(
|
|
[string]$BaseUrl = 'https://tsgwp00525.wjs.geaerospace.net/shopdb',
|
|
[int]$TimeoutSec = 30,
|
|
# Force a post regardless of the throttle. For a tech capturing a
|
|
# known-good config on demand.
|
|
[switch]$Force
|
|
)
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
|
|
# The only path this script still owns. Log, marker, state, collector key,
|
|
# enrollment and base URL all moved to ShopdbBackupClient, which names its files
|
|
# per KIND so two backup kinds on one bay cannot collide.
|
|
$DNCKEY = 'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC'
|
|
|
|
function Convert-RegTypeName {
|
|
param($Kind)
|
|
switch ("$Kind") {
|
|
'String' { 'REG_SZ' }
|
|
'ExpandString' { 'REG_EXPAND_SZ' }
|
|
'DWord' { 'REG_DWORD' }
|
|
'QWord' { 'REG_QWORD' }
|
|
'MultiString' { 'REG_MULTI_SZ' }
|
|
'Binary' { 'REG_BINARY' }
|
|
default { 'REG_SZ' }
|
|
}
|
|
}
|
|
|
|
function Get-DncKeys {
|
|
<#
|
|
The DNC key and every subkey, root first. Split out from
|
|
Export-DncToReg so the formatting logic can be exercised against mock
|
|
keys on a machine with no registry (see Test-RegExport.ps1).
|
|
#>
|
|
$keys = @(Get-Item -Path $DNCKEY -EA Stop)
|
|
$keys += @(Get-ChildItem -Path $DNCKEY -Recurse -EA SilentlyContinue)
|
|
return $keys
|
|
}
|
|
|
|
function Export-DncToReg {
|
|
<#
|
|
Emits .reg text in the WOW6432Node dialect for the supplied keys.
|
|
|
|
Built by hand rather than shelling out to `reg export` because reg.exe
|
|
writes UTF-16 to a temp file we would then have to read back, and
|
|
because this keeps the value types explicit instead of reparsing them.
|
|
|
|
Takes the key list as a parameter so it can be tested with mocks; the
|
|
escaping and dword formatting here are the part that would corrupt a
|
|
backup silently and only surface at restore time.
|
|
#>
|
|
param([Parameter(Mandatory)]$Keys)
|
|
|
|
$lines = @('Windows Registry Editor Version 5.00', '')
|
|
$lines += "; NTLARS DNC Registry Backup"
|
|
$lines += "; Computer: $env:COMPUTERNAME"
|
|
$lines += "; Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
|
$lines += ''
|
|
|
|
foreach ($key in $Keys) {
|
|
# PSPath -> the literal HKEY_LOCAL_MACHINE\... form a .reg file needs.
|
|
$path = $key.Name -replace '^HKEY_LOCAL_MACHINE', 'HKEY_LOCAL_MACHINE'
|
|
$lines += "[$path]"
|
|
foreach ($name in $key.GetValueNames()) {
|
|
$kind = Convert-RegTypeName $key.GetValueKind($name)
|
|
$data = $key.GetValue($name)
|
|
$lhs = if ($name -eq '') { '@' } else {
|
|
# .NET replacement strings do NOT process backslash escapes, so
|
|
# the replacement is the literal output: '\\' emits two
|
|
# backslashes, which is what .reg escaping wants. Writing
|
|
# '\\\\' here emits FOUR and silently corrupts every path-valued
|
|
# setting - verified on Windows before this was fixed.
|
|
'"{0}"' -f ($name -replace '\\', '\\' -replace '"', '\"')
|
|
}
|
|
switch ($kind) {
|
|
'REG_DWORD' {
|
|
$lines += ('{0}=dword:{1:x8}' -f $lhs, [uint32]$data)
|
|
}
|
|
'REG_BINARY' {
|
|
$hex = ($data | ForEach-Object { '{0:x2}' -f $_ }) -join ','
|
|
$lines += ('{0}=hex:{1}' -f $lhs, $hex)
|
|
}
|
|
'REG_QWORD' {
|
|
$bytes = [BitConverter]::GetBytes([uint64]$data)
|
|
$hex = ($bytes | ForEach-Object { '{0:x2}' -f $_ }) -join ','
|
|
$lines += ('{0}=hex(b):{1}' -f $lhs, $hex)
|
|
}
|
|
'REG_MULTI_SZ' {
|
|
$joined = (($data -join "`0") + "`0`0")
|
|
$bytes = [Text.Encoding]::Unicode.GetBytes($joined)
|
|
$hex = ($bytes | ForEach-Object { '{0:x2}' -f $_ }) -join ','
|
|
$lines += ('{0}=hex(7):{1}' -f $lhs, $hex)
|
|
}
|
|
'REG_EXPAND_SZ' {
|
|
$bytes = [Text.Encoding]::Unicode.GetBytes(("$data" + "`0"))
|
|
$hex = ($bytes | ForEach-Object { '{0:x2}' -f $_ }) -join ','
|
|
$lines += ('{0}=hex(2):{1}' -f $lhs, $hex)
|
|
}
|
|
default {
|
|
$escaped = ("$data" -replace '\\', '\\' -replace '"', '\"')
|
|
$lines += ('{0}="{1}"' -f $lhs, $escaped)
|
|
}
|
|
}
|
|
}
|
|
$lines += ''
|
|
}
|
|
return ($lines -join "`r`n") + "`r`n"
|
|
}
|
|
|
|
# =============================================================================
|
|
# Main
|
|
#
|
|
# Guarded so the file can be DOT-SOURCED to get the functions without running a
|
|
# backup. Test-RegExport.ps1 relies on this to exercise Export-DncToReg against
|
|
# mock keys on a machine with no registry.
|
|
# =============================================================================
|
|
|
|
if ($MyInvocation.InvocationName -eq '.') { return }
|
|
|
|
# The plumbing - key lookup, base URL, interval, throttle, logging, the POST and
|
|
# its response handling - lives in ShopdbBackupClient. It used to live here, and
|
|
# being the only implementation meant every defect in it was invisible: the
|
|
# interval read fell back to a hardcoded 24h for months because nobody had a
|
|
# second copy to compare against, and the log wrote three lines every five
|
|
# minutes whatever happened. Seven other Backup-*.ps1 scripts on this share
|
|
# capture device configs and post none of them; they can now do so without
|
|
# inheriting a copy of all that.
|
|
#
|
|
# What stays here is the part that is actually about NTLARS: where its registry
|
|
# lives, and how to turn it into .reg text.
|
|
Import-Module (Join-Path $PSScriptRoot '..\lib\ShopdbBackupClient.psm1') -Force
|
|
|
|
if (-not (Test-Path $DNCKEY)) {
|
|
# Not an error: plenty of PC types have no NTLARS at all. On those PCs this
|
|
# is the permanent state, so it is said once rather than 288 times a day.
|
|
Write-ShopdbQuietState -Kind 'ntlars' -State 'no-dnc-key' -Message (
|
|
"No DNC key at $DNCKEY - NTLARS is not installed on this PC. Nothing to do.")
|
|
exit 0
|
|
}
|
|
|
|
# NTLARS keeps its own MachineNo, used only when pc-config.txt has none. ShopDB
|
|
# compares the two and warns on a mismatch rather than silently trusting one.
|
|
$ctx = Initialize-ShopdbBackup -Kind 'ntlars' -Force:$Force `
|
|
-BaseUrl $(if ($PSBoundParameters.ContainsKey('BaseUrl')) { $BaseUrl } else { '' }) `
|
|
-MachineNumberFallback {
|
|
try {
|
|
$general = Get-ItemProperty -Path (Join-Path $DNCKEY 'General') -EA Stop
|
|
if ($general.MachineNo) { return ([string]$general.MachineNo).Trim() }
|
|
} catch { }
|
|
return ''
|
|
}
|
|
if (-not $ctx.Proceed) { exit 0 }
|
|
|
|
try {
|
|
$regText = Export-DncToReg -Keys (Get-DncKeys)
|
|
} catch {
|
|
Write-ShopdbBackupLog -Kind 'ntlars' -Message "Failed to read the DNC key: $_"
|
|
exit 0
|
|
}
|
|
|
|
# UTF-16LE + BOM, matching what regedit and NTLARS emit. ShopDB sniffs the BOM,
|
|
# so this is belt-and-braces rather than strictly required.
|
|
$bytes = [byte[]](0xFF, 0xFE) + [Text.Encoding]::Unicode.GetBytes($regText)
|
|
|
|
[void](Send-ShopdbBackup -Context $ctx -Bytes $bytes `
|
|
-SourceFileName ("{0}.reg" -f $ctx.MachineNumber) -TimeoutSec $TimeoutSec)
|
|
|
|
Write-ShopdbBackupLog -Kind 'ntlars' -Message '=== Backup-NtlarsSettings end ==='
|
|
exit 0
|