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.
201 lines
8.4 KiB
PowerShell
201 lines
8.4 KiB
PowerShell
# 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
|