<# .SYNOPSIS Reference orchestrator: source a GE-Enforce manifest from shopdb, run the UNCHANGED engine against it, and report the result back to shopdb. This is a thin wrapper around Install-FromManifest.ps1 (the engine). It is a reference a site adapts into its GE-Enforce.ps1 flow; it is not the live dispatcher. The engine, detection, self-heal, and SMB payload resolution are untouched - only the source of the manifest JSON moves from a share file to shopdb, plus a result report. .PARAMETER Scope The imaging pc-type / scope name (e.g. gea-shopfloor-cmm), same value the dispatcher already resolves from C:\Enrollment\pc-type.txt. .PARAMETER EnginePath Path to Install-FromManifest.ps1 (the engine lib, >= 2.6). .PARAMETER ShareManifestPath The current on-share manifest for this scope. In shadow mode the engine runs against THIS (unchanged behavior) and shopdb is only compared + reported. Once cut over, omit it and the engine runs against the shopdb-sourced manifest. .PARAMETER ShadowMode Fetch + compare + report, but install from the share (no behavior change). .NOTES Fail-safe: any error exits 0 so a bad web app never blocks or breaks a PC. Config comes from HKLM:\SOFTWARE\GE\ShopDB (BaseUrl, ApiToken) - see the psm1. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Scope, [Parameter(Mandatory)] [string]$EnginePath, [string]$ShareManifestPath, [switch]$ShadowMode, [string]$BaseUrl, [string]$ApiToken, [string]$LogFile = "C:\Logs\Shopfloor\shopdb-enforce-$(Get-Date -Format yyyyMMdd).log" ) function Write-Log { param([string]$Message, [string]$Level = 'INFO') $line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [$Level] $Message" try { Add-Content -LiteralPath $LogFile -Value $line -ErrorAction SilentlyContinue } catch {} Write-Host $line } try { Import-Module (Join-Path $PSScriptRoot 'ShopdbEnforceClient.psm1') -Force $config = Get-ShopdbConfig -BaseUrl $BaseUrl -ApiToken $ApiToken if (-not $config) { Write-Log 'No shopdb BaseUrl/ApiToken configured yet - retry next cycle.' 'WARN' exit 0 } $sync = Sync-ShopdbManifest -Scope $Scope -Config $config if (-not $sync.Path) { Write-Log "No manifest available for $Scope (shopdb unreachable, no cache)." 'WARN' exit 0 } Write-Log "Manifest for $Scope from $($sync.Source) (v$($sync.Version))." # Shadow mode: compare shopdb vs the share, but install from the share. if ($ShadowMode -and $ShareManifestPath -and (Test-Path $ShareManifestPath)) { $diff = Compare-ShopdbShadow -ShopdbManifestPath $sync.Path -ShareManifestPath $ShareManifestPath if ($diff.Same) { Write-Log 'Shadow: shopdb manifest matches the share.' } else { Write-Log ("Shadow DIFF: shopdb-only=[{0}] share-only=[{1}] orderDiff={2}" -f ` ($diff.ShopdbOnly -join ','), ($diff.ShareOnly -join ','), $diff.OrderDiff) 'WARN' } } # Which manifest the engine actually runs against. if ($ShadowMode -and $ShareManifestPath) { # Shadow: install from the share exactly as today (no payload resolve). $manifestToRun = $ShareManifestPath } else { # Cutover: stage any http/inline payloads to local files and rewrite the # manifest to point at them, so the UNCHANGED engine installs from local # (no SMB needed for share-less PCs). $manifestToRun = Resolve-ShopdbPayloads -ManifestPath $sync.Path -Config $config if ($manifestToRun -ne $sync.Path) { Write-Log "Resolved http/inline payloads to local files: $manifestToRun" } } # --- INTEGRATION POINT --------------------------------------------------- # Run the engine. Install-FromManifest.ps1 is expected to return (or you # adapt it to return) a summary carrying Installed/Skipped/Failed/Filtered # and a Results list of @{Name;Action;SelfHealed;ExitCode;Message}. Wire this # to your engine's actual return/parse; the shape below is the contract. Write-Log "Running engine against $manifestToRun" $summary = & $EnginePath -ManifestPath $manifestToRun -PCType $Scope if (-not $summary) { $summary = @{ Installed = 0; Skipped = 0; Failed = 0; Filtered = 0; Results = @() } } if (-not $summary.EnforcerVersion) { $summary.EnforcerVersion = '2.6' } # Report the result (best-effort). $appliedVersion = 0 if ($sync.Version) { [int]::TryParse($sync.Version, [ref]$appliedVersion) | Out-Null } $report = New-ShopdbReport -Scope $Scope -AppliedVersion $appliedVersion -Summary $summary if (Send-ShopdbReport -Config $config -Report $report) { Write-Log "Reported: installed=$($summary.Installed) skipped=$($summary.Skipped) failed=$($summary.Failed)." } else { Write-Log 'Report POST failed (non-fatal).' 'WARN' } } catch { Write-Log "Unhandled error (non-fatal): $($_.Exception.Message)" 'ERROR' } exit 0