Shopfloor: Configure-PC tool, machine-number logon prompt, execution order fixes
New tools:
Configure-PC.bat/.ps1 - Interactive desktop tool for SupportUser to
configure a shopfloor PC after imaging. Two sections:
1. Machine number: if UDC/eDNC are still at placeholder 9999, prompt
to set the real number right now (updates UDC JSON + eDNC registry,
restarts UDC.exe with new args).
2. Auto-startup toggle: pick which apps start at user logon from a
numbered list (UDC, eDNC, Defect Tracker, WJ Shopfloor, Plant Apps).
Creates/removes .lnk files in AllUsers Startup folder. Toggle UI
shows [ON]/[ ] state, safe to re-run anytime. Plant Apps URL
resolved from .url file at runtime with hardcoded fallback to
https://mes-wjefferson.apps.lr.geaerospace.net/run/...
3. Item 6 in the toggle list: register/unregister a "Check Machine
Number" logon task for standard (non-admin) users. When enabled,
the task fires at every logon, checks for 9999, pops an InputBox
if found, updates both apps, then unregisters itself on success.
Check-MachineNumber.ps1 - The logon task script. Runs as the logged-in
user (needs GUI for InputBox), not SYSTEM. Writing to ProgramData + HKLM
is possible because 02-MachineNumberACLs.ps1 pre-grants BUILTIN\Users
write access on the two specific targets during imaging.
02-MachineNumberACLs.ps1 - Standard type-specific script (runs after
01-eDNC.ps1). Opens C:\ProgramData\UDC\udc_settings.json for Users:Modify
and HKLM:\...\GE Aircraft Engines\DNC\General for Users:SetValue. Narrow
scope, not blanket admin.
Execution order fixes in Run-ShopfloorSetup.ps1:
The dispatcher now has two lists: $skipInBaseline (scripts NOT run in the
alphabetical baseline loop) and $runAfterTypeSpecific (scripts run
explicitly after type-specific scripts complete). This fixes the bug where
06/07 ran before 01-eDNC.ps1 installed DnC, so eDNC/NTLARS shortcuts were
silently skipped.
New execution order:
Baseline: 00-PreInstall, 04-NetworkAndWinRM (skipping 05-08 + tools)
Type-specific: 01-eDNC, 02-MachineNumberACLs
Finalization: 06-OrganizeDesktop, 07-TaskbarLayout
06 internally calls 05 (Office shortcuts, Phase 0) and 08 (Edge config,
Phase 4) as sub-phases, so they also benefit from running late. Office
isn't installed until after the first reboot (ppkg streams C2R), so 05
no-ops at imaging time but succeeds when 06's SYSTEM logon task re-runs
it on the second boot. 08 resolves startup-tab URLs from .url files
delivered by DSC (even later); same self-heal via the logon task.
Other fixes in this commit:
- OpenText Setup-OpenText.ps1 Step 4: exclude WJ_Office.lnk, IBM_qks.lnk,
mmcs.lnk desktop shortcuts (matching the Step 3 .hep profile exclusion
from the previous commit). Removes stale copies from prior installs.
- 05-OfficeShortcuts.ps1: widened Office detection to 6 path variants
covering C2R + MSI + Office15/16, with diagnostic output on miss.
- 06-OrganizeDesktop.ps1: removed Phase 3 (desktop-root pin copies for
eDNC/NTLARS) so shortcuts live in Shopfloor Tools only, not duplicated
at root. Emptied $keepAtRoot. Added Phase 0 (call 05) and Phase 4
(call 08). Lazy folder creation + empty-folder cleanup. Scheduled task
now runs as SYSTEM (was BUILTIN\Users with Limited which failed the
admin check). Added NTLARS to 07's taskbar pin list.
- 08-EdgeDefaultBrowser.ps1: Plant Apps URL fallback hardcoded from
device-config.yaml.
- All new scripts have Start-Transcript logging to C:\Logs\SFLD\ with
timestamps and running-as identity.
- Run-ShopfloorSetup.ps1: Start-Transcript + Stop-Transcript wrapping
entire dispatcher run, writes to C:\Logs\SFLD\shopfloor-setup.log.
Configure-PC.bat added to SupportUser desktop copy list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -49,14 +49,13 @@ if (-not $isAdmin) {
|
||||
$publicDesktop = 'C:\Users\Public\Desktop'
|
||||
$shopfloorToolsDir = Join-Path $publicDesktop 'Shopfloor Tools'
|
||||
$scriptPath = $MyInvocation.MyCommand.Path
|
||||
$scriptDir = Split-Path -Parent $scriptPath
|
||||
|
||||
# Filenames that always stay at the desktop root regardless of classification.
|
||||
# End users click these many times a day and an extra folder click is real
|
||||
# friction. Phase 2 also drops these here as a post-sweep safety net.
|
||||
$keepAtRoot = @(
|
||||
'eDNC.lnk',
|
||||
'NTLARS.lnk'
|
||||
)
|
||||
# Empty right now - every shortcut lives inside a category folder for a
|
||||
# clean end-user desktop. Add entries here if a future shortcut needs to
|
||||
# stay pinned at root.
|
||||
$keepAtRoot = @()
|
||||
|
||||
# ============================================================================
|
||||
# Phase 1: Sweep loose shortcuts at desktop root into category folders
|
||||
@@ -109,17 +108,11 @@ function Invoke-DesktopSweep {
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure category folders exist
|
||||
foreach ($cat in $categories.Keys) {
|
||||
$dir = Join-Path $DesktopPath $cat
|
||||
if (-not (Test-Path -LiteralPath $dir)) {
|
||||
try {
|
||||
New-Item -ItemType Directory -Path $dir -Force -ErrorAction Stop | Out-Null
|
||||
} catch {
|
||||
Write-Warning "Failed to create category folder '$cat' : $_"
|
||||
}
|
||||
}
|
||||
}
|
||||
# Category folders are created LAZILY - only when we're about to move
|
||||
# something into them - so a Display / Wax-Trace PC without Office
|
||||
# doesn't get an empty Office\ folder littering the desktop. See
|
||||
# Remove-EmptyCategoryFolders at the end of the script for the
|
||||
# post-sweep cleanup pass that finishes the job.
|
||||
|
||||
# WScript.Shell for resolving .lnk targets
|
||||
$shell = $null
|
||||
@@ -187,7 +180,15 @@ function Invoke-DesktopSweep {
|
||||
}
|
||||
|
||||
if ($category) {
|
||||
$destDir = Join-Path $DesktopPath $category
|
||||
$destDir = Join-Path $DesktopPath $category
|
||||
if (-not (Test-Path -LiteralPath $destDir)) {
|
||||
try {
|
||||
New-Item -ItemType Directory -Path $destDir -Force -ErrorAction Stop | Out-Null
|
||||
} catch {
|
||||
Write-Warning "Failed to create category folder '$category' : $_"
|
||||
continue
|
||||
}
|
||||
}
|
||||
$destPath = Join-Path $destDir $item.Name
|
||||
try {
|
||||
Move-Item -LiteralPath $item.FullName -Destination $destPath -Force -ErrorAction Stop
|
||||
@@ -269,17 +270,10 @@ function Find-ExistingLnk {
|
||||
}
|
||||
|
||||
function Add-ShopfloorToolsApps {
|
||||
if (-not (Test-Path -LiteralPath $shopfloorToolsDir)) {
|
||||
try {
|
||||
New-Item -ItemType Directory -Path $shopfloorToolsDir -Force -ErrorAction Stop | Out-Null
|
||||
} catch {
|
||||
Write-Warning "Failed to create $shopfloorToolsDir : $_"
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# App registry - each entry describes how to materialize one shortcut
|
||||
# into Shopfloor Tools\.
|
||||
# into Shopfloor Tools\. Folder creation is deferred until we know we
|
||||
# have at least one app to put in it (lazy creation, so PC types with
|
||||
# nothing installed don't get an empty Shopfloor Tools folder).
|
||||
#
|
||||
# Kind = 'exe' -> build a fresh .lnk from ExePath
|
||||
# Kind = 'existing' -> copy an existing .lnk via Find-ExistingLnk
|
||||
@@ -291,12 +285,29 @@ function Add-ShopfloorToolsApps {
|
||||
@{ Name = 'Defect_Tracker'; Kind = 'existing'; SourceName = 'Defect_Tracker.lnk' }
|
||||
)
|
||||
|
||||
# Lazy folder creation - only create Shopfloor Tools\ the first time
|
||||
# we have an app that's actually going to land in it. PC types with
|
||||
# nothing installed get no empty folder.
|
||||
$ensureToolsDir = {
|
||||
if (-not (Test-Path -LiteralPath $shopfloorToolsDir)) {
|
||||
try {
|
||||
New-Item -ItemType Directory -Path $shopfloorToolsDir -Force -ErrorAction Stop | Out-Null
|
||||
return $true
|
||||
} catch {
|
||||
Write-Warning "Failed to create $shopfloorToolsDir : $_"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
foreach ($app in $apps) {
|
||||
$dest = Join-Path $shopfloorToolsDir "$($app.Name).lnk"
|
||||
|
||||
switch ($app.Kind) {
|
||||
'exe' {
|
||||
if (Test-Path -LiteralPath $app.ExePath) {
|
||||
if (-not (& $ensureToolsDir)) { break }
|
||||
if (New-ShopfloorLnk -Path $dest -Target $app.ExePath) {
|
||||
Write-Host " created: $($app.Name) -> $dest"
|
||||
}
|
||||
@@ -308,6 +319,7 @@ function Add-ShopfloorToolsApps {
|
||||
'existing' {
|
||||
$src = Find-ExistingLnk $app.SourceName
|
||||
if ($src) {
|
||||
if (-not (& $ensureToolsDir)) { break }
|
||||
try {
|
||||
Copy-Item -LiteralPath $src -Destination $dest -Force -ErrorAction Stop
|
||||
Write-Host " copied: $($app.Name) from $src"
|
||||
@@ -323,30 +335,56 @@ function Add-ShopfloorToolsApps {
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Phase 3: Pin eDNC and NTLARS shortcuts at the desktop root
|
||||
# Phase 3: Remove empty category folders
|
||||
#
|
||||
# These are the two shortcuts end users touch most frequently. Allowlisted
|
||||
# in the sweep, and also force-dropped here (copied from the Shopfloor
|
||||
# Tools\ versions we just created) so a sweep-and-forget user still ends
|
||||
# up with them loose at the root.
|
||||
# If the sweep classified nothing into a given category (e.g. no Office
|
||||
# on a Display PC, no Shopfloor Tools apps on a kiosk), we don't want an
|
||||
# empty folder cluttering the desktop. The scheduled-task sweep also runs
|
||||
# this so categories that go empty between logons self-heal.
|
||||
# ============================================================================
|
||||
function Update-DesktopRootPins {
|
||||
foreach ($name in @('eDNC.lnk', 'NTLARS.lnk')) {
|
||||
$src = Join-Path $shopfloorToolsDir $name
|
||||
$dst = Join-Path $publicDesktop $name
|
||||
if (Test-Path -LiteralPath $src) {
|
||||
function Remove-EmptyCategoryFolders {
|
||||
foreach ($cat in @('Office', 'Shopfloor Tools', 'Web Links')) {
|
||||
$dir = Join-Path $publicDesktop $cat
|
||||
if (-not (Test-Path -LiteralPath $dir)) { continue }
|
||||
$contents = @(Get-ChildItem -LiteralPath $dir -Force -ErrorAction SilentlyContinue)
|
||||
if ($contents.Count -eq 0) {
|
||||
try {
|
||||
Copy-Item -LiteralPath $src -Destination $dst -Force -ErrorAction Stop
|
||||
Write-Host " root: $name"
|
||||
Remove-Item -LiteralPath $dir -Force -ErrorAction Stop
|
||||
Write-Host " removed empty: $cat\"
|
||||
} catch {
|
||||
Write-Warning "Failed to drop $dst : $_"
|
||||
Write-Warning "Failed to remove empty $dir : $_"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Scheduled task registration (phase 1 re-run at every logon)
|
||||
# Phase 4: Edge default browser + startup tabs (delegated to 08)
|
||||
#
|
||||
# 08-EdgeDefaultBrowser.ps1 resolves startup-tab URLs from .url files on
|
||||
# the Public Desktop (or in Web Links\ after the sweep). The .url files
|
||||
# are delivered by DSC AFTER this initial shopfloor setup, so the first
|
||||
# run at imaging time won't find them (falls back to hardcoded URLs,
|
||||
# Plant Apps gets skipped). By calling 08 from inside 06, every SYSTEM
|
||||
# scheduled-task logon re-run of 06 also re-runs 08 — so after DSC drops
|
||||
# the .url files and the next sweep files them into Web Links\, 08
|
||||
# picks them up and updates the Edge policy. Self-healing.
|
||||
# ============================================================================
|
||||
function Invoke-EdgeDefaultBrowser {
|
||||
$edgeScript = Join-Path $scriptDir '08-EdgeDefaultBrowser.ps1'
|
||||
if (-not (Test-Path -LiteralPath $edgeScript)) {
|
||||
Write-Host " 08-EdgeDefaultBrowser.ps1 not found - skipping"
|
||||
return
|
||||
}
|
||||
try {
|
||||
& $edgeScript
|
||||
} catch {
|
||||
Write-Warning "08-EdgeDefaultBrowser.ps1 failed: $_"
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Scheduled task registration (re-runs 06 at every logon as SYSTEM)
|
||||
# ============================================================================
|
||||
function Register-SweepScheduledTask {
|
||||
param([string]$ScriptPath)
|
||||
@@ -365,12 +403,16 @@ function Register-SweepScheduledTask {
|
||||
|
||||
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
||||
|
||||
# Run as whichever user logs in, at Limited (standard) rights. Public
|
||||
# desktop is writable by BUILTIN\Users so no elevation needed. Using
|
||||
# the well-known Users group SID so this works on non-English Windows.
|
||||
# Run as SYSTEM so the script can (a) pass its admin check at the
|
||||
# top, (b) write to Public Desktop / ProgramData Start Menu / the
|
||||
# Default User profile without Access Denied, and (c) register the
|
||||
# next iteration of itself idempotently. Earlier versions used
|
||||
# -GroupId 'BUILTIN\Users' -RunLevel Limited which fired at logon
|
||||
# but the script exited immediately on the non-admin check.
|
||||
$principal = New-ScheduledTaskPrincipal `
|
||||
-GroupId 'S-1-5-32-545' `
|
||||
-RunLevel Limited
|
||||
-UserId 'NT AUTHORITY\SYSTEM' `
|
||||
-LogonType ServiceAccount `
|
||||
-RunLevel Highest
|
||||
|
||||
$settings = New-ScheduledTaskSettingsSet `
|
||||
-AllowStartIfOnBatteries `
|
||||
@@ -399,6 +441,20 @@ function Register-SweepScheduledTask {
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
Write-Host ""
|
||||
Write-Host "=== Phase 0: create Office shortcuts (if Office is installed) ==="
|
||||
# Delegated to 05-OfficeShortcuts.ps1. Office is installed via ppkg but
|
||||
# doesn't finish streaming until AFTER the first reboot, so the first
|
||||
# imaging-time run of 06 finds no Office and this no-ops. On the second
|
||||
# logon (after reboot), Office is installed and 05 creates the shortcuts
|
||||
# so Phase 1 below can sweep them into the Office\ folder.
|
||||
$officeScript = Join-Path $scriptDir '05-OfficeShortcuts.ps1'
|
||||
if (Test-Path -LiteralPath $officeScript) {
|
||||
try { & $officeScript } catch { Write-Warning "05-OfficeShortcuts.ps1 failed: $_" }
|
||||
} else {
|
||||
Write-Host " 05-OfficeShortcuts.ps1 not found - skipping"
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== Phase 1: sweep loose shortcuts into category folders ==="
|
||||
Invoke-DesktopSweep -DesktopPath $publicDesktop
|
||||
@@ -408,11 +464,15 @@ Write-Host "=== Phase 2: populate Shopfloor Tools with app shortcuts ==="
|
||||
Add-ShopfloorToolsApps
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== Phase 3: drop eDNC / NTLARS at desktop root ==="
|
||||
Update-DesktopRootPins
|
||||
Write-Host "=== Phase 3: remove empty category folders ==="
|
||||
Remove-EmptyCategoryFolders
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== Phase 4: register logon sweeper scheduled task ==="
|
||||
Write-Host "=== Phase 4: Edge default browser + startup tabs ==="
|
||||
Invoke-EdgeDefaultBrowser
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== Phase 5: register logon sweeper scheduled task ==="
|
||||
Register-SweepScheduledTask -ScriptPath $scriptPath
|
||||
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user