9999 machine-number prompt: split into user-context Prompt + SYSTEM-context Apply

OLD design: a single 'Check Machine Number' scheduled task ran as the
logged-in user (BUILTIN\Users, Limited) on AtLogOn. It both showed the
InputBox AND tried to update HKLM\SOFTWARE\WOW6432Node\GE Aircraft
Engines\DNC\General + C:\ProgramData\UDC\udc_settings.json. To make
those non-admin writes possible, 02-MachineNumberACLs.ps1 pre-granted
BUILTIN\Users SetValue + Modify on those targets during imaging.

Three problems with that:
  1. SECURITY: any logged-in user could overwrite the machine-identity
     reg key.
  2. FRAGILE: ACL grants raced with eDNC install timing on some bays
     (eDNC reg key didn't exist yet when 02-MachineNumberACLs ran;
     OpenSubKey returned null, ACL silently skipped, Check-MachineNumber
     later failed with PermissionDenied).
  3. SILENT-SUCCESS BUG: Update-MachineNumber's Set-ItemProperty calls
     lacked -ErrorAction Stop. PermissionDenied is a non-terminating
     error in PS5.1, so the try/catch never fired. The script set
     $out.EdncUpdated=$true anyway and the dialog reported success
     while the reg value stayed at 9999. WJF capture log on FGY07FZ3
     shows this exact pattern.

NEW design - two scheduled tasks split by responsibility:

  - "Prompt Machine Number" : AtLogOn trigger, BUILTIN\Users (Limited).
    Reads current values (read-only). If 9999, shows InputBox. Writes
    typed number to C:\Logs\SFLD\machine-number-request.txt. Triggers
    SYSTEM Apply via schtasks /run. Polls for result JSON (60s timeout).
    Shows result MessageBox with TopMost so it isn't hidden behind
    other windows.

  - "Apply Machine Number" : on-demand, SYSTEM (Highest). Reads the
    request file, calls Update-MachineNumber (full HKLM + ProgramData
    access from SYSTEM context). Pulls per-machine NTLARS .reg + UDC
    settings JSON + UDC live data from the SFLD share if site-config
    has share paths. Writes result JSON. Removes request file.
    Unregisters the Prompt task on full success (Prompt itself can't
    self-unregister - Limited users can't delete a SYSTEM-owned task).

  - Default task SDDL only allows Admins + SYSTEM to read/run a
    SYSTEM-owned task. Added BUILTIN\Users GR+GX ACE via COM
    SetSecurityDescriptor so the Limited Prompt task can schtasks /run
    Apply on demand. They can read + execute it; not modify or delete.

  - Update-MachineNumber.ps1 writes now have -ErrorAction Stop so
    PermissionDenied actually fires the catch block instead of being
    swallowed.

  - 02-MachineNumberACLs.ps1 gutted to a no-op (left in place for
    Stage-Dispatcher discovery; no longer grants the ACLs). Old bays'
    existing grants are harmless since SYSTEM ignores them.

  - Register-CheckMachineNumberTask.ps1 now installs both tasks AND
    unregisters the legacy 'Check Machine Number' task name on
    re-imaging. Run-ShopfloorSetup.ps1's $skipInBaseline list now
    includes Prompt-MachineNumber.ps1 + Apply-MachineNumber.ps1 so
    they aren't auto-run during the baseline pass (only via the
    scheduled tasks).

Smoke tested end-to-end on win11 VM with ShopFloor (Limited) logging in
interactively: AtLogOn trigger fired Prompt, dialog rendered, tech
typed 7777, schtasks /run succeeded (the SDDL fix lets Limited users
trigger SYSTEM tasks), Apply ran as SYSTEM, eDNC reg + machine-number.txt
both updated to 7777, result MessageBox shown, Prompt task auto-
unregistered by Apply's cleanup step. No ACL grants needed on any user.

Apply also re-tested with -ErrorAction Stop confirming non-terminating
PermissionDenied now properly throws into the catch + populates Errors[]
+ flips $out.EdncUpdated to false - so any future write failures will
report honestly instead of silently claiming success.
This commit is contained in:
cproudlock
2026-05-24 17:08:59 -04:00
parent de7d41f5e5
commit 7298d433eb
7 changed files with 471 additions and 136 deletions

View File

