Shell defaults + eDNC reg restore from machine-number backups

- 03-ShellDefaults.ps1: Default-User TaskbarAl=0 (left), HKLM policies to
  hide Start Recommended section, kill Bing web search + suggestions,
  disable Cortana. LTSC-honoured; runs fleet-wide via baseline loop.

- ntlars-backups/: 147 per-machine eDNC registry backups renamed to
  flat <MachineNumber>.reg scheme. Historical off-by-one entries from
  the original dump rewritten to match CSV-target MachineNo.

- Standard/03-RestoreEDncConfig.ps1: at imaging time, if tech typed a
  real machine number at PXE (not 9999), import <num>.reg from the local
  staged copy. Restores eFocas IP, PPDCS serial, Hssb relays -- not just
  the bare MachineNo. Skipped on Timeclock / 9999 / missing backup.

- Update-MachineNumber.ps1: when tech later sets a real number from 9999,
  pull <num>.reg from tsgwp00525 SFLD share (ntlarsBackupSharePath in
  site-config) and reg-import it before writing the new MachineNo.

- Restore-EDncReg.ps1: shared helper (Mount-SFLDShare + Import-EDncRegBackup)
  used by both callers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-04-15 15:42:21 -04:00
parent 67845372b2
commit 6db170bf54
152 changed files with 313 additions and 1 deletions

View File

@@ -0,0 +1,101 @@
# 03-ShellDefaults.ps1 - Shell appearance defaults for all new user profiles.
#
# Applies the following tweaks so shopfloor PCs look/behave closer to the
# Win10 baseline and don't reach out to Bing from the Start menu:
# 1. Taskbar aligned to the left (TaskbarAl=0). Per-user setting, written
# into the Default User hive so every NEW profile inherits it.
# 2. Start menu "Recommended" section hidden (HideRecommendedSection=1).
# 3. Web results + Bing search-box suggestions killed in Start/Search.
# 4. Cortana disabled via policy (no-op on LTSC 2024 since Cortana isn't
# shipped, but set anyway for belt-and-suspenders).
#
# 2-4 are HKLM policy values - W11 LTSC / Enterprise honour them; Home/Pro
# do not. Fine here since the fleet is LTSC 2024.
#
# Existing profiles (SupportUser, pre-existing end-user accounts) are NOT
# modified for TaskbarAl - the Default User hive is a template copied on
# profile creation, not a live cascade. The HKLM policies affect everyone.
$ErrorActionPreference = 'Continue'
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Host "ERROR: 03-ShellDefaults.ps1 must run as Administrator." -ForegroundColor Red
exit 1
}
# ---- 1. HKLM policy DWORDs (all LTSC-honoured) -------------------------------
# Each entry: KeyPath, ValueName, ValueData. Key is created if missing.
$hklmPolicies = @(
# Hide Start menu "Recommended" section.
@{ Key = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer'
Name = 'HideRecommendedSection'; Value = 1 }
# Kill web results in Start/taskbar search.
@{ Key = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search'
Name = 'DisableWebSearch'; Value = 1 }
@{ Key = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search'
Name = 'ConnectedSearchUseWeb'; Value = 0 }
# Disable Cortana (policy; LTSC doesn't ship Cortana but this is harmless).
@{ Key = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search'
Name = 'AllowCortana'; Value = 0 }
# Kill Bing-powered search-box suggestions (per-keystroke telemetry).
@{ Key = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer'
Name = 'DisableSearchBoxSuggestions'; Value = 1 }
)
foreach ($p in $hklmPolicies) {
try {
if (-not (Test-Path $p.Key)) {
New-Item -Path $p.Key -Force | Out-Null
}
Set-ItemProperty -Path $p.Key -Name $p.Name -Value $p.Value -Type DWord -Force
Write-Host ("Set {0}\{1} = {2}" -f $p.Key, $p.Name, $p.Value)
} catch {
Write-Warning ("Failed to set {0}\{1}: {2}" -f $p.Key, $p.Name, $_)
}
}
# ---- 2. Taskbar left-align via Default User hive ----
$defaultHive = 'C:\Users\Default\NTUSER.DAT'
$mountPoint = 'HKU\SFLDDefault'
$regPath = 'Registry::HKEY_USERS\SFLDDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced'
if (-not (Test-Path -LiteralPath $defaultHive)) {
Write-Warning "Default User hive not found at $defaultHive - skipping taskbar tweak."
exit 0
}
# Unload a stale mount from a previous run, if any. reg unload returns
# non-zero when nothing is mounted; ignore that.
& reg.exe unload $mountPoint *> $null
$loadOutput = & reg.exe load $mountPoint $defaultHive 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Warning "reg load failed ($LASTEXITCODE): $loadOutput"
exit 1
}
try {
if (-not (Test-Path $regPath)) {
New-Item -Path $regPath -Force | Out-Null
}
Set-ItemProperty -Path $regPath -Name 'TaskbarAl' -Value 0 -Type DWord -Force
Write-Host "Set Default User TaskbarAl=0 (left-aligned)"
} catch {
Write-Warning "Failed to set TaskbarAl: $_"
} finally {
# Force GC so PowerShell drops its handles on the hive before unload,
# otherwise reg unload fails with "The process cannot access the file".
[gc]::Collect()
[gc]::WaitForPendingFinalizers()
[gc]::Collect()
& reg.exe unload $mountPoint *> $null
if ($LASTEXITCODE -ne 0) {
Write-Warning "reg unload returned $LASTEXITCODE - Default User hive may still be mounted"
}
}
exit 0

