1
GE ENFORCE
Cameron Proudlock edited this page 2026-07-13 19:51:21 -04:00

GE-Enforce: concepts, the shopdb plugin, and imaging-time integration

This guide explains how GE-Enforce works, how the shopdb geenforce plugin manages it, and when GE-Enforce installs and takes over during the imaging process. It is written for site IT.

It pairs with two companion docs:

  • docs/GE-ENFORCE-CLIENT.md - the client fetch/report contract + the reference PowerShell kit (plugins/geenforce/client/).
  • docs/proposals/ge-enforce-plugin.md - the design/plan and the staged cutover.

The ground truth for behavior is the engine itself (Install-FromManifest.ps1) and the on-share manifests; this guide describes what they do, it does not replace them.


1. What GE-Enforce is

GE-Enforce is a desired-state enforcement system for shopfloor PCs. Instead of a one-time install during imaging, it continuously makes each PC match a declared list of what should be installed - and RE-installs anything that drifts (uninstalled, corrupted, or overwritten). It is the shopfloor equivalent of a lightweight, air-gapped-friendly configuration-management agent.

Two things make up the system:

  1. The engine + dispatcher on each PC - PowerShell that reads a manifest and enforces it every logon and periodically.
  2. The manifests - JSON files that declare, per imaging PC type, what to install / copy / write and how to detect whether it is already correct.

The shopdb geenforce plugin adds a third piece: it lets you author, publish, and version those manifests in shopdb (instead of hand-editing JSON on a file share) and see what every PC actually did (fleet compliance reporting).


2. How GE-Enforce works (the framework)

2.1 Two phases

Every shopfloor PC is governed in two distinct phases:

Phase When Runs what Purpose
Preinstall ONCE, at imaging preinstall.json (via the imaging 00-PreInstall step) Day-zero foundation: PowerShell 7, the VC++ redistributable matrix, Oracle Client, Adobe Reader, HostExplorer, serial drivers, etc. "Install once at imaging, no drift correction."
Runtime EVERY logon + periodically common/manifest.json, then gea-shopfloor-<type>/manifest.json, then an optional <type>-<subtype> manifest Ongoing enforcement + self-heal: app versions, config-file drift, registry drift, per-cycle scripts (asset report, VNC firewall, EventSaver), version-gated installs.

The two phases share the same entry SHAPE (field names) but are run by different runners with different capabilities. Preinstall is a one-shot at imaging that implements only Type=MSI and Type=EXE, with only Registry / File detection (other types/detections are skipped). Runtime is the continuous enforcement loop and implements the full Type + DetectionMethod matrix below.

2.2 The runtime loop, step by step

On each cycle (GE-Enforce.ps1 on the PC):

  1. Read the PC's identity from C:\Enrollment\ (see 2.4).
  2. Look up the SFLD share credential in the registry and mount the share (SYSTEM cannot reach the share as its computer account, so it mounts as the provisioned SFLD user - net use W: ...). If no credential yet, exit 0 and retry next cycle (Azure DSC has not provisioned it).
  3. Run the engine (Install-FromManifest.ps1) against common/manifest.json, then gea-shopfloor-<pctype>/manifest.json, then a <pctype>-<subtype> manifest if one exists. Common runs first so shared prerequisites (e.g. Oracle Client) land before type-specific apps that depend on them.
  4. Write a status file back to the share (and, in the shopdb model, POST a report - see 4.3).

Every failure is non-fatal (exit 0) so a network blip or a not-yet-provisioned credential never blocks or breaks a PC.

2.3 The manifest: scopes and entries

A manifest is { "Version", "_comment", "Applications": [ entry, ... ] }. Each imaging PC type is a scope with its own manifest, plus the fleet-wide common scope:

  • common - runs on EVERY PC type; entries use a PCTypes filter to target subsets (e.g. "EventSaver on collections + heattreat, but not CMM").
  • gea-shopfloor-collections, -nocollections, -cmm, -keyence, -common (lab/timeclock), -genspect, -heattreat, -partmarker, -waxtrace - each runs only on PCs of that type, so its entries usually do NOT set PCTypes (the manifest already only runs there). Keyence is the exception: it uses PCTypes for hardware SUBTYPE targeting (keyence-vr6000 vs keyence-vr3000).

Each entry declares one action. Its Type picks the action:

Type Action
MSI / EXE / CMD / BAT run an installer with InstallArgs
PS1 run a script from the share
INF install a driver via pnputil
File copy Source -> Destination
Registry write a value

2.4 Self-heal via detection

Every entry has a DetectionMethod that decides whether the action fires:

Method Means "already correct" when...
Registry the key/value exists (optionally equals a value)
File the file exists
FileVersion the file's version string matches exactly (fleet convention is a 4-part string like 6.4.5.0; the engine does a raw string compare, it does not enforce 4 parts)
Hash the file's SHA256 matches (case-insensitive)
MarkerFile a marker file exists (the engine writes it after a clean install)
ValueMatches a registry value equals the entry's target
pnputil a driver matching a pattern is present
Always / (none) fires EVERY cycle (used for per-cycle scripts)

