# run-enrollment.ps1 # Installs GCCH enrollment provisioning package. That's it. # # Install-ProvisioningPackage triggers an immediate reboot -- nothing after # that call executes. The sync_intune task and all other post-enrollment # setup are registered by Run-ShopfloorSetup.ps1 BEFORE calling this script. $ErrorActionPreference = 'Continue' $logFile = "C:\Logs\enrollment.log" New-Item -ItemType Directory -Path "C:\Logs" -Force -ErrorAction SilentlyContinue | Out-Null function Log { param([string]$Message) $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $line = "$ts $Message" Write-Host $line Add-Content -Path $logFile -Value $line } Log "=== GE Aerospace GCCH Enrollment ===" # --- Find the .ppkg --- $ppkgFile = Get-ChildItem "C:\Enrollment\*.ppkg" -ErrorAction SilentlyContinue | Select-Object -First 1 if (-not $ppkgFile) { Log "No .ppkg found in C:\Enrollment\ - skipping enrollment." return } Log "Package: $($ppkgFile.Name)" # --- Set computer name to E --- $serial = (Get-CimInstance Win32_BIOS).SerialNumber $newName = "E$serial" Log "Setting computer name to $newName" Rename-Computer -NewName $newName -Force -ErrorAction SilentlyContinue # --- Install provisioning package --- # IMPORTANT: The PPKG must be installed BEFORE OOBEComplete is set. Bulk # enrollment PPKGs are designed to run during OOBE; on Windows 11 22H2+ they # can hang indefinitely if OOBE is already marked complete. # # We invoke provtool.exe directly instead of Install-ProvisioningPackage. # The PowerShell cmdlet enforces a hardcoded 180-second timeout on the # underlying provtool call, which a 7-8 GB GCCH PPKG often exceeds on # slower disks. When the cmdlet times out it throws, and the Add- # ProvisioningPackage fallback has been observed to invoke provtool with # an empty packagePathsToAdd (session registered but never started), # leaving the PC un-enrolled. provtool.exe directly has no caller-side # timeout; Start-Process -Wait waits on the actual child process. # # The PPKG triggers an IMMEDIATE reboot once fully applied. Nothing below # that point executes on the current boot. BPRT app installs (Chrome, # Office, Tanium, etc.) happen on the next boot. The sync_intune # scheduled task (registered by Run-ShopfloorSetup.ps1 before calling us) # fires at the next logon to monitor Intune enrollment. $ppkgLogDir = "C:\Logs\PPKG" New-Item -ItemType Directory -Path $ppkgLogDir -Force -ErrorAction SilentlyContinue | Out-Null $provtool = Join-Path $env:SystemRoot 'System32\provtool.exe' # Arg order matches what the Install-ProvisioningPackage cmdlet invokes # internally (observed in ProvEventLog.txt): positional path, then /quiet, # then /source. No /log: or /ppkg: prefix - those are not valid provtool # flags and caused 0x80004005 E_FAIL in the first test. # # /source BPRT keeps us in the bulk-provisioning code path used by the # automatic OOBE PPKG runtime. PSCmdlet would give richer ETW/event-log # diagnostics, but may change which actions in the PPKG actually execute # (not verified against Microsoft docs). Stay on BPRT and enable logging # manually below. $provArgs = @("`"$($ppkgFile.FullName)`"", "/quiet", "/source", "BPRT") # Enable the Provisioning-Diagnostics-Provider Admin channel so events # from the BPRT run land somewhere we can export afterward. This is # idempotent - running each time is safe. wevtutil.exe set-log 'Microsoft-Windows-Provisioning-Diagnostics-Provider/Admin' /enabled:true 2>$null | Out-Null Log "Installing provisioning package via provtool.exe (no PowerShell timeout)..." Log "Command: $provtool $($provArgs -join ' ')" Log "PPKG diagnostic logs -> $ppkgLogDir (provtool writes them automatically)" try { $p = Start-Process -FilePath $provtool -ArgumentList $provArgs -Wait -PassThru -NoNewWindow -ErrorAction Stop Log "provtool.exe exit code: $($p.ExitCode)" if ($p.ExitCode -ne 0) { $hex = '0x{0:X8}' -f $p.ExitCode Log "WARNING: provtool.exe returned non-zero exit code ($hex). Check $ppkgLogDir for diagnostic bundle." } } catch { Log "ERROR: Failed to launch provtool.exe: $_" } # --- Harvest Windows' own provisioning diagnostics into $ppkgLogDir --- # provtool.exe /quiet does not drop a zip bundle on success, so the real # detail lives under C:\ProgramData\Microsoft\Provisioning\, in the # registry under HKLM\Software\Microsoft\Provisioning\Sessions\*, and in # the Provisioning-Diagnostics-Provider event log. Copy/export all three # into our log dir so they ride back with the shopfloor logs bundle. Log "Harvesting Windows provisioning diagnostics to $ppkgLogDir..." try { $provData = 'C:\ProgramData\Microsoft\Provisioning' if (Test-Path $provData) { Copy-Item -Path (Join-Path $provData '*') -Destination $ppkgLogDir ` -Recurse -Force -ErrorAction SilentlyContinue Log " copied $provData -> $ppkgLogDir" } else { Log " $provData not present (provtool may not have touched it)" } } catch { Log " WARN: ProgramData copy threw: $_" } try { $sessions = Get-ChildItem 'HKLM:\Software\Microsoft\Provisioning\Sessions' -ErrorAction SilentlyContinue if ($sessions) { $snap = $sessions | ForEach-Object { $props = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue if ($props) { [pscustomobject]@{ Session = $_.PSChildName BeginTime = $props.BeginTime LastRunTime = $props.LastRunTime RebootCount = $props.RebootCount State = $props.State StateValue = $props.StateValue } } } $snap | ConvertTo-Json -Depth 3 | Out-File -FilePath (Join-Path $ppkgLogDir 'provisioning-sessions.json') -Encoding UTF8 Log " wrote provisioning-sessions.json ($($snap.Count) session(s))" foreach ($s in $snap) { Log " session $($s.Session): State=$($s.State) RebootCount=$($s.RebootCount)" } } else { Log " no sessions under HKLM:\Software\Microsoft\Provisioning\Sessions" } } catch { Log " WARN: session snapshot threw: $_" } try { $evtx = Join-Path $ppkgLogDir 'Provisioning-Diagnostics-Admin.evtx' if (Test-Path $evtx) { Remove-Item $evtx -Force -ErrorAction SilentlyContinue } $null = & wevtutil.exe epl 'Microsoft-Windows-Provisioning-Diagnostics-Provider/Admin' $evtx 2>&1 if (Test-Path $evtx) { Log " exported $evtx" } } catch { Log " WARN: wevtutil export threw: $_" } # --- Set OOBE complete (only reached if PPKG didn't trigger immediate reboot) --- 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 # If we get here, the PPKG didn't reboot immediately. Unlikely but handle it. Log "PPKG did not trigger immediate reboot. Returning to caller."