Fix ShopFloor autologon persistence, S: drive mapping, sync throttle

AutoLogonCount depletion:
  Run-ShopfloorSetup set AutoLogonCount=4 for SupportUser. Windows
  decrements per-logon; at 0 it clears AutoAdminLogon + DefaultPassword,
  nuking the lockdown-configured ShopFloor autologon. Fix: delete
  AutoLogonCount in Invoke-SetupComplete before the lockdown reboot.
  ShopFloor's Autologon.exe-set config persists indefinitely.

Sync_intune window on ShopFloor:
  The marker-check path used 'exit 0' but the task runs with -NoExit,
  leaving a dangling PowerShell window on every ShopFloor logon. Fix:
  [Environment]::Exit(0) kills the host outright, defeating -NoExit.

S: drive mapping:
  Vendor ConsumeCredentials.ps1 calls New-StoredCredential -Persist
  LocalMachine (needs admin) before net use. ShopFloor is non-admin so
  cred-store fails silently and net use has no auth. Fix: new
  Map-SfldShare.ps1 reads HKLM creds and passes them inline to
  net use /user: -- no Credential Manager needed, works as Limited.
  Register-MapSfldShare updated to stage + reference our script.

Wired NIC re-enable:
  SYSTEM task polls for SFLD creds (Phase 5), re-enables wired NICs,
  self-deletes. Replaces the broken Enable-NetAdapter in Monitor
  (Limited principal can't enable NICs). No-WiFi devices unaffected
  (migrate-to-wifi never disables, re-enable is a no-op).

Sync throttle:
  15 min retrigger when only waiting for lockdown (was 5 min for all
  phases). Avoids interrupting the Intune Remediation script.

Defect Tracker path:
  All references corrected to C:\Program Files (x86)\WJF_Defect_Tracker.

QR code retry:
  Build-QRCodeText retried every poll cycle until DeviceId appears
  (was single-shot that could miss the dsregcmd timing window).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-04-16 12:29:02 -04:00
parent f73f999938
commit 2ab6055125
8 changed files with 187 additions and 51 deletions

View File

@@ -0,0 +1,68 @@
# Map-SfldShare.ps1 - Map S: drive on user logon using SFLD creds from HKLM.
#
# Runs as the interactive user (BUILTIN\Users, Limited) so the drive
# mapping lands in the logged-in user's session. Reads username/password
# directly from HKLM:\SOFTWARE\GE\SFLD\Credentials\* and passes them
# inline to net use -- no Windows Credential Manager involvement.
#
# Why not the vendor's ConsumeCredentials.ps1: it calls
# New-StoredCredential -Persist LocalMachine which requires admin.
# ShopFloor is a non-admin user, so the cred-store step fails silently
# and the subsequent net use (which relies on those stored creds) has
# no authentication. Direct net use /user: bypasses the issue entirely.
$ErrorActionPreference = 'Continue'
$logDir = 'C:\Logs\SFLD'
if (-not (Test-Path $logDir)) {
try { New-Item -Path $logDir -ItemType Directory -Force | Out-Null } catch { $logDir = $env:TEMP }
}
$logFile = Join-Path $logDir 'map-share.log'
function Write-MapLog {
param([string]$Message)
$line = '[{0}] [{1}] {2}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $env:USERNAME, $Message
Add-Content -Path $logFile -Value $line -ErrorAction SilentlyContinue
}
Write-MapLog '=== Map-SfldShare start ==='
$credsBase = 'HKLM:\SOFTWARE\GE\SFLD\Credentials'
if (-not (Test-Path $credsBase)) {
Write-MapLog 'No HKLM SFLD credentials yet - exiting'
exit 0
}
foreach ($entry in (Get-ChildItem -Path $credsBase -ErrorAction SilentlyContinue)) {
$p = Get-ItemProperty -Path $entry.PSPath -ErrorAction SilentlyContinue
if (-not $p -or -not $p.TargetHost -or -not $p.Username -or -not $p.Password) { continue }
$drive = $null
$share = $null
try { $drive = $p.DriveLetter } catch {}
try { $share = $p.ShareName } catch {}
if ([string]::IsNullOrWhiteSpace($drive) -or [string]::IsNullOrWhiteSpace($share)) { continue }
$drive = $drive.TrimEnd(':') + ':'
$share = $share.TrimStart('\')
$uncPath = "\\$($p.TargetHost)\$share"
# Skip if already mapped to the right target
$existing = & net use $drive 2>&1
if ($LASTEXITCODE -eq 0 -and ($existing -join "`n") -match [regex]::Escape($uncPath)) {
Write-MapLog "$drive already mapped to $uncPath - skipping"
continue
}
& net use $drive /delete /y 2>$null | Out-Null
$out = & net use $drive $uncPath /user:$($p.Username) $($p.Password) /persistent:yes 2>&1
if ($LASTEXITCODE -eq 0) {
Write-MapLog "Mapped $drive -> $uncPath"
} else {
Write-MapLog "FAILED $drive -> $uncPath (exit $LASTEXITCODE): $out"
}
}
Write-MapLog '=== Map-SfldShare end ==='
exit 0

View File

@@ -103,14 +103,10 @@ Write-Host ""
# the imaging chain on a keypress. Tower (no WiFi) case is a no-op because
# Order 5's WiFi detection left the wired NIC enabled to begin with.
# ============================================================================
try {
Get-NetAdapter -Physical -ErrorAction SilentlyContinue |
Where-Object { $_.InterfaceDescription -notmatch 'Wi-?Fi|Wireless|WLAN|802\.11' } |
Enable-NetAdapter -Confirm:$false -ErrorAction SilentlyContinue
Write-Host "Wired NICs re-enabled (was migrate-to-wifi.ps1 had disabled them on laptops)."
} catch {
Write-Warning "Failed to re-enable wired NICs: $_"
}
# Wired NIC re-enable is handled by the 'GE Re-enable Wired NICs' SYSTEM
# task (registered by Run-ShopfloorSetup.ps1) which polls for SFLD creds
# and re-enables once they appear. This script (Limited principal) can't
# call Enable-NetAdapter itself.
Write-Host ""
# ============================================================================
@@ -740,10 +736,19 @@ function Invoke-SetupComplete {
try { & $ConfigureScript -MachineNumberOnly } catch { Write-Warning "Configure-PC failed: $_" }
}
# Delete AutoLogonCount so it can't deplete and nuke ShopFloor's
# autologon. Run-ShopfloorSetup set it to 4 for the SupportUser
# imaging chain; Windows decrements per-logon and at 0 clears
# AutoAdminLogon + DefaultPassword, breaking the lockdown-set
# ShopFloor autologon. Removing the value entirely leaves the
# lockdown's Autologon.exe-configured autologon intact forever.
& reg.exe delete 'HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' /v AutoLogonCount /f 2>$null | Out-Null
Write-Host "Cleared AutoLogonCount (ShopFloor autologon will persist)."
# Reboot so Winlogon's new DefaultUserName=ShopFloor kicks in -
# autologon only fires at the logon boundary. Next boot brings up
# a clean ShopFloor session; this task will fire again for that
# user, see the marker, and exit in <1s.
# user, see the marker, and exit in <1s (via [Environment]::Exit).
Write-Host ""
Write-Host "Rebooting in 10 seconds for ShopFloor autologon..." -ForegroundColor Yellow
& shutdown.exe /r /t 10
@@ -817,9 +822,11 @@ $syncCompleteMarker = 'C:\Enrollment\sync-complete.txt'
# exit immediately. The task stays registered (BUILTIN\Users can't delete
# tasks) but does nothing -- fires at logon, sees marker, exits in <1s.
if ($AsTask -and (Test-Path -LiteralPath $syncCompleteMarker)) {
Write-Host "Sync already complete (marker exists). Exiting."
try { Stop-Transcript | Out-Null } catch {}
exit 0
# [Environment]::Exit forcefully terminates the PowerShell host,
# defeating -NoExit so the ShopFloor desktop doesn't show a
# dangling PowerShell window on every logon.
[Environment]::Exit(0)
}
# PC types that don't receive DSC (no SAS token, no DSCInstall.log).
@@ -846,12 +853,12 @@ try {
while ($true) {
$snap = Get-Snapshot
# Refresh QR code once AAD join is detected (the initial build
# may have run before enrollment completed, showing "not yet
# Azure AD joined" even after Phase 1 passes).
if (-not $qrRefreshed -and $snap.Phase1.AzureAdJoined) {
# Retry QR code every cycle until it actually renders. dsregcmd
# may report AzureAdJoined=YES before DeviceId is populated, so
# a single-shot refresh misses the window.
if (-not $qrRefreshed) {
$qrText = Build-QRCodeText
$qrRefreshed = $true
$qrRefreshed = [bool]($qrText -notmatch 'not yet Azure AD joined')
}
Clear-Host
@@ -903,13 +910,27 @@ try {
}
}
# Re-trigger sync periodically
# Re-trigger sync periodically. Throttle to 15 min once we're
# only waiting on lockdown -- the 5 min cycle can interrupt the
# Intune Remediation script mid-execution and delay lockdown.
$waitingForLockdownOnly = $false
if (-not $skipDsc) {
$waitingForLockdownOnly = ($snap.DscInstallComplete -and
$snap.Phase5.CredsPopulated -and
-not $snap.LockdownComplete)
} else {
$waitingForLockdownOnly = ($snap.Phase1.AzureAdJoined -and
$snap.Phase1.PoliciesArriving -and
-not $snap.LockdownComplete)
}
$currentInterval = if ($waitingForLockdownOnly) { 15 } else { $RetriggerMinutes }
if ((Get-Date) -ge $nextRetrigger) {
Write-Host ""
Write-Host "Re-triggering Intune sync..." -ForegroundColor Cyan
Invoke-IntuneSync
$lastSync = Get-Date
$nextRetrigger = $lastSync.AddMinutes($RetriggerMinutes)
$nextRetrigger = $lastSync.AddMinutes($currentInterval)
}
Start-Sleep -Seconds $PollSecs