If detection says "not correct," the action runs. That is the self-heal: delete DncMain.exe and next cycle re-installs eDNC; corrupt a config file whose Hash no longer matches and next cycle re-copies it. Entry order is execution order - config-restore entries sit AFTER their installer so a mid-cycle vendor overwrite is healed on the same cycle.

2.5 Targeting gates (all ANDed)

An entry can be narrowed by any combination of:

  • PCTypes - which PC types (alias-aware: old names like Standard map to collections/nocollections/common). Fleet-wide common uses this heavily.
  • TargetHostnames - specific hostnames (supports * wildcards).
  • TargetMachineNumbers - specific bay machine numbers (e.g. Okuma bays).
  • _CmmVersion - CMM PCs only: a tagged entry applies when it equals the bay's resolved PC-DMIS version (C:\Enrollment\cmm\version.txt). IMPORTANT: if no version is resolved (file missing/empty - a pre-picker bay), ALL tagged entries apply (deliberate legacy "install-all" behavior), so such a bay gets every PC-DMIS version, not none. Requires engine lib >= 2.6.
  • PCTypesStrict - disables alias expansion (PREINSTALL runner only; the runtime engine ignores it).

Different PC types have different niche gates: CMM uses a version gate, Keyence a model subtype, Collections per-bay machine numbers. The shopdb editor shows only the gates a given scope actually uses (see 4.1).

2.6 What the PC needs to know about itself (enrollment)

The runtime engine reads the PC's identity from C:\Enrollment\:

  • pc-type.txt - the imaging PC type (which scope to run). pc-subtype.txt is LEGACY (no longer written at imaging since the 2026-05-04 rename reorg; the dispatcher still honors it if present on older fleet PCs).
  • machine-number.txt - the bay number FALLBACK; the eDNC/DNC registry MachineNo value wins if present. 9999 is the imaging placeholder by convention - the enforcement engine does NOT special-case it; it is simply a value that won't match a real bay number in a TargetMachineNumbers gate. (The 9999-skip you may see is only in the status write-back, not enforcement.)
  • cmm/version.txt - CMM bays only: the resolved PC-DMIS version for _CmmVersion.
  • site-config.json - the share root and site settings.
  • SFLD credentials at HKLM:\SOFTWARE\GE\SFLD\Credentials - provisioned by Azure DSC after enrollment (this is what gates the runtime phase starting).

Note: all of the identity files above (pc-type, machine-number, cmm version, site-config) are written in WinPE at the PXE menu, BEFORE the image boots - the preinstall phase already reads them. What happens post-imaging is only Intune enrollment + the Azure DSC credential (see the timeline below).


3. When GE-Enforce installs / takes over (the imaging timeline)

This is the "when to implement it during imaging" question. The order is:

[0] WinPE / PXE menu (BEFORE the image boots)
      - identity written to C:\Enrollment: pc-type.txt, machine-number.txt,
        cmm/version.txt, site-config.json (startnet.cmd). The PC already knows
        what it is before Windows starts.
      |
      v
    PXE image applied, Windows boots
      |
      v
[1] PREINSTALL  (00-PreInstall runner runs preinstall.json ONCE)
      - reads the step-0 identity files, then installs the foundation:
        PowerShell 7, VC++ redists, Oracle Client, Adobe Reader, HostExplorer,
        serial drivers, Display kiosk app, ... (things later runtime apps need)
      - preinstall implements MSI/EXE + Registry/File detection only
      |
      v
[2] GE-ENFORCE ITSELF is laid down during imaging
      - the dispatcher (GE-Enforce.ps1), the engine lib (Install-FromManifest.ps1),
        and a scheduled task (at-logon + every ~5 min + shift windows) are
        registered as part of the image / shopfloor setup
      |
      v
[3] ENROLLMENT (post-imaging)
      - Intune / GCCH enrollment, THEN Azure DSC provisions the SFLD share
        credential into HKLM:\SOFTWARE\GE\SFLD\Credentials
      - (the identity files already exist from step 0 - enrollment adds only the
        credential, which is what unblocks runtime)
      |
      v
[4] FIRST LOGON  -> RUNTIME ENFORCEMENT BEGINS
      - the scheduled task runs GE-Enforce.ps1: mount share, run common + the
        PC-type (+ subtype) manifests, install/self-heal, report
      - repeats every logon + periodically forever after

Key points on timing:

  • Preinstall (step 1) is the imaging-time install. Put anything that must exist before first logon, or that never needs drift correction, here (runtimes, redistributables, drivers). It runs once and is done.
  • Runtime enforcement (step 4) does not start until enrollment (step 3) provisions the SFLD credential. Before that, GE-Enforce exits 0 each cycle and waits. So a freshly imaged PC that is not yet enrolled is inert, by design.
  • The engine lib version matters. _CmmVersion gating needs lib >= 2.6 on the PC; deploy the lib before a manifest that uses it.
  • Some apps appear in BOTH phases: preinstalled at imaging for day-zero, then carried by a runtime entry so drift is corrected later (Oracle, UDC, Adobe, HostExplorer, Defect Tracker).

Rule of thumb: imaging-time (preinstall) = foundation that must be there or never drifts; runtime = everything that needs to stay correct over the PC's life.


