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:
115
playbook/shopfloor-setup/Shopfloor/Apply-MachineNumber.ps1
Normal file
115
playbook/shopfloor-setup/Shopfloor/Apply-MachineNumber.ps1
Normal file
@@ -0,0 +1,115 @@
|
||||
# Apply-MachineNumber.ps1 - SYSTEM-context worker for the two-task machine
|
||||
# number flow. Triggered on-demand by Prompt-MachineNumber.ps1 via
|
||||
# `schtasks /run /tn "WT-Apply-MachineNumber"`. Reads the requested number
|
||||
# from a file the GUI script wrote, invokes Update-MachineNumber as SYSTEM
|
||||
# (full HKLM + ProgramData access), writes a result JSON for the GUI to
|
||||
# display, then cleans up.
|
||||
#
|
||||
# Why SYSTEM:
|
||||
# The eDNC reg key (HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\
|
||||
# General\MachineNo) and UDC settings JSON live in HKLM + ProgramData
|
||||
# respectively - both require admin to write. The OLD design granted
|
||||
# BUILTIN\Users SetValue + Modify via 02-MachineNumberACLs.ps1, but that
|
||||
# was fragile (timing race with eDNC install, ACL silently failed on
|
||||
# some bays) AND a security hole (any user could mess with the machine
|
||||
# identity). Two-task design: GUI gathers input as logged-in user, SYSTEM
|
||||
# does the actual write.
|
||||
#
|
||||
# Files:
|
||||
# C:\Logs\SFLD\machine-number-request.txt - input, single line, new number
|
||||
# C:\Logs\SFLD\machine-number-result.json - output, status fields for GUI
|
||||
# C:\Logs\SFLD\Apply-MachineNumber.log - transcript
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
$logDir = 'C:\Logs\SFLD'
|
||||
if (-not (Test-Path -LiteralPath $logDir)) {
|
||||
try { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } catch { $logDir = $env:TEMP }
|
||||
}
|
||||
$transcript = Join-Path $logDir 'Apply-MachineNumber.log'
|
||||
try { Start-Transcript -Path $transcript -Append -Force | Out-Null } catch {}
|
||||
|
||||
$requestFile = Join-Path $logDir 'machine-number-request.txt'
|
||||
$resultFile = Join-Path $logDir 'machine-number-result.json'
|
||||
|
||||
function Write-Result {
|
||||
param([hashtable]$Body)
|
||||
$Body | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $resultFile -Encoding ascii -Force
|
||||
}
|
||||
|
||||
Write-Host "Apply-MachineNumber.ps1 starting $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
||||
Write-Host "Running as: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)"
|
||||
|
||||
try {
|
||||
if (-not (Test-Path -LiteralPath $requestFile)) {
|
||||
Write-Warning "No request file at $requestFile - nothing to apply."
|
||||
Write-Result @{ Status = 'NoRequest'; Errors = @("request file missing: $requestFile") }
|
||||
exit 0
|
||||
}
|
||||
$newNumber = (Get-Content -LiteralPath $requestFile -First 1 -ErrorAction Stop).Trim()
|
||||
Write-Host "Requested new machine number: $newNumber"
|
||||
|
||||
if ($newNumber -notmatch '^\d+$') {
|
||||
Write-Warning "Request is not digits-only: '$newNumber'"
|
||||
Write-Result @{ Status = 'BadInput'; Requested = $newNumber; Errors = @("Not digits only: '$newNumber'") }
|
||||
Remove-Item -LiteralPath $requestFile -Force -ErrorAction SilentlyContinue
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Dot-source the shared helper. Update-MachineNumber.ps1 now has
|
||||
# -ErrorAction Stop on the writes so failures actually throw.
|
||||
. "$PSScriptRoot\lib\Get-PCProfile.ps1"
|
||||
. "$PSScriptRoot\lib\Update-MachineNumber.ps1"
|
||||
|
||||
$site = if ($siteConfig) { $siteConfig.siteName } else { 'West Jefferson' }
|
||||
$mnResult = Update-MachineNumber -NewNumber $newNumber -Site $site
|
||||
|
||||
$resultBody = @{
|
||||
Status = if ($mnResult.Errors.Count -eq 0) { 'OK' } else { 'PartialErrors' }
|
||||
Requested = $newNumber
|
||||
Site = $site
|
||||
UdcUpdated = [bool]$mnResult.UdcUpdated
|
||||
EdncUpdated = [bool]$mnResult.EdncUpdated
|
||||
OldUdc = $mnResult.OldUdc
|
||||
OldEdnc = $mnResult.OldEdnc
|
||||
UdcSettingsRestored = [bool]$mnResult.UdcSettingsRestored
|
||||
UdcRestored = [bool]$mnResult.UdcRestored
|
||||
MTConnectUpdated = $mnResult.MTConnectUpdated
|
||||
MachineNumberTxtUpdated = [bool]$mnResult.MachineNumberTxtUpdated
|
||||
Errors = $mnResult.Errors
|
||||
AppliedAt = (Get-Date -Format 'o')
|
||||
AppliedAs = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
||||
}
|
||||
Write-Result -Body $resultBody
|
||||
|
||||
Write-Host "Update-MachineNumber result:"
|
||||
Write-Host " UdcUpdated = $($mnResult.UdcUpdated)"
|
||||
Write-Host " EdncUpdated = $($mnResult.EdncUpdated)"
|
||||
Write-Host " Errors = $($mnResult.Errors.Count)"
|
||||
if ($mnResult.Errors) { $mnResult.Errors | ForEach-Object { Write-Host " FAILED: $_" } }
|
||||
|
||||
Remove-Item -LiteralPath $requestFile -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# On clean success, also unregister the Prompt logon task. Prompt itself
|
||||
# tries to self-unregister but it runs as a Limited user (BUILTIN\Users)
|
||||
# and silently fails on Unregister-ScheduledTask (no delete right on a
|
||||
# SYSTEM-registered task). We're running as SYSTEM here, so we can.
|
||||
# Idempotent if Prompt already unregistered itself somehow.
|
||||
if ($mnResult.Errors.Count -eq 0 -and $mnResult.EdncUpdated) {
|
||||
try {
|
||||
if (Get-ScheduledTask -TaskName 'Prompt Machine Number' -ErrorAction SilentlyContinue) {
|
||||
Unregister-ScheduledTask -TaskName 'Prompt Machine Number' -Confirm:$false -ErrorAction Stop
|
||||
Write-Host "Unregistered 'Prompt Machine Number' task (SYSTEM cleanup)."
|
||||
}
|
||||
} catch {
|
||||
Write-Host "Could not unregister 'Prompt Machine Number': $_"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Apply-MachineNumber.ps1 finished $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
||||
} catch {
|
||||
Write-Warning "Apply threw: $_"
|
||||
Write-Result @{ Status = 'Exception'; Errors = @("$_") }
|
||||
} finally {
|
||||
try { Stop-Transcript | Out-Null } catch {}
|
||||
}
|
||||
@@ -93,13 +93,24 @@ if ($mnResult.EdncUpdated) { $results += "eDNC updated to $new" }
|
||||
foreach ($err in $mnResult.Errors) { $results += $err -replace '^', 'FAILED: ' }
|
||||
|
||||
# --- Show result ---
|
||||
$summary = ($results -join "`n") + "`n`nTo apply eDNC changes, restart any running DncMain.exe."
|
||||
$summary = ($results -join "`n") + "`n`nTo apply eDNC changes, restart any running DncMain.exe.`n`nFull log: C:\Logs\SFLD\Check-MachineNumber.log"
|
||||
# Force the MessageBox to topmost + take focus so it isn't hidden behind
|
||||
# other windows. Without this, the result dialog can render off-screen or
|
||||
# behind the FormTracePak / DNC windows and the tech misses it.
|
||||
$tmpForm = New-Object System.Windows.Forms.Form
|
||||
$tmpForm.TopMost = $true
|
||||
$tmpForm.WindowState = 'Minimized'
|
||||
$tmpForm.ShowInTaskbar = $false
|
||||
$tmpForm.Opacity = 0
|
||||
$tmpForm.Show()
|
||||
[System.Windows.Forms.MessageBox]::Show(
|
||||
$tmpForm,
|
||||
$summary,
|
||||
"Machine Number Updated",
|
||||
[System.Windows.Forms.MessageBoxButtons]::OK,
|
||||
[System.Windows.Forms.MessageBoxIcon]::Information
|
||||
) | Out-Null
|
||||
$tmpForm.Close()
|
||||
|
||||
# --- Unregister task on success ---
|
||||
Write-Host "Results: $($results -join '; ')"
|
||||
|
||||
200
playbook/shopfloor-setup/Shopfloor/Prompt-MachineNumber.ps1
Normal file
200
playbook/shopfloor-setup/Shopfloor/Prompt-MachineNumber.ps1
Normal file
@@ -0,0 +1,200 @@
|
||||
# Prompt-MachineNumber.ps1 - User-context GUI script for the two-task
|
||||
# machine number flow. Triggered AtLogOn for any BUILTIN\Users member.
|
||||
#
|
||||
# Flow:
|
||||
# 1. Read current UDC + eDNC values (read-only - no privileges needed).
|
||||
# 2. If neither is 9999, unregister self and exit (this PC is set up).
|
||||
# 3. Show InputBox for new machine number.
|
||||
# 4. Write number to C:\Logs\SFLD\machine-number-request.txt.
|
||||
# 5. Trigger the SYSTEM-context Apply-MachineNumber task via
|
||||
# schtasks /run. SYSTEM has full HKLM + ProgramData access so the
|
||||
# actual write happens with proper privileges - the prompted user
|
||||
# never needs HKLM write rights (security improvement over the old
|
||||
# 02-MachineNumberACLs.ps1 ACL-grant hack).
|
||||
# 6. Poll for C:\Logs\SFLD\machine-number-result.json (30s timeout).
|
||||
# 7. Show result MessageBox. Unregister self on success.
|
||||
#
|
||||
# Why this script doesn't do the writes itself: GUI is required (InputBox),
|
||||
# but GUI requires user-context (SYSTEM can't render to user desktop on
|
||||
# modern Windows). The user-context dialog gathers input; the SYSTEM task
|
||||
# does privileged writes.
|
||||
|
||||
# --- Transcript ---
|
||||
$logDir = 'C:\Logs\SFLD'
|
||||
if (-not (Test-Path -LiteralPath $logDir)) {
|
||||
try { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } catch { $logDir = $env:TEMP }
|
||||
}
|
||||
$transcript = Join-Path $logDir 'Prompt-MachineNumber.log'
|
||||
try { Start-Transcript -Path $transcript -Append -Force | Out-Null } catch {}
|
||||
Write-Host "Prompt-MachineNumber.ps1 starting $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
||||
Write-Host "Running as: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)"
|
||||
|
||||
. "$PSScriptRoot\lib\Get-PCProfile.ps1"
|
||||
. "$PSScriptRoot\lib\Update-MachineNumber.ps1"
|
||||
|
||||
Add-Type -AssemblyName Microsoft.VisualBasic
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
|
||||
$taskName = 'Prompt Machine Number'
|
||||
$applyTaskName = 'Apply Machine Number'
|
||||
$requestFile = Join-Path $logDir 'machine-number-request.txt'
|
||||
$resultFile = Join-Path $logDir 'machine-number-result.json'
|
||||
$site = if ($siteConfig) { $siteConfig.siteName } else { 'West Jefferson' }
|
||||
|
||||
# --- Read current values (read-only, no perms needed) ---
|
||||
$currentMN = Get-CurrentMachineNumber
|
||||
$currentUdc = $currentMN.Udc
|
||||
$currentEdnc = $currentMN.Ednc
|
||||
Write-Host "UDC machine number: $(if ($currentUdc) { $currentUdc } else { '(not found)' })"
|
||||
Write-Host "eDNC machine number: $(if ($currentEdnc) { $currentEdnc } else { '(not found)' })"
|
||||
|
||||
if ($currentUdc -ne '9999' -and $currentEdnc -ne '9999') {
|
||||
Write-Host "Machine number is set (not 9999). Unregistering Prompt task and exiting."
|
||||
try { Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue } catch {}
|
||||
try { Stop-Transcript | Out-Null } catch {}
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "Placeholder 9999 detected - showing prompt."
|
||||
|
||||
# --- Show prompt ---
|
||||
$promptLines = @()
|
||||
$promptLines += "The machine number on this PC is still set to the"
|
||||
$promptLines += "placeholder value (9999). Please enter the correct"
|
||||
$promptLines += "machine number for this workstation."
|
||||
$promptLines += ""
|
||||
if ($currentUdc) { $promptLines += "Current UDC: $currentUdc" }
|
||||
if ($currentEdnc) { $promptLines += "Current eDNC: $currentEdnc" }
|
||||
$promptLines += ""
|
||||
$promptLines += "Enter the new Machine Number:"
|
||||
$prompt = $promptLines -join "`n"
|
||||
$new = [Microsoft.VisualBasic.Interaction]::InputBox($prompt, "Set Machine Number", "")
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($new)) {
|
||||
Write-Host "User cancelled. Will prompt again next logon."
|
||||
try { Stop-Transcript | Out-Null } catch {}
|
||||
exit 0
|
||||
}
|
||||
$new = $new.Trim()
|
||||
|
||||
if ($new -notmatch '^\d+$') {
|
||||
Write-Host "Invalid input: '$new' (not digits only). Showing error and re-prompting next logon."
|
||||
[System.Windows.Forms.MessageBox]::Show(
|
||||
"Machine number must be digits only.`n`nYou entered: '$new'`n`nThe prompt will appear again at next logon.",
|
||||
"Invalid Machine Number",
|
||||
[System.Windows.Forms.MessageBoxButtons]::OK,
|
||||
[System.Windows.Forms.MessageBoxIcon]::Error
|
||||
) | Out-Null
|
||||
try { Stop-Transcript | Out-Null } catch {}
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --- Hand off to SYSTEM task ---
|
||||
# Clean any stale request / result files first so we read fresh ones.
|
||||
Remove-Item -LiteralPath $requestFile, $resultFile -Force -ErrorAction SilentlyContinue
|
||||
|
||||
try {
|
||||
Set-Content -LiteralPath $requestFile -Value $new -Encoding ascii -Force -ErrorAction Stop
|
||||
} catch {
|
||||
[System.Windows.Forms.MessageBox]::Show(
|
||||
"Could not write request file at $requestFile`n`n$_`n`nThe prompt will appear again at next logon.",
|
||||
"Machine Number Request Failed",
|
||||
[System.Windows.Forms.MessageBoxButtons]::OK,
|
||||
[System.Windows.Forms.MessageBoxIcon]::Error
|
||||
) | Out-Null
|
||||
try { Stop-Transcript | Out-Null } catch {}
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Wrote $requestFile with '$new'. Triggering SYSTEM apply task..."
|
||||
& schtasks.exe /run /tn $applyTaskName 2>&1 | ForEach-Object { Write-Host " schtasks: $_" }
|
||||
|
||||
# --- Wait for result ---
|
||||
$deadline = (Get-Date).AddSeconds(60)
|
||||
$result = $null
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
if (Test-Path -LiteralPath $resultFile) {
|
||||
try {
|
||||
$result = Get-Content -LiteralPath $resultFile -Raw -ErrorAction Stop | ConvertFrom-Json
|
||||
break
|
||||
} catch { Start-Sleep -Milliseconds 200 }
|
||||
}
|
||||
Start-Sleep -Milliseconds 500
|
||||
}
|
||||
|
||||
# Make the result MessageBox topmost so it shows above the FormTracePak /
|
||||
# DNC windows and isn't missed.
|
||||
$tmpForm = New-Object System.Windows.Forms.Form
|
||||
$tmpForm.TopMost = $true
|
||||
$tmpForm.WindowState = 'Minimized'
|
||||
$tmpForm.ShowInTaskbar = $false
|
||||
$tmpForm.Opacity = 0
|
||||
$tmpForm.Show()
|
||||
|
||||
if (-not $result) {
|
||||
Write-Host "Timed out waiting for SYSTEM apply task to produce result file ($resultFile)."
|
||||
[System.Windows.Forms.MessageBox]::Show(
|
||||
$tmpForm,
|
||||
"Timed out waiting for the SYSTEM update task to complete.`n`nCheck:`n C:\Logs\SFLD\Apply-MachineNumber.log`n C:\Logs\SFLD\Prompt-MachineNumber.log`n`nThe prompt will appear again at next logon.",
|
||||
"Machine Number Update Timed Out",
|
||||
[System.Windows.Forms.MessageBoxButtons]::OK,
|
||||
[System.Windows.Forms.MessageBoxIcon]::Warning
|
||||
) | Out-Null
|
||||
$tmpForm.Close()
|
||||
try { Stop-Transcript | Out-Null } catch {}
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Build summary from result JSON
|
||||
$lines = @()
|
||||
$lines += "Requested: $($result.Requested)"
|
||||
$lines += ""
|
||||
if ($result.UdcUpdated) { $lines += "UDC updated to $($result.Requested)" } else { $lines += "UDC: not updated (UDC may not be installed)" }
|
||||
if ($result.EdncUpdated) { $lines += "eDNC updated to $($result.Requested)" } else { $lines += "eDNC: not updated" }
|
||||
if ($result.UdcSettingsRestored) { $lines += "UDC settings restored from SFLD" }
|
||||
if ($result.UdcRestored) { $lines += "UDC live data restored from SFLD" }
|
||||
if ($result.MachineNumberTxtUpdated) { $lines += "machine-number.txt updated" }
|
||||
if ($result.MTConnectUpdated -and $result.MTConnectUpdated.Count -gt 0) {
|
||||
$lines += ""
|
||||
$lines += "MTConnect Devices.xml updates:"
|
||||
$result.MTConnectUpdated | ForEach-Object { $lines += " - $_" }
|
||||
}
|
||||
if ($result.Errors -and $result.Errors.Count -gt 0) {
|
||||
$lines += ""
|
||||
$lines += "FAILURES:"
|
||||
$result.Errors | ForEach-Object { $lines += " - $_" }
|
||||
}
|
||||
$lines += ""
|
||||
$lines += "Status: $($result.Status)"
|
||||
$lines += "Logs: C:\Logs\SFLD\Apply-MachineNumber.log"
|
||||
$lines += " C:\Logs\SFLD\Prompt-MachineNumber.log"
|
||||
$lines += ""
|
||||
$lines += "To apply eDNC changes, restart any running DncMain.exe."
|
||||
$summary = $lines -join "`n"
|
||||
|
||||
$icon = if ($result.Status -eq 'OK') { [System.Windows.Forms.MessageBoxIcon]::Information } else { [System.Windows.Forms.MessageBoxIcon]::Warning }
|
||||
[System.Windows.Forms.MessageBox]::Show(
|
||||
$tmpForm,
|
||||
$summary,
|
||||
"Machine Number Update Result",
|
||||
[System.Windows.Forms.MessageBoxButtons]::OK,
|
||||
$icon
|
||||
) | Out-Null
|
||||
$tmpForm.Close()
|
||||
|
||||
# Clean up result file for the next round.
|
||||
Remove-Item -LiteralPath $resultFile -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# Only unregister the Prompt task on full success (no errors AND eDNC
|
||||
# updated to the requested value). If anything failed, leave it registered
|
||||
# for next logon retry.
|
||||
if ($result.Status -eq 'OK' -and $result.EdncUpdated) {
|
||||
Write-Host "All updates succeeded. Unregistering Prompt task."
|
||||
try { Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue } catch {}
|
||||
} else {
|
||||
Write-Host "Some updates failed or skipped. Prompt task stays registered for next logon retry."
|
||||
}
|
||||
|
||||
Write-Host "Prompt-MachineNumber.ps1 finished $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
||||
try { Stop-Transcript | Out-Null } catch {}
|
||||
exit 0
|
||||
@@ -1,17 +1,27 @@
|
||||
# Register-CheckMachineNumberTask.ps1 - Register the "Check Machine Number"
|
||||
# logon scheduled task at imaging time. Mirrors Register-MapSfldShare.ps1.
|
||||
# Register-CheckMachineNumberTask.ps1 - Register the two-task machine
|
||||
# number flow at imaging time:
|
||||
#
|
||||
# The task fires at every interactive logon for BUILTIN\Users (so the
|
||||
# ShopFloor end-user, who is the auto-logon principal post-lockdown,
|
||||
# triggers it). Check-MachineNumber.ps1 reads the current UDC + eDNC
|
||||
# machine numbers, and:
|
||||
# - If neither is 9999, unregisters the task and exits (one-shot).
|
||||
# - If either is 9999, pops an InputBox forcing the user to type the
|
||||
# real number; on success calls Update-MachineNumber.ps1 which pulls
|
||||
# the per-machine NTLARS .reg + UDC settings JSON + UDC data backup
|
||||
# from the SFLD share and applies them.
|
||||
# 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.
|
||||
#
|
||||
# Idempotent: safe to re-run. Existing task is overwritten.
|
||||
# 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'
|
||||
|
||||
@@ -28,9 +38,19 @@ function Write-RegLog {
|
||||
|
||||
Write-RegLog '=== Register-CheckMachineNumberTask start ==='
|
||||
|
||||
$taskName = 'Check Machine Number'
|
||||
$promptTaskName = 'Prompt Machine Number'
|
||||
$applyTaskName = 'Apply Machine Number'
|
||||
$oldTaskName = 'Check Machine Number' # legacy, removed below
|
||||
|
||||
# Only arm the task if the bay was imaged with the 9999 placeholder. If
|
||||
# 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'
|
||||
@@ -41,63 +61,87 @@ if (Test-Path -LiteralPath $mnFile) {
|
||||
}
|
||||
Write-RegLog "Imaging-time machine-number.txt = '$mnAtImaging'"
|
||||
if ($mnAtImaging -ne '9999') {
|
||||
Write-RegLog "Machine number is real ('$mnAtImaging' != 9999). Not registering task."
|
||||
# Clean up any stale task from a prior 9999-imaging cycle on the same disk.
|
||||
try {
|
||||
if (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue) {
|
||||
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction Stop
|
||||
Write-RegLog "Unregistered stale task '$taskName'"
|
||||
}
|
||||
} catch {}
|
||||
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 the script path. Prefer the staged shopfloor-setup tree on C:
|
||||
# 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.
|
||||
$checkScript = Join-Path $PSScriptRoot 'Check-MachineNumber.ps1'
|
||||
if (-not (Test-Path -LiteralPath $checkScript)) {
|
||||
$checkScript = 'C:\Enrollment\shopfloor-setup\Shopfloor\Check-MachineNumber.ps1'
|
||||
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
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $checkScript)) {
|
||||
Write-RegLog "Check-MachineNumber.ps1 not found at $checkScript - cannot register"
|
||||
exit 1
|
||||
}
|
||||
Write-RegLog "Check-MachineNumber.ps1 at: $checkScript"
|
||||
|
||||
$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 `"$checkScript`""
|
||||
|
||||
-Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Normal -File `"$promptScript`""
|
||||
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
||||
|
||||
# Run as the logged-in user (needs GUI for InputBox), NOT SYSTEM.
|
||||
# Group SID S-1-5-32-545 = BUILTIN\Users; catches ShopFloor + any
|
||||
# support / admin user that logs in interactively.
|
||||
$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 $taskName `
|
||||
-Action $action `
|
||||
-Trigger $trigger `
|
||||
-Principal $principal `
|
||||
-Settings $settings `
|
||||
-Force `
|
||||
-ErrorAction Stop | Out-Null
|
||||
|
||||
Write-RegLog "Registered scheduled task '$taskName' (AtLogOn, BUILTIN\Users, Limited)"
|
||||
# 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 '$taskName': $_"
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -244,11 +244,16 @@ function Update-MachineNumber {
|
||||
Start-Sleep -Seconds 1
|
||||
|
||||
# --- Update UDC settings JSON ---
|
||||
# -ErrorAction Stop on the WRITE so PermissionDenied / IO errors become
|
||||
# terminating and actually hit the catch block. Without this, the cmdlet
|
||||
# writes a non-terminating error (visible in transcript) but flow
|
||||
# continues + $out.UdcUpdated is set to $true, leading the dialog to
|
||||
# report "UDC updated" when the file write actually failed.
|
||||
if (Test-Path $script:UdcSettingsPath) {
|
||||
try {
|
||||
$json = Get-Content $script:UdcSettingsPath -Raw | ConvertFrom-Json
|
||||
$json = Get-Content $script:UdcSettingsPath -Raw -ErrorAction Stop | ConvertFrom-Json
|
||||
$json.GeneralSettings.MachineNumber = $NewNumber
|
||||
$json | ConvertTo-Json -Depth 99 | Set-Content -Path $script:UdcSettingsPath -Encoding UTF8
|
||||
$json | ConvertTo-Json -Depth 99 | Set-Content -Path $script:UdcSettingsPath -Encoding UTF8 -ErrorAction Stop
|
||||
$out.UdcUpdated = $true
|
||||
} catch {
|
||||
$out.Errors += "UDC update failed: $_"
|
||||
@@ -256,9 +261,15 @@ function Update-MachineNumber {
|
||||
}
|
||||
|
||||
# --- Update eDNC registry ---
|
||||
# Same -ErrorAction Stop reasoning as above. Set-ItemProperty's
|
||||
# PermissionDenied is non-terminating by default; without -ErrorAction
|
||||
# Stop, the catch block never fires and $out.EdncUpdated=$true gets set
|
||||
# despite the write failing. This is the bug that made the 13:35:39
|
||||
# tech run on FGY07FZ3 report "eDNC updated to 3005 / All updates
|
||||
# succeeded" while the actual reg value stayed at 9999.
|
||||
if (Test-Path $script:EdncRegPath) {
|
||||
try {
|
||||
Set-ItemProperty -Path $script:EdncRegPath -Name MachineNo -Value $NewNumber -Type String -Force
|
||||
Set-ItemProperty -Path $script:EdncRegPath -Name MachineNo -Value $NewNumber -Type String -Force -ErrorAction Stop
|
||||
$out.EdncUpdated = $true
|
||||
} catch {
|
||||
$out.Errors += "eDNC update failed: $_"
|
||||
|
||||
Reference in New Issue
Block a user