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:
101
playbook/shopfloor-setup/Shopfloor/03-ShellDefaults.ps1
Normal file
101
playbook/shopfloor-setup/Shopfloor/03-ShellDefaults.ps1
Normal 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
|
||||
88
playbook/shopfloor-setup/Shopfloor/lib/Restore-EDncReg.ps1
Normal file
88
playbook/shopfloor-setup/Shopfloor/lib/Restore-EDncReg.ps1
Normal 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
|
||||
}
|
||||
@@ -60,7 +60,43 @@ function Update-MachineNumber {
|
||||
[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) ---
|
||||
Get-Process UDC -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
|
||||
Reference in New Issue
Block a user