View File

@@ -0,0 +1,88 @@
# Restore-EDncReg.ps1 - Import a per-machine eDNC .reg backup from a folder.
#
# Two callers:
# 1. Standard/03-RestoreEDncConfig.ps1 (imaging-time, source = local PXE copy)
# 2. Shopfloor/lib/Update-MachineNumber.ps1 (post-image 9999 -> real, source
# = tsgwp00525 SFLD share mounted via existing SFLD credential pattern)
#
# The .reg files live as <num>-<host>.reg or <num>.reg. Returns the path
# that was imported, or $null if nothing was found/imported. Errors are
# caught and logged via Write-Host (no throwing); callers continue.
#
# Dot-source with:
# . "$PSScriptRoot\lib\Restore-EDncReg.ps1" (from Shopfloor\)
# . "$PSScriptRoot\..\Shopfloor\lib\Restore-EDncReg.ps1" (from Standard\)
function Mount-SFLDShare {
<#
.SYNOPSIS
Mounts a \\server\share path using SFLD creds from
HKLM:\SOFTWARE\GE\SFLD\Credentials\*. Returns $true on success,
$false if creds are missing or mount fails.
#>
param(
[Parameter(Mandatory)][string]$SharePath,
[string]$DriveLetter = 'V:'
)
$server = ($SharePath -replace '^\\\\', '') -split '\\' | Select-Object -First 1
$basePath = 'HKLM:\SOFTWARE\GE\SFLD\Credentials'
if (-not (Test-Path $basePath)) { return $false }
$cred = $null
foreach ($entry in Get-ChildItem -Path $basePath -ErrorAction SilentlyContinue) {
$props = Get-ItemProperty -Path $entry.PSPath -ErrorAction SilentlyContinue
if (-not $props -or -not $props.TargetHost) { continue }
if ($props.TargetHost -eq $server -or
$props.TargetHost -like "$server.*" -or
$server -like "$($props.TargetHost).*") {
$cred = $props; break
}
}
if (-not $cred -or -not $cred.Username -or -not $cred.Password) { return $false }
& net use $DriveLetter /delete /y 2>$null | Out-Null
$null = & net use $DriveLetter $SharePath /user:$($cred.Username) $($cred.Password) /persistent:no 2>&1
return ($LASTEXITCODE -eq 0)
}
function Import-EDncRegBackup {
<#
.SYNOPSIS
Looks for <MachineNumber>-*.reg or <MachineNumber>.reg in SourceRoot
and runs `reg import` on it. Caller is responsible for mounting any
network share first; this function works on whatever local or drive-
letter path is handed in.
.PARAMETER SourceRoot
Directory containing the .reg backups.
.PARAMETER MachineNumber
The target machine number (digits). Used as a filename prefix.
.OUTPUTS
Path of the imported file, or $null if none found / import failed.
#>
param(
[Parameter(Mandatory)][string]$SourceRoot,
[Parameter(Mandatory)][string]$MachineNumber
)
if (-not (Test-Path -LiteralPath $SourceRoot)) {
Write-Host " Restore-EDncReg: source root not found: $SourceRoot"
return $null
}
$candidate = Get-ChildItem -Path $SourceRoot -Filter "$MachineNumber.reg" -File -ErrorAction SilentlyContinue |
Select-Object -First 1
if (-not $candidate) {
Write-Host " Restore-EDncReg: no backup for machine $MachineNumber in $SourceRoot"
return $null
}
Write-Host " Restore-EDncReg: importing $($candidate.FullName)"
$out = & reg.exe import "$($candidate.FullName)" 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Host " Restore-EDncReg: reg import failed (exit $LASTEXITCODE): $out"
return $null
}
Write-Host " Restore-EDncReg: imported OK"
return $candidate.FullName
}

View File

