The scanner has been reporting the same count for weeks, which is what a rule that only prints becomes. It now FAILS the build, and it looks where the leaks actually were: PowerShell, the installer, the seeds, generated JSON, the frontend - case-insensitively, across plugins, shopdb, scripts, deploy, tools. A line that is deliberate declares itself with an ADR-015-OK marker and a reason, so the claim is visible in review instead of tolerated in silence. What it found, fixed here: - The shadow client wrote one site's ShopDB URL into HKLM whenever the registry disagreed. At the site it was written for that reads as healing drift; anywhere else it overwrites the site's own address on every enforce cycle, and the site cannot win because the cycle repeats. The bay's value now wins, an explicit -BaseUrl seeds it, and with neither there is nothing honest to write, so it says so and skips. - The kiosk dispatcher fell back to one plant's host when HKLM was unset, so a kiosk elsewhere quietly opened a server it has no business reaching. The fallback is now this site's site_base_url, baked in at seed time, and the dispatcher refuses rather than guessing when neither is set. Its legacy shortcut matcher derives the host from that URL instead of naming one. - The OpenAPI generator hardcoded a production hostname into every spec it generated, which then published to a public wiki. The relative mount is the only server it can honestly name; a site passes its own by environment. - Placeholders and examples in the UI and the client help offered real internal subnets and a real production URL. They now use documentation ranges. Both publication gates - the export scrub and the docs publishability test - carry the site patterns, which neither did. One plant's hostname, FQDN and internal networks are out of the documentation and the generated specs. Comments naming the reference site are reworded rather than deleted: the reasoning is worth keeping, the plant name is not what makes it true.
181 lines
7.8 KiB
PowerShell
181 lines
7.8 KiB
PowerShell
<#
|
|
.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 "Main Plant" `
|
|
-ShopdbUrl https://shopdb.example.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
|
|
}
|