Replace all Unicode characters with ASCII in playbook scripts

Em dashes (U+2014) and arrows (U+2192) break PowerShell 5.1 on
Windows when the file has no UTF-8 BOM -- byte 0x94 gets read as
a right double quote in Windows-1252, silently closing strings
mid-parse. This caused run-enrollment.ps1 to fail on PXE-imaged
machines with "string is missing the terminator" at line 113.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-04-10 13:23:11 -04:00
parent fb5841eb20
commit c06310f5bd
7 changed files with 321 additions and 275 deletions

View File

@@ -1,6 +1,11 @@
# run-enrollment.ps1
# Installs GCCH enrollment provisioning package via Install-ProvisioningPackage
# Called by FirstLogonCommands as SupportUser (admin) after imaging
# Installs GCCH enrollment provisioning package, waits for all PPKG apps
# to finish installing, registers sync_intune as a persistent @logon task,
# then reboots.
#
# Called by Run-ShopfloorSetup.ps1 AFTER all PreInstall + type-specific
# apps are already installed (not as a FirstLogonCommand -- that was the
# old flow).
$ErrorActionPreference = 'Continue'
$logFile = "C:\Logs\enrollment.log"
@@ -19,8 +24,8 @@ Log "=== GE Aerospace GCCH Enrollment ==="
# --- Find the .ppkg ---
$ppkgFile = Get-ChildItem "C:\Enrollment\*.ppkg" -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $ppkgFile) {
Log "ERROR: No .ppkg found in C:\Enrollment\"
exit 1
Log "No .ppkg found in C:\Enrollment\ - skipping enrollment."
return
}
Log "Package: $($ppkgFile.Name)"
@@ -43,28 +48,25 @@ try {
Log "Provisioning package added successfully (fallback)."
} catch {
Log "ERROR: Fallback also failed: $_"
exit 1
return
}
}
# --- Wait for PPKG provisioning to finish ---
# Install-ProvisioningPackage is async it queues the provisioning engine
# Install-ProvisioningPackage is async -- it queues the provisioning engine
# and returns immediately. The actual app installs (Chrome, Office, Tanium,
# CyberArk, etc.) run in the background as BPRT steps. Each step writes a
# Log.txt under C:\Logs\BPRT\<step name>\. "Remove Staging Locations" is
# the LAST step — when its Log.txt exists, all provisioning is done.
# We poll for that marker before writing the stage file, so the PPKG reboot
# doesn't fire while installs are still in progress.
# CyberArk, etc.) run in the background as BPRT steps. "Remove Staging
# Locations" is the LAST step -- when its Log.txt exists, all provisioning
# is done.
$bprtMarker = 'C:\Logs\BPRT\Remove Staging Locations\Log.txt'
$maxWait = 900 # 15 minutes (Office install can be slow)
$pollInterval = 10
$elapsed = 0
Log "Waiting for PPKG provisioning to complete (polling for $bprtMarker)..."
Log "Waiting for PPKG provisioning to complete..."
while (-not (Test-Path -LiteralPath $bprtMarker) -and $elapsed -lt $maxWait) {
Start-Sleep -Seconds $pollInterval
$elapsed += $pollInterval
# Show progress every 30 seconds
if ($elapsed % 30 -eq 0) {
$completedSteps = @(Get-ChildItem 'C:\Logs\BPRT' -Directory -ErrorAction SilentlyContinue |
Where-Object { Test-Path (Join-Path $_.FullName 'Log.txt') } |
@@ -75,11 +77,8 @@ while (-not (Test-Path -LiteralPath $bprtMarker) -and $elapsed -lt $maxWait) {
if (Test-Path -LiteralPath $bprtMarker) {
Log "PPKG provisioning complete after $elapsed s."
$allSteps = @(Get-ChildItem 'C:\Logs\BPRT' -Directory -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name)
Log " Completed steps: $($allSteps -join ', ')"
} else {
Log "WARNING: PPKG provisioning timeout after $maxWait s — some apps may not be installed."
Log " Proceeding anyway. The SYSTEM logon task will retry incomplete items on next boot."
Log "WARNING: PPKG provisioning timeout after $maxWait s."
}
# --- Set OOBE complete ---
@@ -87,27 +86,56 @@ Log "Setting OOBE as complete..."
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\OOBE" /v OOBEComplete /t REG_DWORD /d 1 /f | Out-Null
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\OOBE" /v SetupDisplayedEula /t REG_DWORD /d 1 /f | Out-Null
# --- Stage the imaging chain for next boot ---
# The PPKG schedules a reboot (PendingFileRenameOperations for Zscaler
# rename, PPKG self-cleanup, etc). Instead of canceling it and cramming
# Run-ShopfloorSetup into this same session, we let the reboot happen
# and register a RunOnce entry that fires Stage-Dispatcher.ps1 on the
# next autologon. The dispatcher reads setup-stage.txt and chains
# through: shopfloor-setup -> sync-intune -> configure-pc.
$stageFile = 'C:\Enrollment\setup-stage.txt'
$dispatcherPath = 'C:\Enrollment\Stage-Dispatcher.ps1'
$runOnceKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce'
# --- Register sync_intune as persistent @logon scheduled task ---
# sync_intune monitors the Intune enrollment lifecycle (5-phase status
# table). It stays registered as a logon task until:
# - Pre-reboot: monitors until Phase 1+2+3 done -> reboots
# - Post-reboot: monitors until DSC install complete -> unregisters
# itself, launches Configure-PC
#
# Runs as BUILTIN\Users (logged-in user) so it can show GUI (QR code,
# status table). Needs the interactive session, NOT SYSTEM.
$taskName = 'Shopfloor Intune Sync'
$monitorScript = 'C:\Enrollment\shopfloor-setup\Shopfloor\lib\Monitor-IntuneProgress.ps1'
$configureScript = 'C:\Enrollment\shopfloor-setup\Shopfloor\Configure-PC.ps1'
Log "Writing stage file: shopfloor-setup"
Set-Content -LiteralPath $stageFile -Value 'shopfloor-setup' -Force
if (Test-Path -LiteralPath $monitorScript) {
try {
$action = New-ScheduledTaskAction `
-Execute 'powershell.exe' `
-Argument "-NoProfile -NoExit -ExecutionPolicy Bypass -File `"$monitorScript`" -AsTask -ConfigureScript `"$configureScript`""
if (Test-Path -LiteralPath $dispatcherPath) {
Log "Registering RunOnce for Stage-Dispatcher.ps1"
Set-ItemProperty -Path $runOnceKey -Name 'ShopfloorSetup' `
-Value "powershell.exe -NoProfile -ExecutionPolicy Bypass -File `"$dispatcherPath`"" `
-Type String -Force
$trigger = New-ScheduledTaskTrigger -AtLogOn
$principal = New-ScheduledTaskPrincipal `
-GroupId 'S-1-5-32-545' `
-RunLevel Limited
$settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-ExecutionTimeLimit (New-TimeSpan -Hours 2)
Register-ScheduledTask `
-TaskName $taskName `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Settings $settings `
-Force `
-ErrorAction Stop | Out-Null
Log "Registered '$taskName' logon task (persists until sync complete)."
} catch {
Log "WARNING: Failed to register sync task: $_"
}
} else {
Log "WARNING: Stage-Dispatcher.ps1 not found at $dispatcherPath - RunOnce not set"
Log "WARNING: Monitor-IntuneProgress.ps1 not found at $monitorScript"
}
Log "=== Enrollment complete. PPKG reboot will fire and Stage-Dispatcher picks up on next logon. ==="
Log "=== Enrollment complete. Rebooting... ==="
# Reboot -- PPKG file operations (Zscaler rename, cleanup) happen on next boot.
# sync_intune fires at next logon via the scheduled task.
shutdown /r /t 10