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>
98 lines
3.7 KiB
PowerShell
98 lines
3.7 KiB
PowerShell
# Deploy-GEEnforce.ps1
|
|
#
|
|
# One-shot deploy of the GE-Enforce runtime + scheduled task to an already-
|
|
# imaged shopfloor PC. Use this to promote a PC from legacy per-type
|
|
# enforcers (CMM-Enforce, Keyence-Enforce, Machine-Enforce, etc.) to the
|
|
# unified GE-Enforce, without re-imaging.
|
|
#
|
|
# Usage (on target PC, as admin):
|
|
# powershell -ExecutionPolicy Bypass -File .\Deploy-GEEnforce.ps1 `
|
|
# -SourceRoot '\\<host>\<share>\enforcer-stage'
|
|
#
|
|
# The SourceRoot must contain:
|
|
# GE-Enforce.ps1
|
|
# lib\Install-FromManifest.ps1
|
|
#
|
|
# For imaging-time deployment, Run-ShopfloorSetup.ps1 invokes
|
|
# Register-GEEnforce.ps1 directly. This script is specifically for
|
|
# retrofitting live PCs.
|
|
|
|
param(
|
|
[Parameter(Mandatory=$true)]
|
|
[string]$SourceRoot,
|
|
|
|
[string]$InstallRoot = 'C:\Program Files\GE\Shopfloor',
|
|
|
|
[switch]$TriggerImmediate
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
function Write-DeployLog { param([string]$m) Write-Host "[Deploy-GEEnforce] $m" }
|
|
|
|
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('Administrators')) {
|
|
throw 'Must run as Administrator.'
|
|
}
|
|
|
|
Write-DeployLog "SourceRoot: $SourceRoot"
|
|
Write-DeployLog "InstallRoot: $InstallRoot"
|
|
|
|
# ---- 1. Validate source ----
|
|
$srcEnforcer = Join-Path $SourceRoot 'GE-Enforce.ps1'
|
|
$srcLib = Join-Path $SourceRoot 'lib\Install-FromManifest.ps1'
|
|
foreach ($p in @($srcEnforcer, $srcLib)) {
|
|
if (-not (Test-Path -LiteralPath $p)) {
|
|
throw "Missing source file: $p"
|
|
}
|
|
}
|
|
|
|
# ---- 2. Stage runtime ----
|
|
Write-DeployLog "Staging runtime to $InstallRoot"
|
|
$libDst = Join-Path $InstallRoot 'lib'
|
|
foreach ($d in @($InstallRoot, $libDst)) {
|
|
if (-not (Test-Path $d)) { New-Item -Path $d -ItemType Directory -Force | Out-Null }
|
|
}
|
|
Copy-Item -LiteralPath $srcEnforcer -Destination (Join-Path $InstallRoot 'GE-Enforce.ps1') -Force
|
|
Copy-Item -LiteralPath $srcLib -Destination (Join-Path $libDst 'Install-FromManifest.ps1') -Force
|
|
Write-DeployLog ' Runtime files copied'
|
|
|
|
# ---- 3. Register scheduled task + unregister legacy ----
|
|
$registerScript = Join-Path $SourceRoot 'Register-GEEnforce.ps1'
|
|
if (-not (Test-Path -LiteralPath $registerScript)) {
|
|
# Fall back to the copy that should sit next to this deploy script in the
|
|
# repo. In the common rollout, Register-GEEnforce.ps1 lives beside
|
|
# GE-Enforce.ps1 in $SourceRoot.
|
|
$registerScript = Join-Path (Split-Path -Parent $PSCommandPath) 'Register-GEEnforce.ps1'
|
|
}
|
|
if (-not (Test-Path -LiteralPath $registerScript)) {
|
|
throw "Cannot find Register-GEEnforce.ps1 in $SourceRoot or alongside this script."
|
|
}
|
|
|
|
Write-DeployLog "Invoking $registerScript"
|
|
& $registerScript -EnforcerPath (Join-Path $InstallRoot 'GE-Enforce.ps1')
|
|
|
|
# ---- 4. Optional: trigger one enforce cycle immediately ----
|
|
if ($TriggerImmediate) {
|
|
Write-DeployLog 'Triggering GE Shopfloor Enforce now via schtasks /run'
|
|
& schtasks /run /tn 'GE Shopfloor Enforce' | Out-Null
|
|
Start-Sleep -Seconds 3
|
|
$log = Get-ChildItem -Path 'C:\Logs\Shopfloor' -Filter 'enforce-*.log' -ErrorAction SilentlyContinue |
|
|
Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
|
if ($log) {
|
|
Write-DeployLog "Latest log: $($log.FullName) (watching for 30s)"
|
|
$end = (Get-Date).AddSeconds(30)
|
|
$lastSize = 0
|
|
while ((Get-Date) -lt $end) {
|
|
$size = (Get-Item $log.FullName).Length
|
|
if ($size -gt $lastSize) {
|
|
Get-Content -Path $log.FullName -Tail 20 -ErrorAction SilentlyContinue | ForEach-Object { Write-Host " $_" }
|
|
Write-Host ' ---'
|
|
$lastSize = $size
|
|
}
|
|
Start-Sleep -Seconds 2
|
|
}
|
|
}
|
|
}
|
|
|
|
Write-DeployLog 'DONE. Verify with: schtasks /query /tn "GE Shopfloor Enforce" /v /fo list'
|