@@ -60,7 +60,43 @@ function Update-MachineNumber {
[string]$Site = 'West Jefferson' [string]$Site = 'West Jefferson'
) )
$out = @{ UdcUpdated = $false; EdncUpdated = $false; Errors = @() } $out = @{ UdcUpdated = $false; EdncUpdated = $false; Errors = @(); RegImported = $null }
# --- If UDC or eDNC is still at placeholder 9999, try to pull the
# per-machine .reg backup from the SFLD share and restore all
# the eFocas/PPDCS/Hssb config. The tech-typed $NewNumber is still
# written last (below), so the restore never clobbers it. ---
$current = Get-CurrentMachineNumber
$isPlaceholder = (($current.Udc -in @('9999', $null, '')) -or ($current.Ednc -in @('9999', $null, '')))
if ($isPlaceholder -and $NewNumber -ne '9999') {
$sharePath = $null
$siteCfgPath = 'C:\Enrollment\site-config.json'
if (Test-Path $siteCfgPath) {
try {
$cfg = Get-Content $siteCfgPath -Raw | ConvertFrom-Json
$sharePath = $cfg.pcProfiles.'Standard-Machine'.ntlarsBackupSharePath
} catch {}
}
if ($sharePath) {
try {
. (Join-Path $PSScriptRoot 'Restore-EDncReg.ps1')
$mounted = Mount-SFLDShare -SharePath $sharePath -DriveLetter 'V:'
if ($mounted) {
try {
$out.RegImported = Import-EDncRegBackup -SourceRoot 'V:\' -MachineNumber $NewNumber
} finally {
& net use V: /delete /y 2>$null | Out-Null
}
} else {
Write-Host " Update-MachineNumber: SFLD share unreachable - skipping restore."
}
} catch {
$out.Errors += "ntlars restore failed: $_"
}
}
}
# --- Stop UDC before editing its JSON (avoid stale shutdown write) --- # --- Stop UDC before editing its JSON (avoid stale shutdown write) ---
Get-Process UDC -ErrorAction SilentlyContinue | ForEach-Object { Get-Process UDC -ErrorAction SilentlyContinue | ForEach-Object {

View File

@@ -0,0 +1,86 @@
# 03-RestoreEDncConfig.ps1 - Restore per-machine eDNC config from .reg backup.
#
# Runs at shopfloor-setup time AFTER 01-eDNC.ps1 has installed eDNC. If the
# tech typed a real machine number at the PXE menu (not left blank -> 9999),
# look for a .reg backup matching that number on the PXE-local copy of the
# setup tree and import it. That restores everything eDNC-side the backup
# captured: eFocas IP/port, PPDCS serial (baud/parity/bits), Hssb KRelay1,
# etc. - instead of the reimaged PC coming up on factory defaults.
#
# After reg import, the tech-typed machine number is written to HKLM and to
# UDC's settings JSON to guarantee the current number wins over whatever the
# backup happened to contain (off-by-one backups exist in the historical
# dump; see ntlars-fixed/ work on the PXE host).
#
# Skipped when:
# - pc-subtype != Machine (Timeclock PCs do not use a machine number)
# - machine-number.txt missing / empty / 9999 (tech declined to set one)
# - ntlars-backups/ folder missing from the staged setup tree
# - no matching .reg file on disk for this machine number
$ErrorActionPreference = 'Continue'
$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 '03-RestoreEDncConfig.log') -Append -Force | Out-Null } catch {}
Write-Host "=== Restore eDNC config from backup ==="
# ---- Sub-type gate ----
$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 "Standard-Timeclock - skipping."
try { Stop-Transcript | Out-Null } catch {}
return
}
}
# ---- Read machine number captured at PXE time ----
$mnFile = 'C:\Enrollment\machine-number.txt'
if (-not (Test-Path $mnFile)) {
Write-Host "machine-number.txt not present - skipping (tech did not set one)."
try { Stop-Transcript | Out-Null } catch {}
return
}
$machineNum = (Get-Content $mnFile -First 1 -ErrorAction SilentlyContinue).Trim()
if (-not $machineNum -or $machineNum -eq '9999') {
Write-Host "Machine number is '$machineNum' (placeholder or empty) - skipping."
try { Stop-Transcript | Out-Null } catch {}
return
}
Write-Host "Machine number: $machineNum"
# ---- Locate local backup root (staged from PXE during imaging) ----
$backupRoot = 'C:\Enrollment\shopfloor-setup\Standard\ntlars-backups'
if (-not (Test-Path $backupRoot)) {
Write-Host "ntlars-backups folder not staged at $backupRoot - skipping."
try { Stop-Transcript | Out-Null } catch {}
return
}
. "$PSScriptRoot\..\Shopfloor\lib\Restore-EDncReg.ps1"
$imported = Import-EDncRegBackup -SourceRoot $backupRoot -MachineNumber $machineNum
if (-not $imported) {
Write-Host "No backup imported - leaving eDNC at installer defaults."
try { Stop-Transcript | Out-Null } catch {}
return
}
# ---- Tech-typed number wins: overwrite MachineNo in both eDNC and UDC. ----
# The imported .reg probably already has the right number (we rewrote the
# historical dump) but off-by-one backups exist, and this is cheap insurance.
. "$PSScriptRoot\..\Shopfloor\lib\Update-MachineNumber.ps1"
$current = Get-CurrentMachineNumber
Write-Host "Post-import state: UDC='$($current.Udc)' eDNC='$($current.Ednc)'"
$result = Update-MachineNumber -NewNumber $machineNum
if ($result.UdcUpdated) { Write-Host " UDC MachineNumber set to $machineNum" }
if ($result.EdncUpdated) { Write-Host " eDNC MachineNo set to $machineNum" }
foreach ($err in $result.Errors) { Write-Warning " $err" }
try { Stop-Transcript | Out-Null } catch {}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More