diff --git a/playbook/shopfloor-setup/Run-ShopfloorSetup.ps1 b/playbook/shopfloor-setup/Run-ShopfloorSetup.ps1 index cc6f063..855eb89 100644 --- a/playbook/shopfloor-setup/Run-ShopfloorSetup.ps1 +++ b/playbook/shopfloor-setup/Run-ShopfloorSetup.ps1 @@ -26,11 +26,11 @@ Write-Host " Transcript: $transcriptPath" Write-Host "================================================================" Write-Host "" -# Bump AutoLogonCount HIGH at the start so reboots during setup (e.g. VC++ 2008 -# triggering an immediate ExitWindowsEx) don't exhaust autologin attempts before -# the dispatcher can complete. The end-of-script reset puts it back to 2 once -# everything succeeds. -reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v AutoLogonCount /t REG_DWORD /d 99 /f | Out-Null +# AutoLogonCount is NOT set here. Previously we bumped it to 99/4, but +# Windows decrements it per-logon and at 0 clears AutoAdminLogon -- which +# nukes the lockdown-configured ShopFloor autologon later in the chain. +# The unattend XML's handles SupportUser logons; +# the lockdown's Autologon.exe handles ShopFloor. We stay out of it. # Cancel any pending reboot so it doesn't interrupt setup cmd /c "shutdown /a 2>nul" *>$null @@ -237,32 +237,28 @@ if (Test-Path -LiteralPath $monitorScript) { # Set auto-logon to expire after 4 more logins (2 needed for sync_intune # pre-reboot + post-reboot, plus 2 margin for unexpected reboots from # Windows Update, PPKG file operations, or script crashes). -reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v AutoLogonCount /t REG_DWORD /d 4 /f | Out-Null -Write-Host "Auto-logon set to 4 remaining logins." +# (AutoLogonCount intentionally not set -- see comment at top of script) # --- Register cross-PC-type enforcers (Acrobat, etc.) --- # These run on every logon regardless of PC type, mounting the SFLD share # for version-pinned app enforcement. Initial install already handled by # preinstall flow; enforcers only kick in when detection fails. -# --- Re-enable wired NICs once SFLD creds arrive (Phase 5) --- +# --- Re-enable wired NICs once lockdown completes (Phase 6) --- # migrate-to-wifi.ps1 disables wired NICs so the PPKG runs over WiFi. -# After Phase 5 (SFLD creds populated), WiFi duty is done and the tech -# needs wired back for production ethernet. Monitor-IntuneProgress runs -# as Limited and can't call Enable-NetAdapter (needs admin). This SYSTEM -# task fires at logon, waits for the SFLD cred marker, re-enables wired -# NICs, and self-deletes. If creds haven't landed yet, the task exits -# quickly and the repetition interval retries every 5 minutes. +# Keep them disabled through the entire Intune sync + DSC + lockdown +# chain so nothing interrupts the WiFi-based enrollment. Only re-enable +# after lockdown lands (Autologon_Remediation.log confirms ShopFloor +# autologon set). Monitor-IntuneProgress runs as Limited and can't call +# Enable-NetAdapter (needs admin). This SYSTEM task fires at logon, +# polls for lockdown completion, re-enables wired NICs, and self-deletes. $reEnableTask = 'GE Re-enable Wired NICs' try { $script = @' -$credsBase = 'HKLM:\SOFTWARE\GE\SFLD\Credentials' -if (-not (Test-Path $credsBase)) { exit 0 } -$hasCreds = $false -Get-ChildItem -Path $credsBase -ErrorAction SilentlyContinue | ForEach-Object { - $p = Get-ItemProperty -Path $_.PSPath -ErrorAction SilentlyContinue - if ($p -and $p.TargetHost -and $p.Username -and $p.Password) { $hasCreds = $true } -} -if (-not $hasCreds) { exit 0 } +$imeLogs = 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs' +$remLog = Join-Path $imeLogs 'Autologon_Remediation.log' +if (-not (Test-Path $remLog)) { exit 0 } +$content = Get-Content $remLog -Raw -ErrorAction SilentlyContinue +if ($content -notmatch 'Autologon set for ShopFloor') { exit 0 } Get-NetAdapter -Physical -ErrorAction SilentlyContinue | Where-Object { $_.InterfaceDescription -notmatch 'Wi-?Fi|Wireless|WLAN|802\.11' } | Enable-NetAdapter -Confirm:$false -ErrorAction SilentlyContinue diff --git a/playbook/shopfloor-setup/Shopfloor/Register-MapSfldShare.ps1 b/playbook/shopfloor-setup/Shopfloor/Register-MapSfldShare.ps1 index 9884840..3a94c2c 100644 --- a/playbook/shopfloor-setup/Shopfloor/Register-MapSfldShare.ps1 +++ b/playbook/shopfloor-setup/Shopfloor/Register-MapSfldShare.ps1 @@ -1,12 +1,18 @@ -# Register-MapSfldShare.ps1 - Stage Map-SfldShare.ps1 + register a logon -# task that maps S: for any user in BUILTIN\Users (SupportUser, ShopFloor, -# any future end-user accounts). +# Register-MapSfldShare.ps1 - Stage Map-SfldShare.ps1 + register an +# HKLM\Run entry that maps S: for any interactive user (SupportUser, +# ShopFloor, any future end-user accounts). +# +# Why HKLM\Run instead of a scheduled task: Run fires at Explorer +# startup in the logged-in user's interactive session with their full +# token + HKCU mounted. No principal/LogonType/group-SID plumbing, no +# "task fires in session 0 but drive not visible to Explorer" class of +# bugs. Works for every BUILTIN\Users member with no extra logic. # # Why not the vendor's ConsumeCredentials.ps1: it calls -# New-StoredCredential -Persist LocalMachine (needs admin) before net use. -# ShopFloor is non-admin, so the cred-store fails and net use has no auth. -# Our Map-SfldShare.ps1 reads HKLM creds directly and passes them inline -# to net use /user: -- no Credential Manager needed, works as Limited. +# New-StoredCredential -Persist LocalMachine (needs admin) before net +# use. ShopFloor is non-admin, so the cred-store fails and net use has +# no auth. Our Map-SfldShare.ps1 reads HKLM creds directly and passes +# them inline to net use /user: -- no Credential Manager needed. $ErrorActionPreference = 'Continue' @@ -14,6 +20,10 @@ $installRoot = 'C:\Program Files\GE\SfldShare' $mapScript = Join-Path $installRoot 'Map-SfldShare.ps1' $logDir = 'C:\Logs\SFLD' $logFile = Join-Path $logDir 'register-mapshare.log' +$runKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' +$runValue = 'GE Map SFLD Share' +$legacyTask = 'GE Shopfloor Map S: Drive' + if (-not (Test-Path $logDir)) { New-Item -Path $logDir -ItemType Directory -Force | Out-Null } function Write-RegLog { @@ -38,39 +48,30 @@ if (Test-Path $src) { exit 1 } +# Remove the legacy scheduled task if it exists (left behind by older +# imaging runs that used the scheduled-task approach). +if (Get-ScheduledTask -TaskName $legacyTask -ErrorAction SilentlyContinue) { + try { + Unregister-ScheduledTask -TaskName $legacyTask -Confirm:$false -ErrorAction Stop + Write-RegLog "Removed legacy scheduled task '$legacyTask'" + } catch { + Write-RegLog "Failed to remove legacy task '$legacyTask': $_" + } +} + +# Register HKLM\Run entry. Runs at Explorer startup for every +# interactive user in that user's session. try { - $action = New-ScheduledTaskAction ` - -Execute 'powershell.exe' ` - -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$mapScript`"" + $command = '"{0}" -NoProfile -ExecutionPolicy Bypass -File "{1}"' -f ` + "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe", $mapScript - $trigger = New-ScheduledTaskTrigger -AtLogOn - - # BUILTIN\Users + Limited: any logged-in user triggers it; action - # runs in that user's session so net use lands the drive in the - # right place. - $principal = New-ScheduledTaskPrincipal -GroupId 'S-1-5-32-545' -RunLevel Limited - - $settings = New-ScheduledTaskSettingsSet ` - -AllowStartIfOnBatteries ` - -DontStopIfGoingOnBatteries ` - -StartWhenAvailable ` - -ExecutionTimeLimit (New-TimeSpan -Minutes 5) - - Write-RegLog "Registering 'GE Shopfloor Map S: Drive' (logon trigger, BUILTIN\Users -> $vendorScript)" - - Register-ScheduledTask ` - -TaskName 'GE Shopfloor Map S: Drive' ` - -Action $action ` - -Trigger $trigger ` - -Principal $principal ` - -Settings $settings ` - -Force ` - -Description 'Map SFLD share drives on any user logon using HKLM creds (parallel to the principal-restricted vendor task) so ShopFloor and other end-user accounts get S: mapped' ` - -ErrorAction Stop | Out-Null - - Write-RegLog 'Scheduled task registered' + if (-not (Test-Path $runKey)) { + New-Item -Path $runKey -Force | Out-Null + } + New-ItemProperty -Path $runKey -Name $runValue -Value $command -PropertyType String -Force | Out-Null + Write-RegLog "Set $runKey\$runValue = $command" } catch { - Write-RegLog "FAILED to register task: $_" + Write-RegLog "FAILED to register Run key: $_" exit 1 } diff --git a/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 b/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 index 9c771f4..9797e8d 100644 --- a/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 +++ b/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 @@ -601,13 +601,19 @@ function Format-Snapshot { @{ Ok = $Snap.Phase3.InstallComplete; Failed = $false } ) $p4HasFailed = $false; $p4AllDone = $true; $p4AnyStarted = $false - if ($Snap.Phase4 -and $Snap.Phase4.Count -gt 0) { + $p4HasScripts = ($Snap.Phase4 -and $Snap.Phase4.Count -gt 0) + if ($p4HasScripts) { foreach ($s in $Snap.Phase4) { if ($s.Status -eq 'failed') { $p4HasFailed = $true } if ($s.Status -ne 'done') { $p4AllDone = $false } if ($s.Status -ne 'pending') { $p4AnyStarted = $true } } - } else { $p4AllDone = $false } + } else { + # No scripts discovered. If DSC install is already complete, + # there are simply no custom scripts for this image type -- + # that's COMPLETE, not WAITING. + $p4AllDone = $Snap.Phase3.InstallComplete + } $p4Status = if ($p4HasFailed) { 'FAILED' } elseif ($p4AllDone) { 'COMPLETE' } elseif ($p4AnyStarted) { 'IN PROGRESS' } else { 'WAITING' } $p5Done = ($Snap.Phase5.ConsumeCredsTask -and $Snap.Phase5.CredsPopulated) @@ -736,15 +742,6 @@ 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 @@ -925,6 +922,12 @@ try { } $currentInterval = if ($waitingForLockdownOnly) { 15 } else { $RetriggerMinutes } + # If we just entered lockdown-wait and the existing countdown is + # shorter than 15 min, push it out immediately so we don't fire + # a stale 5-min retrigger mid-Remediation. + $minNext = $lastSync.AddMinutes($currentInterval) + if ($minNext -gt $nextRetrigger) { $nextRetrigger = $minNext } + if ((Get-Date) -ge $nextRetrigger) { Write-Host "" Write-Host "Re-triggering Intune sync..." -ForegroundColor Cyan