Consolidates per-type enforcers (CMM, Keyence, Machine, Common, Acrobat)
into one dispatcher driven by pc-type.txt + site-config and a share-side
manifest layout. Same share is now the single source of truth for routine
software updates without re-imaging.
Runtime:
common/GE-Enforce.ps1 SYSTEM scheduled task. Reads
common/manifest.json plus optional
<pcType>/manifest.json and
<pcType-subType>/manifest.json.
Dispatches each entry through the lib.
Writes _outputs/logs/<hostname>/status.json
on the share after each cycle for fleet
monitoring.
common/Register-GEEnforce.ps1 Task registration. Triggers: AtLogOn +
every 5 min (jittered per-PC from
hostname hash) + daily at 05:45,
13:45, 21:45 EST shift windows.
Unregisters legacy per-type tasks on
install so the two coexist at most for
the duration of a single enforce cycle.
common/Deploy-GEEnforce.ps1 Retrofit helper for already-imaged PCs
(admin-run; copies runtime + registers
task + optional immediate trigger).
Library (common/lib/Install-FromManifest.ps1):
- New Type values: PS1, BAT, File, Registry, INF
- New DetectionMethod values: Always, MarkerFile, ValueMatches, pnputil
- TargetHostnames filter (exact + -like wildcards, ANDed with PCTypes)
- Schema version check (logs WARN on manifest newer than lib MAJOR)
- Auto-writes MarkerFile on successful one-shot PS1/BAT/CMD runs
- MSI log scan on failure surfaces meaningful install errors
- Lib version bumped 2.0 -> 2.1 for TargetHostnames
Observability:
common/monitor-fleet-status.py Scans _outputs/logs/*/status.json for
stale check-ins, failed scopes, and
version drift. Respects scope (dir-name),
PCTypes, and TargetHostnames filters so
entries excluded from a PC do not
false-flag as drift.
Regression harness:
common/test/ Parameterized VM harness + README
covering every action type plus
rollback, bad/missing SFLD creds, and
schema versioning.
Imaging integration:
Run-ShopfloorSetup.ps1 now stages GE-Enforce.ps1 and lib to
C:\Program Files\GE\Shopfloor\ and invokes Register-GEEnforce.ps1
at the end of setup. Legacy Register-CommonEnforce invocation is
kept for the transition; it and the legacy per-type enforcer files
are dead code once Register-GEEnforce runs and will be removed in a
dedicated cleanup pass.
Standard-Machine manifest:
eDNC entry bumped 6.4.3 -> 6.4.5. DetectionValue pinned to the
4-part FileVersion 6.4.5.0 verified against a fresh install in the
Win11 analyzer VM. UDC DetectionValue pinned to 1.0.34 (registry
stores 3-part for UDC; verified live).
scripts/mirror-from-gold.sh:
Restructured around share-root rsyncs (one pass per Samba share)
to close gaps in the prior per-subdir layout: winpeapps/_shared/
Applications (7.5 GB of Adobe + fonts + Java + Office + OpenText
+ printdrivers + wireless + Zscaler), additional winpeapps image
types, and enrollment flat-layout root files. Adds
--skip-clonezilla and --skip-reports.
Verified end-to-end in the Win11 analyzer VM:
- Every action Type and DetectionMethod round-tripped
- PCTypes filter (Oracle excluded on Shopfloor, Firefox included
on Shopfloor and DESKTOP-*, excluded elsewhere)
- TargetHostnames filter (exact, wildcard, no-match)
- Upgrade path: XML hash bump + fleet re-copy
- Rollback path: history-archive restore propagates via enforcer,
fleet converges back without per-PC intervention
- Status writeback + monitor script drift detection
- Graceful degradation on bad creds, missing creds, share
unreachable (all exit 0, log clearly, retry next cycle)
Not in this commit (follow-ups):
- Retire legacy per-type *-Enforce.ps1 files and simplify
09-Setup-*.ps1 scripts (coordinated multi-file cleanup)
- Stage 2b: InUseCheck close-and-reopen, ApplyMode gating,
UpdateWindow, .apply-now.txt sentinel, BITS pre-staging,
1618 mutex retry, PostInstallCheck, Uninstall action
- Management app (manifest CRUD + deploy + rollback + fleet view)
- ShopFloor autologon persistence bug (deferred for next imaging
attempt with live registry evidence)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
102 lines
4.3 KiB
PowerShell
102 lines
4.3 KiB
PowerShell
# Register-GEEnforce.ps1 - Registers "GE Shopfloor Enforce" scheduled task.
|
|
#
|
|
# Idempotent: deletes an existing task with the same name first. Also
|
|
# unregisters the legacy per-type enforcer tasks so they don't race.
|
|
#
|
|
# Triggers (all run as SYSTEM, RunLevel=Highest):
|
|
# - AtLogOn for first-boot catch-up
|
|
# - Repetition every 5 min for fleet auto-update (jittered start offset
|
|
# in the first 5 min window to spread 200 PCs)
|
|
# - Shift-change windows 05:45, 13:45, 21:45 EST (30-min window each;
|
|
# handled by running the task at window start
|
|
# and letting the lib run anything not yet
|
|
# applied - Stage 2b will gate ApplyMode=Nightly
|
|
# entries to only fire inside a window).
|
|
#
|
|
# Called from Run-ShopfloorSetup.ps1 at imaging time. Also usable manually
|
|
# to re-register on an existing PC.
|
|
|
|
param(
|
|
[string]$EnforcerPath = 'C:\Program Files\GE\Shopfloor\GE-Enforce.ps1'
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$taskName = 'GE Shopfloor Enforce'
|
|
$legacyTasks = @(
|
|
'GE Common Enforce',
|
|
'GE Acrobat Enforce',
|
|
'GE Shopfloor Machine Apps Enforce',
|
|
'GE Machine Enforce',
|
|
'GE CMM Enforce',
|
|
'GE Keyence Enforce'
|
|
)
|
|
|
|
function Write-RegisterLog { param([string]$m) Write-Host "[Register-GEEnforce] $m" }
|
|
|
|
# Unregister legacy per-type enforcers so only GE-Enforce drives the fleet.
|
|
foreach ($name in $legacyTasks) {
|
|
$t = Get-ScheduledTask -TaskName $name -ErrorAction SilentlyContinue
|
|
if ($t) {
|
|
Write-RegisterLog "Unregistering legacy task: $name"
|
|
Unregister-ScheduledTask -TaskName $name -Confirm:$false -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
# Drop an existing copy of our own task (re-imaging idempotency).
|
|
$existing = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
|
|
if ($existing) {
|
|
Write-RegisterLog "Removing existing scheduled task: $taskName"
|
|
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
# --- Action ---
|
|
$action = New-ScheduledTaskAction `
|
|
-Execute 'powershell.exe' `
|
|
-Argument "-NoProfile -ExecutionPolicy Bypass -File `"$EnforcerPath`""
|
|
|
|
# --- Triggers ---
|
|
# Per-PC random offset [0, 5) min so 200 PCs don't all fire on :00/:05/:10/...
|
|
# Derived from hostname hash so the same PC always picks the same offset.
|
|
$hostHash = [System.BitConverter]::ToUInt32(
|
|
[System.Security.Cryptography.MD5]::Create().ComputeHash(
|
|
[System.Text.Encoding]::UTF8.GetBytes($env:COMPUTERNAME)), 0)
|
|
$offsetMin = $hostHash % 5 # 0..4
|
|
|
|
$startToday = (Get-Date -Hour 0 -Minute $offsetMin -Second 0).AddSeconds(0)
|
|
|
|
$logonTrigger = New-ScheduledTaskTrigger -AtLogOn
|
|
# Periodic every 5 minutes, repeating indefinitely, starting at the offset
|
|
$periodicTrigger = New-ScheduledTaskTrigger -Once -At $startToday -RepetitionInterval (New-TimeSpan -Minutes 5)
|
|
|
|
$shift1Trigger = New-ScheduledTaskTrigger -Daily -At '05:45' # 3-to-1 shift
|
|
$shift2Trigger = New-ScheduledTaskTrigger -Daily -At '13:45' # 1-to-2
|
|
$shift3Trigger = New-ScheduledTaskTrigger -Daily -At '21:45' # 2-to-3
|
|
|
|
$triggers = @($logonTrigger, $periodicTrigger, $shift1Trigger, $shift2Trigger, $shift3Trigger)
|
|
|
|
# --- Principal + Settings ---
|
|
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
|
|
$settings = New-ScheduledTaskSettingsSet `
|
|
-AllowStartIfOnBatteries `
|
|
-DontStopIfGoingOnBatteries `
|
|
-StartWhenAvailable `
|
|
-ExecutionTimeLimit (New-TimeSpan -Hours 1) `
|
|
-MultipleInstances IgnoreNew
|
|
|
|
$description = "GE Shopfloor unified enforcer. Reads common + pc-type manifests from " +
|
|
"\\tsgwp00525.wjs.geaerospace.net\shared\dt\shopfloor\, runs " +
|
|
"Install-FromManifest.ps1 against each. Periodic 5-min jitter + " +
|
|
"AtLogOn + 3x shift-change windows. Log: C:\Logs\Shopfloor\enforce-*.log"
|
|
|
|
Register-ScheduledTask `
|
|
-TaskName $taskName `
|
|
-Action $action `
|
|
-Trigger $triggers `
|
|
-Principal $principal `
|
|
-Settings $settings `
|
|
-Description $description | Out-Null
|
|
|
|
Write-RegisterLog "Registered '$taskName' (offset $offsetMin min)"
|
|
Write-RegisterLog " Triggers: AtLogOn, every 5min, 05:45, 13:45, 21:45"
|
|
Write-RegisterLog " Action: powershell -File $EnforcerPath"
|