Test bays reported 0 installed / 0 skipped / 0 failed and nothing was being processed. The share is a TRANSIENT mount: GE-Enforce.ps1 maps it for the length of its cycle and unmounts at the end - mounted 12:10:02, unmounted 12:10:27 on FB9TP7V3. The shadow task ran on its own 15-minute schedule, so it woke with the drive gone, handed the engine a path that no longer resolved, and the summary zero-filled. A silent nothing, indistinguishable from a healthy no-op, which is why it read as "not processing" rather than as a failure. Register-ShopdbShadow.ps1 is replaced by Invoke-ShopdbShadow.ps1, which runs AS a manifest entry and invokes the runner directly. The share is mounted because the enforce cycle is what called us; there is one cadence instead of two; and there is no task to register, drift, or heal. It also unregisters the superseded task, so bays that already carry it clean themselves up rather than keeping a run that cannot see the share and overwrites the real result with 0/0/0. The runner gains the Test-Path guard that should have been there: an unreachable -ShareManifestPath now logs, writes an event, and reports a real failure naming the reason, instead of running the engine against nothing. That silent zero is what made this expensive to find. Verified on the win11 VM with a substituted drive removed between runs - the same call gives real counts while mapped and the guard when not. The earlier task self-heal is moot now; it was a fix at the wrong layer, and testing against a local path is what hid a mount I had already noted was dynamic.
86 lines
3.9 KiB
PowerShell
86 lines
3.9 KiB
PowerShell
# Invoke-ShopdbShadow.ps1 -- run one shopdb SHADOW cycle, from the manifest.
|
|
#
|
|
# Shadow = fetch the shopdb manifest, diff it against the share manifest, report
|
|
# the cycle to shopdb. The engine still installs FROM THE SHARE, so behaviour is
|
|
# unchanged. It is the observable step before any cutover.
|
|
#
|
|
# WHY THIS RUNS AS A MANIFEST ENTRY AND NOT A SCHEDULED TASK
|
|
#
|
|
# The first version registered a separate 15-minute task. That cannot work:
|
|
# GE-Enforce.ps1 mounts the SFLD share for the length of its own cycle and
|
|
# unmounts it at the end (mounted 12:10:02, unmounted 12:10:27). A task on its
|
|
# own schedule therefore wakes up with the drive gone, hands the engine a path
|
|
# that no longer resolves, and reports 0 installed / 0 skipped / 0 failed - a
|
|
# silent nothing indistinguishable from a healthy no-op. It also meant two
|
|
# cadences that could drift apart, and a registration that had to be healed.
|
|
#
|
|
# Running here removes all of it: the share is mounted because the enforce cycle
|
|
# is what invoked us, the cadence is the fleet's own, and there is no task.
|
|
#
|
|
# Runs as SYSTEM under GE-Enforce, from an entry gated to the test bays.
|
|
# Fail-safe: exits 0 on every path - shadowing must never stop a bay enforcing.
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
|
|
$InstallDir = 'C:\Program Files\GE\Shopfloor'
|
|
$BaseUrl = 'https://tsgwp00525.wjs.geaerospace.net/shopdb'
|
|
$LegacyTask = 'ShopDB GE-Enforce (shadow)'
|
|
|
|
function Write-ShadowLog {
|
|
# To a file as well as the host: the engine records only "ps1: <path>" and
|
|
# an exit code for a PS1 entry, so Write-Host reaches nothing, and with the
|
|
# fail-safe exit 0 a silent early-out looks exactly like success.
|
|
param([string]$Message)
|
|
$line = "[{0}] [shadow] {1}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Message
|
|
Write-Host $line
|
|
try {
|
|
$dir = 'C:\Logs\Shopfloor'
|
|
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
|
|
Add-Content -LiteralPath (Join-Path $dir ('shadow-{0}.log' -f (Get-Date -Format yyyyMMdd))) -Value $line
|
|
} catch { }
|
|
}
|
|
|
|
try {
|
|
# Retire the scheduled task the earlier version left behind. Bays that got
|
|
# it would otherwise keep firing a run that cannot see the share, reporting
|
|
# 0/0/0 over the top of the real result from this one.
|
|
$stale = Get-ScheduledTask -TaskName $LegacyTask -ErrorAction SilentlyContinue
|
|
if ($stale) {
|
|
Unregister-ScheduledTask -TaskName $LegacyTask -Confirm:$false -ErrorAction SilentlyContinue
|
|
Write-ShadowLog "removed the superseded '$LegacyTask' task (it ran outside the share mount)."
|
|
}
|
|
|
|
# Scope is this script's own directory name, never hardcoded: the same file
|
|
# ships from more than one scope and a wrong value would shadow the wrong
|
|
# manifest silently.
|
|
$scopeDir = Split-Path -Parent $PSScriptRoot
|
|
$scope = Split-Path -Leaf $scopeDir
|
|
$shareManifest = Join-Path $scopeDir 'manifest.json'
|
|
|
|
$runner = Join-Path $InstallDir 'Invoke-ShopdbEnforce.ps1'
|
|
$engine = Join-Path $InstallDir 'lib\Install-FromManifest.ps1'
|
|
foreach ($required in @($runner, $engine, $shareManifest)) {
|
|
if (-not (Test-Path -LiteralPath $required)) {
|
|
Write-ShadowLog "MISSING $required - skipping this cycle."
|
|
exit 0
|
|
}
|
|
}
|
|
|
|
# BaseUrl only when it differs, so a hand-set value is not churned.
|
|
$regPath = 'HKLM:\SOFTWARE\GE\ShopDB'
|
|
if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force | Out-Null }
|
|
if ((Get-ItemProperty -Path $regPath -Name BaseUrl -ErrorAction SilentlyContinue).BaseUrl -ne $BaseUrl) {
|
|
Set-ItemProperty -Path $regPath -Name BaseUrl -Value $BaseUrl
|
|
Write-ShadowLog "BaseUrl set to $BaseUrl"
|
|
}
|
|
|
|
Write-ShadowLog "shadowing $scope against $shareManifest"
|
|
& $runner -Scope $scope -EnginePath $engine -ShadowMode -ShareManifestPath $shareManifest
|
|
Write-ShadowLog "cycle complete (runner exit $LASTEXITCODE)."
|
|
exit 0
|
|
}
|
|
catch {
|
|
Write-ShadowLog "FAILED: $_"
|
|
exit 0
|
|
}
|