geenforce: ship shadow mode through the share, and correct the cutover doc
The cheap cutover the doc described - manifest over API, payloads still on the
share - does not work. Measured on the win11 VM against lib 2.6 with the same
manifest and engine, varying only InstallerRoot: the payload cache gives
0 installed / 2 failed ("CMD/BAT not found" under the cache), the scope
directory gives 2 installed / 0 failed.
Two causes. Invoke-ShopdbEnforce.ps1 takes no -InstallerRoot and hardcodes the
payload cache outside shadow mode, which is right for displays where every
entry is inline and wrong for every smb entry. And -IncludeCommon cannot work
for smb payloads at all: they live under their own scope directory, so a merged
manifest needs two roots where the engine takes one, and Join-InstallerPath
rejects rooted values by design so absolute paths are not a way out. The real
dispatcher never merges - it calls the engine once per scope with that scope's
root - and a fix should follow that shape.
Both claims are corrected in the doc rather than deleted, because reading it
was what made this look like a twenty-minute job; it would have failed 29 of 31
entries on the pilot bay.
Shadow mode is unaffected - it points InstallerRoot at the share scope dir,
which is the working path - so the pilot proceeds. Register-ShopdbShadow.ps1
is the reference copy of the script that puts a share-attached bay into shadow:
it writes BaseUrl, then registers the task as SYSTEM. It derives the share
manifest from $PSScriptRoot rather than hardcoding a drive, because GE-Enforce
mounts the share to a letter it picks at run time and SYSTEM has no standing
UNC access. Delivered as a manifest entry gated to one hostname, so the pilot
needs no hands on the bay and heals if it drifts.
This commit is contained in:
79
plugins/geenforce/client/Register-ShopdbShadow.ps1
Normal file
79
plugins/geenforce/client/Register-ShopdbShadow.ps1
Normal file
@@ -0,0 +1,79 @@
|
||||
# Register-ShopdbShadow.ps1 -- put this bay into shopdb SHADOW mode.
|
||||
#
|
||||
# Shadow mode = fetch the shopdb manifest, diff it against the share manifest,
|
||||
# report the cycle to shopdb, and install FROM THE SHARE exactly as today. Zero
|
||||
# behaviour change. It is the observable step before any cutover.
|
||||
#
|
||||
# Runs as SYSTEM under GE-Enforce, from a manifest entry gated to one hostname.
|
||||
# Idempotent: re-registers the task each cycle so drift self-heals, and writes
|
||||
# BaseUrl only when it differs.
|
||||
#
|
||||
# WHY the share manifest is read through the mounted drive: GE-Enforce.ps1
|
||||
# mounts the SFLD share with SFLD credentials before invoking the engine, and
|
||||
# this script runs inside that window. SYSTEM has no standing access to the UNC
|
||||
# path, so the mounted drive is the only path that resolves. The drive letter is
|
||||
# not fixed, so it is derived from where this script is running rather than
|
||||
# hardcoded.
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
$TaskName = 'ShopDB GE-Enforce (shadow)'
|
||||
$InstallDir = 'C:\Program Files\GE\Shopfloor'
|
||||
$BaseUrl = 'https://tsgwp00525.wjs.geaerospace.net/shopdb'
|
||||
$Scope = 'gea-shopfloor-nocollections'
|
||||
|
||||
function Write-ShadowLog {
|
||||
param([string]$Message)
|
||||
$line = "[{0}] [shadow-setup] {1}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Message
|
||||
Write-Host $line
|
||||
}
|
||||
|
||||
try {
|
||||
# --- BaseUrl (the client reads this; no token needed on an allowlisted subnet)
|
||||
$regPath = 'HKLM:\SOFTWARE\GE\ShopDB'
|
||||
if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force | Out-Null }
|
||||
$current = (Get-ItemProperty -Path $regPath -Name BaseUrl -ErrorAction SilentlyContinue).BaseUrl
|
||||
if ($current -ne $BaseUrl) {
|
||||
Set-ItemProperty -Path $regPath -Name BaseUrl -Value $BaseUrl
|
||||
Write-ShadowLog "BaseUrl set to $BaseUrl"
|
||||
}
|
||||
|
||||
$runner = Join-Path $InstallDir 'Invoke-ShopdbEnforce.ps1'
|
||||
$engine = Join-Path $InstallDir 'lib\Install-FromManifest.ps1'
|
||||
foreach ($p in @($runner, $engine)) {
|
||||
if (-not (Test-Path -LiteralPath $p)) {
|
||||
Write-ShadowLog "MISSING $p - the File entries have not landed yet; will retry next cycle."
|
||||
exit 0 # fail-safe: never break the bay, the entry re-runs
|
||||
}
|
||||
}
|
||||
|
||||
# --- the share manifest this scope is enforced from, via the mounted drive.
|
||||
# $PSScriptRoot is <drive>:\<scope>\shopdb-client, so its grandparent is the
|
||||
# scope dir. Deriving it keeps this correct whatever letter GE-Enforce mounted.
|
||||
$scopeDir = Split-Path -Parent $PSScriptRoot
|
||||
$shareManifest = Join-Path $scopeDir 'manifest.json'
|
||||
if (-not (Test-Path -LiteralPath $shareManifest)) {
|
||||
Write-ShadowLog "share manifest not found at $shareManifest - not registering."
|
||||
exit 0
|
||||
}
|
||||
|
||||
$arguments = '-NoProfile -ExecutionPolicy Bypass -File "{0}" -Scope "{1}" -EnginePath "{2}" -ShadowMode -ShareManifestPath "{3}"' `
|
||||
-f $runner, $Scope, $engine, $shareManifest
|
||||
|
||||
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $arguments
|
||||
# RepetitionInterval ALONE - passing RepetitionDuration serializes to a
|
||||
# Duration the Task Scheduler schema rejects.
|
||||
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 15)
|
||||
$principal = New-ScheduledTaskPrincipal -UserId 'NT AUTHORITY\SYSTEM' -RunLevel Highest
|
||||
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
|
||||
|
||||
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger `
|
||||
-Principal $principal -Settings $settings -Force | Out-Null
|
||||
Write-ShadowLog "registered '$TaskName' (every 15 min), shadowing $shareManifest"
|
||||
exit 0
|
||||
}
|
||||
catch {
|
||||
# Fail-safe: a broken setup script must never stop the bay enforcing.
|
||||
Write-ShadowLog "FAILED: $_"
|
||||
exit 0
|
||||
}
|
||||
Reference in New Issue
Block a user