# 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 :\\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 }