@@ -1,81 +1,29 @@
# 02-MachineNumberACLs.ps1 - Pre-grant write access on the UDC settings
# file and eDNC registry key so that STANDARD (non-admin) users can update
# the machine number via the Check-MachineNumber logon task without
# elevation or a UAC prompt.
# 02-MachineNumberACLs.ps1 - NO-OP (deprecated 2026-05-24).
#
# Runs during imaging as admin (type-specific Standard phase, after
# 01-eDNC.ps1 has installed DnC). Only touches Standard PCs.
# This script used to grant BUILTIN\Users SetValue on the eDNC reg key
# and Modify on the UDC ProgramData dir so the logged-in user could
# update machine number from the Check-MachineNumber logon dialog without
# elevation.
#
# What gets opened up (narrow scope, not blanket admin):
# - C:\ProgramData\UDC\udc_settings.json -> BUILTIN\Users : Modify
# - HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\General
# -> BUILTIN\Users : SetValue
# That design had two flaws:
# 1. Security hole - any logged-in user could overwrite the machine-
# identity reg key.
# 2. Fragile - ACL grants raced with eDNC install timing on some bays;
# the OpenSubKey call returned null + the grant was silently skipped,
# leaving Check-MachineNumber unable to update the bay (yet the old
# Update-MachineNumber.ps1 reported success anyway because
# Set-ItemProperty's PermissionDenied is non-terminating).
#
# Replaced by the two-task design in Register-CheckMachineNumberTask.ps1:
# - "Prompt Machine Number" : user-context GUI, no privileges
# - "Apply Machine Number" : SYSTEM-context worker, full HKLM access
#
# Left as a no-op so Stage-Dispatcher / Run-ShopfloorSetup discovery
# patterns don't have to be updated. Existing bays' ACL grants are still
# present and harmless (the SYSTEM Apply task ignores them).
# --- Transcript ---
$logDir = 'C:\Logs\SFLD'
if (-not (Test-Path $logDir)) { try { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } catch {} }
try { Start-Transcript -Path (Join-Path $logDir '02-MachineNumberACLs.log') -Append -Force | Out-Null } catch {}
# --- Skip on Timeclock sub-type (no UDC/eDNC to grant ACLs for) ---
$subtypeFile = 'C:\Enrollment\pc-subtype.txt'
if (Test-Path $subtypeFile) {
$subtype = (Get-Content $subtypeFile -First 1 -ErrorAction SilentlyContinue).Trim()
if ($subtype -eq 'Timeclock') {
Write-Host "02-MachineNumberACLs: skipped (Standard-Timeclock)"
try { Stop-Transcript | Out-Null } catch {}
return
}
}
Write-Host "02-MachineNumberACLs.ps1 starting $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Host "Running as: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)"
Write-Host ""
Write-Host "Setting ACLs for standard-user machine number access..."
# --- UDC settings directory ---
# Set ACL on the DIRECTORY (not the file) with inheritance so that
# udc_settings.json inherits the permission whenever UDC.exe creates it.
# UDC_Setup.exe is killed by KillAfterDetection before UDC.exe writes the
# JSON, so the file doesn't exist at this point. Directory-level ACL with
# ContainerInherit + ObjectInherit covers any file created inside later.
$udcDir = 'C:\ProgramData\UDC'
if (Test-Path -LiteralPath $udcDir) {
try {
$acl = Get-Acl -LiteralPath $udcDir
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
'BUILTIN\Users', 'Modify',
([System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor
[System.Security.AccessControl.InheritanceFlags]::ObjectInherit),
[System.Security.AccessControl.PropagationFlags]::None,
'Allow')
$acl.AddAccessRule($rule)
Set-Acl -LiteralPath $udcDir -AclObject $acl -ErrorAction Stop
Write-Host " UDC dir: BUILTIN\Users granted Modify (inherited) on $udcDir"
} catch {
Write-Warning " Failed to set ACL on $udcDir : $_"
}
} else {
Write-Host " UDC dir not found at $udcDir - skipping (UDC not installed?)" -ForegroundColor DarkGray
}
# --- eDNC registry key ---
$ednRegPathWin = 'SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\General'
try {
$regKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($ednRegPathWin, $true)
if ($regKey) {
$regSec = $regKey.GetAccessControl()
$rule = New-Object System.Security.AccessControl.RegistryAccessRule(
'BUILTIN\Users', 'SetValue', 'Allow')
$regSec.AddAccessRule($rule)
$regKey.SetAccessControl($regSec)
$regKey.Close()
Write-Host " eDNC reg: BUILTIN\Users granted SetValue on HKLM:\$ednRegPathWin"
} else {
Write-Host " eDNC registry key not found - skipping (eDNC not installed?)" -ForegroundColor DarkGray
}
} catch {
Write-Warning " Failed to set ACL on HKLM:\$ednRegPathWin : $_"
}
Write-Host "ACL setup complete."
Write-Host "02-MachineNumberACLs.ps1: no-op (replaced by SYSTEM Apply task - see Register-CheckMachineNumberTask.ps1)"
try { Stop-Transcript | Out-Null } catch {}