4. How the shopdb plugin manages this

The geenforce plugin turns the manifest from hand-edited JSON on a share into shopdb data you author, version, publish, and monitor. It lives under the top-level GE-Enforce section (Manifests | Enforcement Reports), not Settings, because it is a full management surface.

4.1 Manifests - authoring (GE-Enforce > Manifests)

  • PC Types (scopes): each imaging PC type is a row; add/edit/delete. (The scope carries an optional computertypeid reference field, but the collector's imaging-pc-type -> ComputerType mapping is configured separately at Settings > Collector PC Types.)
  • Entries: an ordered list (Up/Down = the execution-order contract). Add/Edit opens a typed form: the payload fields switch on Type (MSI shows Installer + InstallArgs, PS1 shows Script + Args, File shows Source + Destination, Registry shows the Reg* fields), a detection block, an InUseCheck editor, and a Targeting section that shows only the gates the scope uses (CMM shows the version gate; the common/preinstall scopes show PC types; a scope whose entries use machine numbers shows those) with a "Show all targeting options" escape hatch.
  • Simulate ("what would a PC get?"): enter a PC profile (type, subtype, hostname, machine number, CMM version) and see which entries apply and why the rest are filtered - without reading a PowerShell log.
  • Publish / Versions / Roll Back: editing changes a DRAFT only. Publish freezes an immutable version; PCs are only ever served the published version; Roll Back restores an earlier one. History (date, author, note) per version.

4.2 Milestone 1 - export to the share (engine unchanged)

Today the enforcement engine still reads manifests from the SFLD share. The plugin's Export to Share button writes the current published manifest to <shareroot>/<scope>/manifest.json (backing up the old file to _meta/history first). So the workflow is:

author + publish in shopdb -> Export to Share -> the unchanged engine picks it up next cycle.

Nothing about the engine, the share layout, or the PCs changes. Rollback is restoring the _meta/history backup (or re-publishing an older version and re-exporting). This is the safe first milestone: all the authoring benefit, zero client risk.

Configure the share root once at the top of the Manifests page.

4.3 Enforcement Reports - fleet compliance (GE-Enforce > Enforcement Reports)

Each PC reports its enforcement result back to shopdb (see the client kit). The Reports page shows, per PC:

  • Received - did the PC apply the latest published version? (applied vs latest). "behind" means it has not picked up your newest publish yet.
  • Status - ok (nothing needed), selfhealed (drift corrected), failed.
  • Counts - installed / skipped / failed, plus per-entry detail (action, self-heal flag, exit code, message) in the row's Detail view.

This is the observed-state half of the loop: the manifest is what SHOULD be installed; the report is what each PC ACTUALLY did.

4.4 The client side (per PC)

The engine sources the manifest and reports results using the reference kit in plugins/geenforce/client/ (ShopdbEnforceClient.psm1 + Invoke-ShopdbEnforce.ps1), configured from HKLM:\SOFTWARE\GE\ShopDB (BaseUrl + a geenforce.fetch/geenforce.report service token). See docs/GE-ENFORCE-CLIENT.md for the fetch/report contract, the last-known-good cache, shadow mode, and the staged cutover from share-sourced to shopdb-sourced manifests. Until that cutover, the client only REPORTS; the manifest still comes from the share via Export to Share (4.2).


5. Day-to-day: common tasks

All in GE-Enforce > Manifests. No PowerShell, no editing JSON on the share.

  • Add an app to a PC type: open the PC type, Add Entry, pick the Type (the form adapts), fill the installer + detection + any targeting, place it in order with Up/Down (config restores go BELOW their installer), Preview, Publish, then Export to Share.
  • Bump an app version: drop the new installer in the scope's apps/ folder on the share, open the entry, update the Installer filename + the Detection value (the new version), Publish, Export to Share. PCs self-heal next cycle.
  • Roll back a bad publish: the PC type's Versions list -> Roll Back to the last good version -> Export to Share.
  • Canary a risky change: add the one test PC under Target hostnames (via "Show all targeting options"), Publish; when happy, remove the filter and Publish again.
  • Check "did PC Y get app X": use Simulate with that PC's type / machine number / CMM version; and check Enforcement Reports for what it actually did.

6. Reference

  • Engine (behavior ground truth): Install-FromManifest.ps1 (lib >= 2.6).
  • Dispatcher: GE-Enforce.ps1 (mount + run common then type scope).
  • Preinstall runner: 00-PreInstall-* over preinstall.json (imaging-time).
  • shopdb model + API: plugins/geenforce/ (models, importer/serializer, filters mirror, service, routes).
  • Behavioral parity gate (proves the shopdb model round-trips the real manifests): plugins/geenforce/parity.py + flask geenforce parity.
  • Client kit + contract: plugins/geenforce/client/, docs/GE-ENFORCE-CLIENT.md.
  • Agent deployment (per PC, any imaging path): docs/GE-ENFORCE-DEPLOY.md + plugins/geenforce/client/Install-GEEnforce.ps1.
  • Design + cutover plan: docs/proposals/ge-enforce-plugin.md.