- Register-MapSfldShare.ps1: swap scheduled task for HKLM\Run entry. Task with -GroupId runs in session 0 with no HKCU, so /persistent:yes fails and the drive mapping isn't visible to Explorer. Run key fires at Explorer startup in the interactive user's session with full token + HKCU. Unregisters legacy 'GE Shopfloor Map S: Drive' task for PCs already imaged. - Run-ShopfloorSetup.ps1: stop bumping AutoLogonCount (99 at start, 4 at end). Windows decrements per-logon and at 0 clears AutoAdminLogon + DefaultPassword, which nukes the lockdown-configured ShopFloor autologon. Re-enable-wired-NICs task now gates on Autologon_Remediation.log 'Autologon set for ShopFloor' instead of SFLD creds, so wired stays off through the whole Intune+DSC+lockdown chain. - Monitor-IntuneProgress.ps1: Phase 4 treats 'no custom scripts' as COMPLETE when DSC install is done (was WAITING, which stalled the state machine on PC types without scripts). Push retrigger out to 15min when entering lockdown-wait so a stale 5min retrigger doesn't fire mid-Remediation. Removed the AutoLogonCount delete in Invoke-SetupComplete since we no longer set it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
350 lines
15 KiB
PowerShell
350 lines
15 KiB
PowerShell
# Run-ShopfloorSetup.ps1 - Main dispatcher for shopfloor PC type setup.
|
|
#
|
|
# Flow:
|
|
# 1. PreInstall apps (Oracle, VC++, OpenText, UDC) -- from local bundle
|
|
# 2. Type-specific scripts (eDNC, ACLs, CMM share apps, etc.)
|
|
# 3. Deferred baseline (desktop org, taskbar pins)
|
|
# 4. Copy desktop tools (sync_intune, Configure-PC, Set-MachineNumber)
|
|
# 5. Run enrollment (PPKG install + wait for completion)
|
|
# 6. Register sync_intune as @logon scheduled task
|
|
# 7. Reboot -- PPKG file operations complete, sync_intune fires on next logon
|
|
#
|
|
# Called by the unattend FirstLogonCommands as SupportUser (admin).
|
|
|
|
# --- Transcript logging ---
|
|
$logDir = 'C:\Logs\SFLD'
|
|
if (-not (Test-Path $logDir)) {
|
|
try { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } catch { $logDir = $env:TEMP }
|
|
}
|
|
$transcriptPath = Join-Path $logDir 'shopfloor-setup.log'
|
|
try { Start-Transcript -Path $transcriptPath -Append -Force | Out-Null } catch {}
|
|
|
|
Write-Host ""
|
|
Write-Host "================================================================"
|
|
Write-Host "=== Run-ShopfloorSetup.ps1 starting $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') ==="
|
|
Write-Host " Transcript: $transcriptPath"
|
|
Write-Host "================================================================"
|
|
Write-Host ""
|
|
|
|
# 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 <AutoLogon><LogonCount> 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
|
|
|
|
# Wired NIC state handling moved to sync_intune (Monitor-IntuneProgress.ps1).
|
|
# Previously this script prompted the tech to unplug the PXE cable and
|
|
# then re-enabled wired adapters interactively - that blocked the whole
|
|
# imaging chain on human keypress. The new flow leaves the wired state
|
|
# exactly as Order 5 (migrate-to-wifi.ps1) left it:
|
|
# - Tower (no WiFi): wired stays enabled, Run-ShopfloorSetup runs on wired.
|
|
# - Laptop (WiFi): wired disabled, Run-ShopfloorSetup runs on WiFi.
|
|
# Sync_intune re-enables wired at the start of its monitor loop, by which
|
|
# time the tech has had ample wall-clock time to physically unplug PXE
|
|
# and re-cable into production without blocking the chain on a keypress.
|
|
|
|
$enrollDir = "C:\Enrollment"
|
|
$typeFile = Join-Path $enrollDir "pc-type.txt"
|
|
$setupDir = Join-Path $enrollDir "shopfloor-setup"
|
|
|
|
if (-not (Test-Path $typeFile)) {
|
|
Write-Host "No pc-type.txt found - skipping shopfloor setup."
|
|
exit 0
|
|
}
|
|
|
|
$pcType = (Get-Content $typeFile -First 1).Trim()
|
|
if (-not $pcType) {
|
|
Write-Host "pc-type.txt is empty - skipping shopfloor setup."
|
|
exit 0
|
|
}
|
|
|
|
$subtypeFile = Join-Path $enrollDir "pc-subtype.txt"
|
|
$pcSubType = ''
|
|
if (Test-Path $subtypeFile) {
|
|
$pcSubType = (Get-Content $subtypeFile -First 1).Trim()
|
|
}
|
|
|
|
Write-Host "Shopfloor PC Type: $pcType$(if ($pcSubType) { " / $pcSubType" })"
|
|
|
|
# Scripts to skip in the alphabetical baseline loop. Each is either run
|
|
# explicitly in the finalization phase below, or invoked internally by
|
|
# another script:
|
|
#
|
|
# 05 - Office shortcuts. Invoked by 06 as Phase 0. Office isn't installed
|
|
# until after the PPKG + reboot, so 05 no-ops initially. 06's SYSTEM
|
|
# logon task re-runs it on the next boot.
|
|
# 06 - Desktop org. Phase 2 needs eDNC/NTLARS on disk (installed by
|
|
# type-specific 01-eDNC.ps1). Run in finalization phase.
|
|
# 07 - Taskbar pin layout. Reads 06's output. Run in finalization phase.
|
|
# 08 - Edge default browser + startup tabs. Invoked by 06 as Phase 4.
|
|
# Reads .url files delivered by DSC (after Intune enrollment). 06's
|
|
# SYSTEM logon task re-runs it to pick them up.
|
|
$skipInBaseline = @(
|
|
'05-OfficeShortcuts.ps1',
|
|
'06-OrganizeDesktop.ps1',
|
|
'07-TaskbarLayout.ps1',
|
|
'08-EdgeDefaultBrowser.ps1',
|
|
'Check-MachineNumber.ps1',
|
|
'Configure-PC.ps1'
|
|
)
|
|
|
|
# Scripts run AFTER type-specific scripts complete. 05 and 08 are NOT
|
|
# here because 06 calls them internally as sub-phases.
|
|
$runAfterTypeSpecific = @(
|
|
'06-OrganizeDesktop.ps1',
|
|
'07-TaskbarLayout.ps1'
|
|
)
|
|
|
|
# --- Run Shopfloor baseline scripts first (skipping deferred ones) ---
|
|
$baselineDir = Join-Path $setupDir "Shopfloor"
|
|
if (Test-Path $baselineDir) {
|
|
$scripts = Get-ChildItem -Path $baselineDir -Filter "*.ps1" -File | Sort-Object Name
|
|
foreach ($script in $scripts) {
|
|
if ($skipInBaseline -contains $script.Name) {
|
|
Write-Host "Skipping baseline: $($script.Name) (runs in finalization phase)"
|
|
continue
|
|
}
|
|
cmd /c "shutdown /a 2>nul" *>$null
|
|
Write-Host "Running baseline: $($script.Name)"
|
|
try {
|
|
& $script.FullName
|
|
} catch {
|
|
Write-Warning "Baseline script $($script.Name) failed: $_"
|
|
}
|
|
}
|
|
}
|
|
|
|
# --- Run type-specific scripts (if not just baseline Shopfloor) ---
|
|
if ($pcType -ne "Shopfloor") {
|
|
$typeDir = Join-Path $setupDir $pcType
|
|
if (Test-Path $typeDir) {
|
|
# Only run numbered scripts (01-eDNC.ps1, 02-MachineNumberACLs.ps1).
|
|
# Unnumbered .ps1 files (Set-MachineNumber.ps1) are desktop tools,
|
|
# not installer scripts, and must not auto-run during setup.
|
|
$scripts = Get-ChildItem -Path $typeDir -Filter "*.ps1" -File |
|
|
Where-Object { $_.Name -match '^\d' } |
|
|
Sort-Object Name
|
|
foreach ($script in $scripts) {
|
|
cmd /c "shutdown /a 2>nul" *>$null
|
|
Write-Host "Running $pcType setup: $($script.Name)"
|
|
try {
|
|
& $script.FullName
|
|
} catch {
|
|
Write-Warning "Script $($script.Name) failed: $_"
|
|
}
|
|
}
|
|
} else {
|
|
Write-Host "No type-specific scripts found for $pcType."
|
|
}
|
|
}
|
|
|
|
# --- Finalization: run deferred baseline scripts (desktop org, taskbar pins)
|
|
# ---
|
|
# These needed to wait until all apps (eDNC, NTLARS, UDC, OpenText) were
|
|
# installed by the baseline + type-specific phases above. 06 internally
|
|
# calls 05 (Office shortcuts) and 08 (Edge config) as sub-phases, so we
|
|
# only need to invoke 06 and 07 explicitly here.
|
|
foreach ($name in $runAfterTypeSpecific) {
|
|
$script = Join-Path $baselineDir $name
|
|
if (-not (Test-Path $script)) {
|
|
Write-Warning "Deferred script not found: $script"
|
|
continue
|
|
}
|
|
cmd /c "shutdown /a 2>nul" *>$null
|
|
Write-Host "Running deferred baseline: $name"
|
|
try {
|
|
& $script
|
|
} catch {
|
|
Write-Warning "Deferred script $name failed: $_"
|
|
}
|
|
}
|
|
|
|
Write-Host "Shopfloor setup complete for $pcType."
|
|
|
|
# --- Copy utility scripts to SupportUser desktop ---
|
|
foreach ($tool in @('sync_intune.bat', 'Configure-PC.bat', 'Force-Lockdown.bat')) {
|
|
$src = Join-Path $setupDir "Shopfloor\$tool"
|
|
if (Test-Path $src) {
|
|
Copy-Item -Path $src -Destination "C:\Users\SupportUser\Desktop\$tool" -Force
|
|
Write-Host "$tool copied to desktop."
|
|
}
|
|
}
|
|
|
|
# Standard-Machine PCs get the UDC/eDNC machine number helper. Timeclock
|
|
# PCs do not use a machine number, so the helper has nothing to do there.
|
|
if ($pcType -eq "Standard" -and $pcSubType -ne "Timeclock") {
|
|
foreach ($helper in @("Set-MachineNumber.bat", "Set-MachineNumber.ps1")) {
|
|
$src = Join-Path $setupDir "Standard\$helper"
|
|
if (Test-Path $src) {
|
|
Copy-Item -Path $src -Destination "C:\Users\SupportUser\Desktop\$helper" -Force
|
|
Write-Host "$helper copied to SupportUser desktop."
|
|
}
|
|
}
|
|
}
|
|
|
|
# --- Register sync_intune as persistent @logon scheduled task ---
|
|
# Must be registered BEFORE enrollment because Install-ProvisioningPackage
|
|
# triggers an immediate reboot that kills run-enrollment.ps1. The task
|
|
# registration must survive the PPKG reboot, so we do it here while
|
|
# Run-ShopfloorSetup.ps1 is still running.
|
|
#
|
|
# The task fires at every logon until sync_intune detects completion and
|
|
# unregisters itself. It monitors Intune enrollment (Phase 1-5), NOT BPRT
|
|
# app installs -- BPRT finishes on its own in the background after the
|
|
# PPKG reboot, and is irrelevant to the Intune lifecycle.
|
|
$taskName = 'Shopfloor Intune Sync'
|
|
$monitorScript = Join-Path $setupDir 'Shopfloor\lib\Monitor-IntuneProgress.ps1'
|
|
$configureScript = Join-Path $setupDir 'Shopfloor\Configure-PC.ps1'
|
|
|
|
if (Test-Path -LiteralPath $monitorScript) {
|
|
try {
|
|
$action = New-ScheduledTaskAction `
|
|
-Execute 'powershell.exe' `
|
|
-Argument "-NoProfile -NoExit -ExecutionPolicy Bypass -File `"$monitorScript`" -AsTask -ConfigureScript `"$configureScript`""
|
|
|
|
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
|
|
|
$principal = New-ScheduledTaskPrincipal `
|
|
-GroupId 'S-1-5-32-545' `
|
|
-RunLevel Limited
|
|
|
|
$settings = New-ScheduledTaskSettingsSet `
|
|
-AllowStartIfOnBatteries `
|
|
-DontStopIfGoingOnBatteries `
|
|
-StartWhenAvailable `
|
|
-ExecutionTimeLimit (New-TimeSpan -Hours 2)
|
|
|
|
Register-ScheduledTask `
|
|
-TaskName $taskName `
|
|
-Action $action `
|
|
-Trigger $trigger `
|
|
-Principal $principal `
|
|
-Settings $settings `
|
|
-Force `
|
|
-ErrorAction Stop | Out-Null
|
|
|
|
Write-Host "Registered '$taskName' logon task."
|
|
} catch {
|
|
Write-Warning "Failed to register sync task: $_"
|
|
}
|
|
} else {
|
|
Write-Warning "Monitor-IntuneProgress.ps1 not found at $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).
|
|
# (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 lockdown completes (Phase 6) ---
|
|
# migrate-to-wifi.ps1 disables wired NICs so the PPKG runs over WiFi.
|
|
# 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 = @'
|
|
$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
|
|
Unregister-ScheduledTask -TaskName 'GE Re-enable Wired NICs' -Confirm:$false -ErrorAction SilentlyContinue
|
|
'@
|
|
$scriptPath = 'C:\Program Files\GE\ReEnableNIC.ps1'
|
|
if (-not (Test-Path 'C:\Program Files\GE')) {
|
|
New-Item -Path 'C:\Program Files\GE' -ItemType Directory -Force | Out-Null
|
|
}
|
|
Set-Content -Path $scriptPath -Value $script -Force
|
|
|
|
$reEnableAction = New-ScheduledTaskAction -Execute 'powershell.exe' `
|
|
-Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""
|
|
$reEnableTrigger = New-ScheduledTaskTrigger -AtLogOn
|
|
$reEnableTrigger.Repetition = (New-ScheduledTaskTrigger -Once -At (Get-Date) `
|
|
-RepetitionInterval (New-TimeSpan -Minutes 5)).Repetition
|
|
$reEnablePrincipal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
|
|
$reEnableSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
|
|
-ExecutionTimeLimit (New-TimeSpan -Minutes 2)
|
|
Register-ScheduledTask -TaskName $reEnableTask -Action $reEnableAction -Trigger $reEnableTrigger `
|
|
-Principal $reEnablePrincipal -Settings $reEnableSettings -Force -ErrorAction Stop | Out-Null
|
|
Write-Host "Registered '$reEnableTask' task (waits for SFLD creds, then re-enables wired NICs)."
|
|
} catch {
|
|
Write-Warning "Failed to register NIC re-enable task: $_"
|
|
}
|
|
|
|
$commonSetupDir = Join-Path $PSScriptRoot 'common'
|
|
$registerCommon = Join-Path $commonSetupDir 'Register-CommonEnforce.ps1'
|
|
if (Test-Path -LiteralPath $registerCommon) {
|
|
Write-Host ""
|
|
Write-Host "=== Registering Common Apps enforcer ==="
|
|
try { & $registerCommon } catch { Write-Warning "Common enforce registration failed: $_" }
|
|
} else {
|
|
Write-Host "Register-CommonEnforce.ps1 not found (optional) - skipping"
|
|
}
|
|
|
|
# Map S: drive on user logon for every account in BUILTIN\Users. The
|
|
# vendor 'SFLD - Consume Credentials' task is principal-restricted and
|
|
# does not fire for the ShopFloor end-user, so this parallel task fills
|
|
# the gap. Cross-PC-type because every shopfloor account needs S:.
|
|
$registerMapShare = Join-Path $PSScriptRoot 'Shopfloor\Register-MapSfldShare.ps1'
|
|
if (Test-Path -LiteralPath $registerMapShare) {
|
|
Write-Host ""
|
|
Write-Host "=== Registering S: drive logon mapper ==="
|
|
try { & $registerMapShare } catch { Write-Warning "Map-SfldShare registration failed: $_" }
|
|
} else {
|
|
Write-Host "Register-MapSfldShare.ps1 not found (optional) - skipping"
|
|
}
|
|
|
|
# Standard-Machine gets a machine-apps enforcer (UDC, eDNC, NTLARS) that
|
|
# replaced the Intune DSC path (DSC has no sub-type awareness and was
|
|
# pushing these to Timeclocks). Timeclocks skip this registration.
|
|
if ($pcType -eq "Standard" -and $pcSubType -eq "Machine") {
|
|
$registerMachine = Join-Path $setupDir "Standard\Register-MachineEnforce.ps1"
|
|
if (Test-Path -LiteralPath $registerMachine) {
|
|
Write-Host ""
|
|
Write-Host "=== Registering Machine-apps enforcer ==="
|
|
try { & $registerMachine } catch { Write-Warning "Machine enforce registration failed: $_" }
|
|
} else {
|
|
Write-Host "Register-MachineEnforce.ps1 not found (optional) - skipping"
|
|
}
|
|
}
|
|
|
|
# --- Run enrollment (PPKG install) ---
|
|
# Enrollment is the LAST thing we do. Install-ProvisioningPackage triggers
|
|
# an immediate reboot -- everything after this call is unlikely to execute.
|
|
# The sync_intune task is already registered above, so the PPKG reboot
|
|
# can kill us and the chain continues on the next boot.
|
|
$enrollScript = Join-Path $enrollDir 'run-enrollment.ps1'
|
|
if (Test-Path -LiteralPath $enrollScript) {
|
|
Write-Host ""
|
|
Write-Host "=== Running enrollment (PPKG install) ==="
|
|
Write-Host "NOTE: PPKG will trigger an immediate reboot. This is expected."
|
|
try { Stop-Transcript | Out-Null } catch {}
|
|
& $enrollScript
|
|
# If we get here, the PPKG didn't reboot (maybe no PPKG file found).
|
|
Write-Host "Enrollment returned without rebooting. Rebooting now..."
|
|
shutdown /r /t 10
|
|
} else {
|
|
Write-Host "run-enrollment.ps1 not found - skipping enrollment."
|
|
Write-Host ""
|
|
Write-Host "================================================================"
|
|
Write-Host "=== Run-ShopfloorSetup.ps1 complete $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') ==="
|
|
Write-Host "================================================================"
|
|
try { Stop-Transcript | Out-Null } catch {}
|
|
Write-Host "Rebooting in 10 seconds..."
|
|
shutdown /r /t 10
|
|
}
|