# Register-CheckMachineNumberTask.ps1 - Register the two-task machine # number flow at imaging time: # # 1. "Prompt Machine Number" - AtLogOn, BUILTIN\Users, Limited. # Shows InputBox + writes new number to a request file, then triggers # the SYSTEM task via schtasks /run. # # 2. "Apply Machine Number" - on-demand only (no trigger), SYSTEM, # RunLevel Highest. Reads the request file, calls Update-MachineNumber # with full HKLM + ProgramData access, writes a result JSON, removes # the request file. No GUI - the Prompt task polls the result file # and displays the dialog. # # Replaces the old single-task design that ran as the logged-in user with # pre-granted BUILTIN\Users HKLM ACLs (02-MachineNumberACLs.ps1). That # approach was fragile (timing race with eDNC install, silent ACL skip) # and a security hole (any user could write to the machine-identity reg # key). With SYSTEM doing the actual writes, no ACL grants needed. # # Idempotent: safe to re-run. Existing tasks are overwritten. # # File kept named Register-CheckMachineNumberTask.ps1 (rather than # Register-MachineNumberTasks.ps1) so Run-ShopfloorSetup's existing # discovery doesn't need editing. $ErrorActionPreference = 'Continue' $logDir = 'C:\Logs\SFLD' if (-not (Test-Path $logDir)) { New-Item -Path $logDir -ItemType Directory -Force | Out-Null } $logFile = Join-Path $logDir 'register-checkmn.log' function Write-RegLog { param([string]$Message) $line = '[{0}] [INFO] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Message Add-Content -Path $logFile -Value $line -ErrorAction SilentlyContinue Write-Host $line } Write-RegLog '=== Register-CheckMachineNumberTask start ===' $promptTaskName = 'Prompt Machine Number' $applyTaskName = 'Apply Machine Number' $oldTaskName = 'Check Machine Number' # legacy, removed below # Clean up the legacy single-task name from prior imaging cycles. try { if (Get-ScheduledTask -TaskName $oldTaskName -ErrorAction SilentlyContinue) { Unregister-ScheduledTask -TaskName $oldTaskName -Confirm:$false -ErrorAction Stop Write-RegLog "Unregistered legacy task '$oldTaskName'" } } catch { Write-RegLog "Could not unregister legacy '$oldTaskName': $_" } # Only arm the tasks if the bay was imaged with the 9999 placeholder. If # the tech entered a real machine number during PXE imaging it's already # in C:\Enrollment\machine-number.txt; no prompt needed on first logon. $mnFile = 'C:\Enrollment\machine-number.txt' $mnAtImaging = '9999' if (Test-Path -LiteralPath $mnFile) { $raw = (Get-Content -LiteralPath $mnFile -First 1 -ErrorAction SilentlyContinue) if ($raw) { $mnAtImaging = $raw.Trim() } } Write-RegLog "Imaging-time machine-number.txt = '$mnAtImaging'" if ($mnAtImaging -ne '9999') { Write-RegLog "Machine number is real ('$mnAtImaging' != 9999). Not registering tasks." foreach ($t in @($promptTaskName, $applyTaskName)) { try { if (Get-ScheduledTask -TaskName $t -ErrorAction SilentlyContinue) { Unregister-ScheduledTask -TaskName $t -Confirm:$false -ErrorAction Stop Write-RegLog "Unregistered stale task '$t'" } } catch {} } Write-RegLog '=== Register-CheckMachineNumberTask end (no-op) ===' exit 0 } # Resolve script paths. Prefer the staged shopfloor-setup tree on C: # (where Run-ShopfloorSetup ran from); fall back to the same dir as this # Register script if invoked standalone. function Resolve-Script { param([string]$LeafName) $p = Join-Path $PSScriptRoot $LeafName if (Test-Path -LiteralPath $p) { return $p } $p = "C:\Enrollment\shopfloor-setup\Shopfloor\$LeafName" if (Test-Path -LiteralPath $p) { return $p } return $null } $promptScript = Resolve-Script 'Prompt-MachineNumber.ps1' $applyScript = Resolve-Script 'Apply-MachineNumber.ps1' if (-not $promptScript) { Write-RegLog "Prompt-MachineNumber.ps1 not found - cannot register"; exit 1 } if (-not $applyScript) { Write-RegLog "Apply-MachineNumber.ps1 not found - cannot register"; exit 1 } Write-RegLog "Prompt script: $promptScript" Write-RegLog "Apply script: $applyScript" # --- Prompt task (user-context, GUI) --- try { $action = New-ScheduledTaskAction ` -Execute 'powershell.exe' ` -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Normal -File `"$promptScript`"" $trigger = New-ScheduledTaskTrigger -AtLogOn # Group SID S-1-5-32-545 = BUILTIN\Users (catches ShopFloor + support/admin # users that log in interactively). RunLevel Limited - no elevation; the # actual writes happen in the SYSTEM Apply task below. $principal = New-ScheduledTaskPrincipal -GroupId 'S-1-5-32-545' -RunLevel Limited $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 5) Register-ScheduledTask -TaskName $promptTaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force -ErrorAction Stop | Out-Null Write-RegLog "Registered scheduled task '$promptTaskName' (AtLogOn, BUILTIN\Users, Limited)" } catch { Write-RegLog "FAILED to register '$promptTaskName': $_" exit 1 } # --- Apply task (SYSTEM, on-demand) --- try { $action = New-ScheduledTaskAction ` -Execute 'powershell.exe' ` -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$applyScript`"" # No trigger - the Prompt task starts this via schtasks /run /tn. $principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 10) Register-ScheduledTask -TaskName $applyTaskName -Action $action -Principal $principal -Settings $settings -Force -ErrorAction Stop | Out-Null Write-RegLog "Registered scheduled task '$applyTaskName' (on-demand, SYSTEM, Highest)" # Default SDDL on a SYSTEM-owned task only grants Admins + SYSTEM # FullAccess - BUILTIN\Users can't see or run it via schtasks /run. # Add an ACE granting BUILTIN\Users GenericRead + GenericExecute so the # user-context Prompt task can trigger this Apply task on demand. They # still can't modify/delete it - only read+execute. try { $svc = New-Object -ComObject Schedule.Service $svc.Connect() $taskObj = $svc.GetFolder('\').GetTask($applyTaskName) # GenericRead = 0x80000000 (GR), GenericExecute = 0x20000000 (GX) # BU = BUILTIN\Users $newSd = 'O:BAG:BAD:(A;;FA;;;BA)(A;;FA;;;SY)(A;;GRGX;;;BU)' # SetSecurityDescriptor flag 0 = default, persists DACL change. $taskObj.SetSecurityDescriptor($newSd, 0) Write-RegLog "Granted BUILTIN\Users GR+GX on '$applyTaskName' (so Limited users can schtasks /run)" } catch { Write-RegLog "FAILED to set task SDDL on '$applyTaskName': $_ (Limited users may not be able to trigger Apply)" } } catch { Write-RegLog "FAILED to register '$applyTaskName': $_" exit 1 } Write-RegLog '=== Register-CheckMachineNumberTask end ===' exit 0