Add GE-Enforce agent deployment: Install-GEEnforce.ps1 + deploy doc
Closes the "how do sites actually deploy GE-Enforce" gap (esp. OOBE-ppkg sites without a PXE/WinPE step). Site-neutral + imaging-path independent. - plugins/geenforce/client/Install-GEEnforce.ps1: a bootstrap that writes the PC's identity (C:\Enrollment\pc-type.txt is what determines the PC type; plus machine-number/cmm version/cmm id/site-config as needed), sets the shopdb BaseUrl + token in HKLM:\SOFTWARE\GE\ShopDB, deploys the client kit, optionally copies the engine from -EngineSource, and registers the SYSTEM scheduled task (at logon + every N min). Idempotent; fails loud (installer, not the fail-safe runtime). Engine is REFERENCED not vendored - it belongs to the GE-Enforce framework; the script warns if absent but still labels the PC. - docs/GE-ENFORCE-DEPLOY.md: the deploy contract - the three things a PC needs (client, identity, credential), the identity table (what determines PC type, no auto-detection - the provisioner supplies it; shopdb cannot set it at imaging), and how to invoke per path (PXE step, OOBE ppkg via ProvisioningCommands, Intune, manual), the engine boundary, and verification. - Cross-linked from docs/GE-ENFORCE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
135
docs/GE-ENFORCE-DEPLOY.md
Normal file
135
docs/GE-ENFORCE-DEPLOY.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# Deploying the GE-Enforce agent on a PC
|
||||
|
||||
This is the deploy contract: what has to be laid down on a PC so GE-Enforce runs,
|
||||
and how to do it regardless of imaging path (PXE, OOBE provisioning package,
|
||||
Intune, or by hand). It complements `docs/GE-ENFORCE.md` (concepts) and
|
||||
`docs/GE-ENFORCE-CLIENT.md` (the fetch/report contract).
|
||||
|
||||
The reference installer is `plugins/geenforce/client/Install-GEEnforce.ps1`. It
|
||||
is site-neutral: you pass the PC's identity in, it writes the files/registry the
|
||||
engine reads and registers the enforcement task.
|
||||
|
||||
---
|
||||
|
||||
## 1. What "deploying GE-Enforce" means
|
||||
|
||||
A PC needs three things present before enforcement works. HOW they get there is
|
||||
up to your imaging path; WHAT they are is fixed:
|
||||
|
||||
1. **The GE-Enforce client** - the engine (`Install-FromManifest.ps1`), the
|
||||
shopdb client kit (`ShopdbEnforceClient.psm1`, `Invoke-ShopdbEnforce.ps1`),
|
||||
and a scheduled task (at logon + periodic) that runs as SYSTEM.
|
||||
2. **Identity** in `C:\Enrollment` - so the PC knows what it is (see section 2).
|
||||
3. **A credential** - the SFLD share credential (for a share-sourced manifest)
|
||||
and/or the shopdb service token (for the fetch/report client). This is what
|
||||
gates enforcement actually starting; until it exists the task exits 0 and
|
||||
retries.
|
||||
|
||||
`Install-GEEnforce.ps1` lays down 1 and 2, and can write the shopdb token for 3.
|
||||
The engine itself is the GE-Enforce framework's, not shopdb's - point the
|
||||
installer at your copy with `-EngineSource`, or place it under the install root
|
||||
first (see section 5).
|
||||
|
||||
---
|
||||
|
||||
## 2. Identity: how a PC determines its PC type (and bay)
|
||||
|
||||
There is NO auto-detection. The provisioner supplies the values; the engine only
|
||||
reads files. This is the core of "set the PC up to know its type."
|
||||
|
||||
| Value | Written to | Purpose | Required? |
|
||||
|---|---|---|---|
|
||||
| **PC type** | `C:\Enrollment\pc-type.txt` (first line) | picks the manifest scope (`gea-shopfloor-<type>`) | YES |
|
||||
| Machine (bay) number | `C:\Enrollment\machine-number.txt` (fallback; DNC registry `MachineNo` wins) | per-bay gates | only for bay-gated entries |
|
||||
| CMM version | `C:\Enrollment\cmm\version.txt` | `_CmmVersion` gating (CMM PCs) | CMM only |
|
||||
| CMM bay id | `C:\Enrollment\cmm\cmmid.txt` | CMM bay identity | CMM only |
|
||||
| Share root + site | `C:\Enrollment\site-config.json` | where manifests/payloads live | for share-sourced |
|
||||
| shopdb URL + token | `HKLM:\SOFTWARE\GE\ShopDB` (BaseUrl, ApiToken) | fetch/report client | for shopdb client |
|
||||
|
||||
Valid `pc-type` values are the manifest scope names
|
||||
(`gea-shopfloor-cmm`, `-collections`, `-nocollections`, `-common`, `-keyence`,
|
||||
`-genspect`, `-heattreat`, `-partmarker`, `-waxtrace`) or a legacy alias the
|
||||
engine maps (`Standard`, `CMM`, ...).
|
||||
|
||||
**shopdb cannot set the type at imaging** - a PC is not known to shopdb until it
|
||||
enrolls and reports. If you want the value to come from an asset system, pre-map
|
||||
asset-tag / hostname -> PC type in your provisioning and feed it to the
|
||||
installer.
|
||||
|
||||
---
|
||||
|
||||
## 3. Running it, per imaging path
|
||||
|
||||
`Install-GEEnforce.ps1` is the same in every case; only how you invoke it differs.
|
||||
|
||||
### PXE / imaging step (identity known at image time)
|
||||
Run it as an imaging step after the OS lays down, passing the type the operator
|
||||
selected:
|
||||
|
||||
```
|
||||
powershell -ExecutionPolicy Bypass -File Install-GEEnforce.ps1 `
|
||||
-PCType gea-shopfloor-cmm -MachineNumber 0615 -CmmVersion 2019 `
|
||||
-ShareRoot \\server\share\dt\shopfloor -Site "West Jefferson" `
|
||||
-ShopdbUrl https://shopdb.site.geaerospace.net -ShopdbToken shopdb_pat_xxx `
|
||||
-EngineSource \\server\share\dt\shopfloor\common
|
||||
```
|
||||
|
||||
### OOBE provisioning package (ppkg)
|
||||
Sites that apply a ppkg during OOBE (no PXE/WinPE step) embed the installer + the
|
||||
client kit in the ppkg and run it from a `CommandLine` / `ProvisioningCommands`
|
||||
action. Supply the PC type from a ppkg variable, a first-boot prompt, or an
|
||||
asset lookup:
|
||||
|
||||
```
|
||||
powershell -ExecutionPolicy Bypass -File Install-GEEnforce.ps1 -PCType %PCTYPE% ...
|
||||
```
|
||||
|
||||
Timing is forgiving: the scheduled task is fail-safe, so if OOBE finishes before
|
||||
Intune/DSC provisions the credential, enforcement simply waits and starts once
|
||||
the credential lands. There is no ordering trap.
|
||||
|
||||
### Intune / manual
|
||||
Same script as a Win32 app / remediation, or run by hand on an existing PC to
|
||||
retrofit it. `-NoTask` provisions identity + kit without registering the task.
|
||||
|
||||
---
|
||||
|
||||
## 4. What the installer does (idempotent)
|
||||
|
||||
1. Writes the `C:\Enrollment` identity files (section 2).
|
||||
2. Writes `HKLM:\SOFTWARE\GE\ShopDB` (BaseUrl + token) if provided.
|
||||
3. Copies the client kit (the two files shipped next to it) to `-InstallRoot`
|
||||
(default `C:\ProgramData\GE-Enforce`).
|
||||
4. If `-EngineSource` is given, copies `GE-Enforce.ps1` + `lib\Install-FromManifest.ps1`.
|
||||
5. Registers the scheduled task (SYSTEM, at logon + every `-IntervalMinutes`) to
|
||||
run `Invoke-ShopdbEnforce.ps1 -Scope <PCType> -EnginePath <engine>`.
|
||||
|
||||
Re-running it updates identity/config and re-registers the task in place.
|
||||
|
||||
---
|
||||
|
||||
## 5. The engine boundary
|
||||
|
||||
shopdb ships the **manifest store + client kit + this installer**, not the
|
||||
GE-Enforce **engine** (`Install-FromManifest.ps1`) or dispatcher - those live in
|
||||
the GE-Enforce framework. So one of:
|
||||
|
||||
- pass `-EngineSource <path>` pointing at a folder that has `GE-Enforce.ps1` and
|
||||
`lib\Install-FromManifest.ps1` (e.g. your share's `common` dir), or
|
||||
- place the engine under `<InstallRoot>\lib\Install-FromManifest.ps1` yourself
|
||||
before enforcement runs.
|
||||
|
||||
The installer warns if the engine is missing but still provisions identity so a
|
||||
PC is at least correctly labelled. Use engine lib >= 2.6 (required for the
|
||||
`_CmmVersion` gate).
|
||||
|
||||
---
|
||||
|
||||
## 6. Verify a provisioned PC
|
||||
|
||||
- `Get-Content C:\Enrollment\pc-type.txt` -> the expected scope.
|
||||
- `Get-ItemProperty HKLM:\SOFTWARE\GE\ShopDB` -> BaseUrl + ApiToken set.
|
||||
- `Get-ScheduledTask GE-Enforce` -> Ready.
|
||||
- Trigger it once and check the client log
|
||||
(`C:\Logs\Shopfloor\shopdb-enforce-*.log`), then confirm the PC appears under
|
||||
**GE-Enforce > Enforcement Reports** in shopdb with the right PC type.
|
||||
@@ -324,4 +324,6 @@ All in GE-Enforce > Manifests. No PowerShell, no editing JSON on the share.
|
||||
- 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`.
|
||||
|
||||
180
plugins/geenforce/client/Install-GEEnforce.ps1
Normal file
180
plugins/geenforce/client/Install-GEEnforce.ps1
Normal file
@@ -0,0 +1,180 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Provision a PC for GE-Enforce + shopdb: write its identity (so it knows its PC
|
||||
type and bay), deploy the shopdb client kit, and register the enforcement
|
||||
scheduled task. Site-neutral and imaging-path independent - run it from a PXE
|
||||
step, an OOBE provisioning package (ppkg), Intune, or by hand.
|
||||
|
||||
This is the "set up the PC to determine its PC type and such" step. There is no
|
||||
auto-detection: the caller supplies the type (and, where relevant, the bay
|
||||
machine number and CMM version), and this script writes the files/registry the
|
||||
GE-Enforce engine reads.
|
||||
|
||||
This script does NOT contain the GE-Enforce engine (Install-FromManifest.ps1)
|
||||
or dispatcher - those are the GE-Enforce framework's. Point at your copy with
|
||||
-EngineSource, or ensure it is already present under -InstallRoot\lib.
|
||||
|
||||
.PARAMETER PCType
|
||||
The imaging PC type = the manifest scope this PC runs. One of the scope names
|
||||
(e.g. gea-shopfloor-cmm, gea-shopfloor-collections) or a legacy alias the
|
||||
engine maps. Written to C:\Enrollment\pc-type.txt. REQUIRED.
|
||||
|
||||
.PARAMETER MachineNumber
|
||||
The bay machine number (for TargetMachineNumbers gates). Written to
|
||||
machine-number.txt as a fallback; the DNC registry MachineNo wins if present.
|
||||
|
||||
.PARAMETER CmmVersion
|
||||
CMM bays only: the resolved PC-DMIS version (2016/2019/2026) for _CmmVersion
|
||||
gating. Written to C:\Enrollment\cmm\version.txt. Omit for non-CMM PCs.
|
||||
|
||||
.PARAMETER CmmId
|
||||
CMM bays only: the bay id. Written to C:\Enrollment\cmm\cmmid.txt.
|
||||
|
||||
.PARAMETER ShareRoot
|
||||
The GE-Enforce share root (UNC), written into site-config.json as
|
||||
shopfloorShareRoot.
|
||||
|
||||
.PARAMETER Site
|
||||
Site name, written into site-config.json.
|
||||
|
||||
.PARAMETER ShopdbUrl
|
||||
shopdb base URL (scheme + host) for the fetch/report client. Written to
|
||||
HKLM:\SOFTWARE\GE\ShopDB\BaseUrl.
|
||||
|
||||
.PARAMETER ShopdbToken
|
||||
A geenforce.fetch (+ geenforce.report) managed service token. Written to
|
||||
HKLM:\SOFTWARE\GE\ShopDB\ApiToken. (In many sites Azure DSC provisions this
|
||||
instead - omit here if so.)
|
||||
|
||||
.PARAMETER EngineSource
|
||||
Optional path (folder or share) containing the GE-Enforce engine to copy in:
|
||||
expects GE-Enforce.ps1 and lib\Install-FromManifest.ps1. If omitted, the engine
|
||||
is assumed already present under -InstallRoot.
|
||||
|
||||
.PARAMETER InstallRoot
|
||||
Where the client kit + engine live on the PC. Default C:\ProgramData\GE-Enforce.
|
||||
|
||||
.PARAMETER TaskName
|
||||
Scheduled task name. Default 'GE-Enforce'.
|
||||
|
||||
.PARAMETER IntervalMinutes
|
||||
How often the enforcement task repeats. Default 5.
|
||||
|
||||
.PARAMETER NoTask
|
||||
Provision identity + kit only; do not register the scheduled task.
|
||||
|
||||
.EXAMPLE
|
||||
.\Install-GEEnforce.ps1 -PCType gea-shopfloor-cmm -MachineNumber 0615 `
|
||||
-CmmVersion 2019 -ShareRoot \\server\share\dt\shopfloor -Site "West Jefferson" `
|
||||
-ShopdbUrl https://shopdb.site.geaerospace.net -ShopdbToken shopdb_pat_xxx `
|
||||
-EngineSource \\server\share\dt\shopfloor\common
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$PCType,
|
||||
[string]$MachineNumber,
|
||||
[string]$CmmVersion,
|
||||
[string]$CmmId,
|
||||
[string]$ShareRoot,
|
||||
[string]$Site,
|
||||
[string]$ShopdbUrl,
|
||||
[string]$ShopdbToken,
|
||||
[string]$EngineSource,
|
||||
[string]$InstallRoot = 'C:\ProgramData\GE-Enforce',
|
||||
[string]$EnrollmentRoot = 'C:\Enrollment',
|
||||
[string]$TaskName = 'GE-Enforce',
|
||||
[int]$IntervalMinutes = 5,
|
||||
[switch]$NoTask
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Write-TextFile {
|
||||
param([string]$Path, [string]$Value)
|
||||
$dir = Split-Path -Parent $Path
|
||||
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
|
||||
Set-Content -LiteralPath $Path -Value $Value -Encoding ascii -NoNewline
|
||||
Write-Host " wrote $Path"
|
||||
}
|
||||
|
||||
try {
|
||||
Write-Host "GE-Enforce provisioning: PCType=$PCType"
|
||||
|
||||
# --- 1. Identity: what the PC is (what the engine reads) -----------------
|
||||
Write-TextFile (Join-Path $EnrollmentRoot 'pc-type.txt') $PCType
|
||||
if ($MachineNumber) { Write-TextFile (Join-Path $EnrollmentRoot 'machine-number.txt') $MachineNumber }
|
||||
if ($CmmVersion) { Write-TextFile (Join-Path $EnrollmentRoot 'cmm\version.txt') $CmmVersion }
|
||||
if ($CmmId) { Write-TextFile (Join-Path $EnrollmentRoot 'cmm\cmmid.txt') $CmmId }
|
||||
|
||||
$siteConfig = @{}
|
||||
if ($ShareRoot) { $siteConfig['shopfloorShareRoot'] = $ShareRoot }
|
||||
if ($Site) { $siteConfig['site'] = $Site }
|
||||
if ($siteConfig.Count) {
|
||||
$siteConfigPath = Join-Path $EnrollmentRoot 'site-config.json'
|
||||
$dir = Split-Path -Parent $siteConfigPath
|
||||
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
|
||||
($siteConfig | ConvertTo-Json) | Set-Content -LiteralPath $siteConfigPath -Encoding ascii
|
||||
Write-Host " wrote $siteConfigPath"
|
||||
}
|
||||
|
||||
# --- 2. shopdb client registry config ------------------------------------
|
||||
if ($ShopdbUrl -or $ShopdbToken) {
|
||||
$regPath = 'HKLM:\SOFTWARE\GE\ShopDB'
|
||||
if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force | Out-Null }
|
||||
if ($ShopdbUrl) { Set-ItemProperty -Path $regPath -Name BaseUrl -Value $ShopdbUrl }
|
||||
if ($ShopdbToken) { Set-ItemProperty -Path $regPath -Name ApiToken -Value $ShopdbToken }
|
||||
Write-Host " wrote HKLM:\SOFTWARE\GE\ShopDB"
|
||||
}
|
||||
|
||||
# --- 3. Deploy the client kit (ships alongside this script) --------------
|
||||
if (-not (Test-Path $InstallRoot)) { New-Item -ItemType Directory -Path $InstallRoot -Force | Out-Null }
|
||||
foreach ($file in 'ShopdbEnforceClient.psm1', 'Invoke-ShopdbEnforce.ps1') {
|
||||
$src = Join-Path $PSScriptRoot $file
|
||||
if (Test-Path $src) {
|
||||
Copy-Item -Path $src -Destination (Join-Path $InstallRoot $file) -Force
|
||||
Write-Host " deployed $file"
|
||||
} else {
|
||||
Write-Warning "client kit file not found next to this script: $file"
|
||||
}
|
||||
}
|
||||
|
||||
# --- 4. Engine (referenced, not vendored) --------------------------------
|
||||
if ($EngineSource) {
|
||||
Copy-Item -Path (Join-Path $EngineSource 'GE-Enforce.ps1') `
|
||||
-Destination (Join-Path $InstallRoot 'GE-Enforce.ps1') -Force -ErrorAction SilentlyContinue
|
||||
$libDir = Join-Path $InstallRoot 'lib'
|
||||
if (-not (Test-Path $libDir)) { New-Item -ItemType Directory -Path $libDir -Force | Out-Null }
|
||||
Copy-Item -Path (Join-Path $EngineSource 'lib\Install-FromManifest.ps1') `
|
||||
-Destination (Join-Path $libDir 'Install-FromManifest.ps1') -Force -ErrorAction SilentlyContinue
|
||||
Write-Host " copied engine from $EngineSource"
|
||||
}
|
||||
$enginePath = Join-Path $InstallRoot 'lib\Install-FromManifest.ps1'
|
||||
if (-not (Test-Path $enginePath)) {
|
||||
Write-Warning "engine not present at $enginePath - provide -EngineSource or place it there before enforcement runs."
|
||||
}
|
||||
|
||||
# --- 5. Scheduled task: run the enforcement client as SYSTEM -------------
|
||||
if (-not $NoTask) {
|
||||
$runner = Join-Path $InstallRoot 'Invoke-ShopdbEnforce.ps1'
|
||||
$arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$runner`" -Scope `"$PCType`" -EnginePath `"$enginePath`""
|
||||
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $arguments
|
||||
$triggers = @(
|
||||
(New-ScheduledTaskTrigger -AtLogOn),
|
||||
(New-ScheduledTaskTrigger -Once -At (Get-Date) `
|
||||
-RepetitionInterval (New-TimeSpan -Minutes $IntervalMinutes))
|
||||
)
|
||||
$principal = New-ScheduledTaskPrincipal -UserId 'NT AUTHORITY\SYSTEM' -RunLevel Highest
|
||||
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries `
|
||||
-DontStopIfGoingOnBatteries -StartWhenAvailable
|
||||
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $triggers `
|
||||
-Principal $principal -Settings $settings -Force | Out-Null
|
||||
Write-Host " registered task '$TaskName' (at logon + every $IntervalMinutes min)"
|
||||
}
|
||||
|
||||
Write-Host "GE-Enforce provisioning complete."
|
||||
exit 0
|
||||
} catch {
|
||||
Write-Error "GE-Enforce provisioning FAILED: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
Reference in New Issue
Block a user