Wax/Trace: defer HKEY_USERS per-user prefs restore to first ShopFloor logon via SYSTEM scheduled task
Bay's ShopFloor user account exists but has never logged in at imaging time, so its NTUSER.DAT doesn't exist yet and we can't reg-load its hive to remap source SID -> ShopFloor SID. The in-line restore at 09-Setup Step 3b handles HKLM (controller config, device-map) + files, but per-user prefs (LouteditS Layout, Page margins, Recent Files, ~2700 rows in a typical WJF capture) get skipped. Fix: register a SYSTEM-context scheduled task at imaging time that fires AtLogOn UserId=ShopFloor. When ShopFloor first logs in, Windows loads their NTUSER.DAT automatically; task fires (running as SYSTEM so lockdown policies on ShopFloor's user-context don't block HKLM writes via the same Install script); SID-remap path finds the live hive and writes prefs into HKEY_USERS\<ShopFloor-sid>. Task writes a flag file + unregisters itself after one successful run. Pieces: - Install-FormtracepakSettings.ps1: new -HKEYUsersOnly switch that skips the HKLM .reg files + HKLM CSV rows (already restored at imaging time). Fallback user chain ShopFloor->SupportUser->$USERNAME. - Schedule-WaxTracePerUserRestore.ps1: registers the task, writes C:\WaxTrace-Install\Run-WaxTracePerUserRestore.ps1 task action which invokes Install with -HKEYUsersOnly and self-cleans on success. - 09-Setup-WaxAndTrace.ps1 Step 3b: in-line restore now uses -RestoreRegistry -RestoreData -RestoreConfig (HKLM + files now); calls Schedule-WaxTracePerUserRestore.ps1 to queue HKEY_USERS for first ShopFloor logon. - sync-waxtrace.sh: pushes Schedule-WaxTracePerUserRestore.ps1 to PXE share alongside Install-FormtracepakSettings.ps1. Smoke tested on win11 VM partially: task registration works, manual trigger fires + self-unregisters cleanly, flag file lands. Real per- user SID-remap happens at first ShopFloor logon (can't simulate from qga without an interactive ShopFloor session).
This commit is contained in:
@@ -329,27 +329,63 @@ if (-not $asset) {
|
||||
# does not exist on the freshly imaged bay). HKLM controller config / model
|
||||
# device-map / etc. came from the vendor MSI install in Step 2 already.
|
||||
$backupZip = $null
|
||||
$backupDir = 'C:\WaxTrace-Install\backup'
|
||||
if ($bayAsset -and (Test-Path -LiteralPath $backupDir)) {
|
||||
$candidate = Join-Path $backupDir ("$bayAsset.zip")
|
||||
# startnet.cmd robocopy's the whole installers-post\waxtrace\backups\ dir
|
||||
# to C:\WaxTrace-Install\backups\ (plural). Try plural first, then singular
|
||||
# for backward-compat with any older boot.wim that used the cherry-pick path.
|
||||
$backupDirCandidates = @(
|
||||
'C:\WaxTrace-Install\backups',
|
||||
'C:\WaxTrace-Install\backup'
|
||||
)
|
||||
foreach ($bd in $backupDirCandidates) {
|
||||
if (-not (Test-Path -LiteralPath $bd) -or -not $bayAsset) { continue }
|
||||
$candidate = Join-Path $bd ("$bayAsset.zip")
|
||||
if (Test-Path -LiteralPath $candidate) {
|
||||
$backupZip = $candidate
|
||||
} else {
|
||||
$newest = Get-ChildItem -LiteralPath $backupDir -Filter "${bayAsset}*.zip" -File -ErrorAction SilentlyContinue |
|
||||
break
|
||||
}
|
||||
$newest = Get-ChildItem -LiteralPath $bd -Filter "${bayAsset}*.zip" -File -ErrorAction SilentlyContinue |
|
||||
Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||
if ($newest) { $backupZip = $newest.FullName }
|
||||
if ($newest) {
|
||||
$backupZip = $newest.FullName
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($backupZip) {
|
||||
$installPs1 = Join-Path $stagingRoot 'Install-FormtracepakSettings.ps1'
|
||||
if (Test-Path -LiteralPath $installPs1) {
|
||||
Write-WTLog "Restoring per-asset backup from $backupZip"
|
||||
Write-WTLog "Restoring per-asset backup from $backupZip (HKLM + files; HKEY_USERS deferred to first ShopFloor logon)"
|
||||
try {
|
||||
& $installPs1 -BackupPath $backupZip -RestoreData -RestoreConfig -Force
|
||||
# In-line restore: HKLM (controller config, device-map) + files
|
||||
# (layouts, prefs in install dir). HKEY_USERS per-user prefs would
|
||||
# need ShopFloor's hive loaded, which it isn't at imaging time -
|
||||
# deferred to a scheduled SYSTEM task that fires on first ShopFloor
|
||||
# logon (see Schedule-WaxTracePerUserRestore call below).
|
||||
& $installPs1 -BackupPath $backupZip -RestoreRegistry -RestoreData -RestoreConfig -Force
|
||||
Write-WTLog " Restore call returned (see restore log + Install-FormtracepakSettings output above)"
|
||||
} catch {
|
||||
Write-WTLog " Restore call threw: $_" 'WARN'
|
||||
}
|
||||
|
||||
# Schedule the per-user prefs restore as a SYSTEM task that fires on
|
||||
# first ShopFloor logon. The task self-removes after one successful
|
||||
# run via flag file at C:\WaxTrace-Install\per-user-restore-ShopFloor.flag.
|
||||
$schedScript = Join-Path $stagingRoot 'Schedule-WaxTracePerUserRestore.ps1'
|
||||
if (-not (Test-Path -LiteralPath $schedScript)) {
|
||||
# Fall back: pull from shopfloor-setup scripts/ sibling tree
|
||||
$alt = Join-Path $PSScriptRoot 'scripts\Schedule-WaxTracePerUserRestore.ps1'
|
||||
if (Test-Path -LiteralPath $alt) { $schedScript = $alt }
|
||||
}
|
||||
if (Test-Path -LiteralPath $schedScript) {
|
||||
Write-WTLog "Registering deferred ShopFloor per-user restore task ($schedScript)"
|
||||
try {
|
||||
& $schedScript -BackupPath $backupZip -InstallScript $installPs1 -TargetUser 'ShopFloor' -AssetNumber $bayAsset
|
||||
Write-WTLog " Scheduled task 'WaxTrace-PerUser-Restore' registered"
|
||||
} catch {
|
||||
Write-WTLog " Schedule task call threw: $_" 'WARN'
|
||||
}
|
||||
} else {
|
||||
Write-WTLog "Schedule-WaxTracePerUserRestore.ps1 not found - per-user prefs will NOT auto-restore at first ShopFloor logon" 'WARN'
|
||||
}
|
||||
} else {
|
||||
Write-WTLog "Install-FormtracepakSettings.ps1 not found at $installPs1 - skipping restore" 'WARN'
|
||||
}
|
||||
|
||||
@@ -57,7 +57,19 @@ param(
|
||||
[switch]$RestoreConfig,
|
||||
[switch]$RestoreAll,
|
||||
[switch]$DryRun,
|
||||
[switch]$Force
|
||||
[switch]$Force,
|
||||
# Target user to remap captured HKEY_USERS\<src-sid> per-user prefs onto.
|
||||
# Captured backup carries the source bay's interactive user SID (e.g.
|
||||
# lg782713sd at WJ); on the imaged bay that SID doesn't exist as a
|
||||
# loaded hive. We look up $TargetUser's SID and rewrite the SID in
|
||||
# both .reg files and the CSV restore path before importing, so prefs
|
||||
# land under the new bay's user instead of an orphan SID.
|
||||
[string]$TargetUser = 'ShopFloor',
|
||||
# When true, skip HKLM .reg files + HKLM CSV rows. Used by the
|
||||
# deferred scheduled task that runs on first ShopFloor login - HKLM
|
||||
# was already restored during imaging, only HKEY_USERS prefs need
|
||||
# to land in the now-loaded ShopFloor hive.
|
||||
[switch]$HKEYUsersOnly
|
||||
)
|
||||
|
||||
$SharedRoot = 'S:\DT\Shopfloor\backup\waxandtrace'
|
||||
@@ -357,14 +369,126 @@ if ($RestoreRegistry) {
|
||||
|
||||
$regDir = Join-Path $workingBackup 'registry'
|
||||
|
||||
# Method 1: .reg file import
|
||||
# --- HKEY_USERS SID remap setup ---
|
||||
# The captured HKEY_USERS\<src-sid> entries belong to the source bay's
|
||||
# interactive user (e.g. lg782713sd at WJ). On the imaged bay the
|
||||
# equivalent user is whatever $TargetUser is (default: ShopFloor).
|
||||
# Resolve target user's SID + ensure the hive is loaded so the rewrite
|
||||
# lands somewhere real. If we can't resolve the target user, fall back
|
||||
# to skipping the HKEY_USERS portion entirely (HKLM still restores).
|
||||
$srcSidRegex = '(S-1-5-21-\d+-\d+-\d+-\d+)'
|
||||
$targetSid = $null
|
||||
$tempHiveLoaded = $false
|
||||
# Resolve $TargetUser. If user gave an explicit value AND it resolves,
|
||||
# use it. Otherwise try the fallback chain: ShopFloor (post-SFLD-2.0
|
||||
# default) -> SupportUser (imaging-context user) -> current $env:USERNAME.
|
||||
$candidateUsers = @()
|
||||
if ($TargetUser) { $candidateUsers += $TargetUser }
|
||||
foreach ($u in @('ShopFloor','SupportUser', $env:USERNAME)) {
|
||||
if ($u -and ($candidateUsers -notcontains $u)) { $candidateUsers += $u }
|
||||
}
|
||||
foreach ($cand in $candidateUsers) {
|
||||
try {
|
||||
$u = Get-LocalUser -Name $cand -ErrorAction Stop
|
||||
$targetSid = $u.SID.Value
|
||||
$TargetUser = $cand
|
||||
Write-Host " Target user '$TargetUser' SID = $targetSid (Get-LocalUser)"
|
||||
break
|
||||
} catch { }
|
||||
# Fall back: scan HKLM ProfileList for ProfileImagePath matching
|
||||
# C:\Users\<cand>. Works for both local and domain users.
|
||||
$profileList = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'
|
||||
$sidFromProfile = $null
|
||||
Get-ChildItem $profileList -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
$p = (Get-ItemProperty -LiteralPath $_.PSPath -ErrorAction SilentlyContinue).ProfileImagePath
|
||||
if ($p -and (Split-Path -Leaf $p) -ieq $cand) {
|
||||
$sidFromProfile = $_.PSChildName
|
||||
}
|
||||
}
|
||||
if ($sidFromProfile) {
|
||||
$targetSid = $sidFromProfile
|
||||
$TargetUser = $cand
|
||||
Write-Host " Target user '$TargetUser' SID = $targetSid (ProfileList)"
|
||||
break
|
||||
}
|
||||
}
|
||||
if (-not $targetSid) {
|
||||
Write-Warning " Could not resolve any of: $($candidateUsers -join ', ') - per-user prefs will be SKIPPED"
|
||||
}
|
||||
if (-not $targetSid) {
|
||||
Write-Warning " Could not resolve '$TargetUser' SID - per-user prefs will be SKIPPED (only HKLM restores)"
|
||||
} else {
|
||||
# Ensure the target hive is loaded so reg.exe import + New-Item paths
|
||||
# against HKEY_USERS\<targetSid>\... resolve.
|
||||
if (-not (Test-Path "Registry::HKEY_USERS\$targetSid")) {
|
||||
$ntuser = "C:\Users\$TargetUser\NTUSER.DAT"
|
||||
if (Test-Path -LiteralPath $ntuser) {
|
||||
Write-Host " Loading $TargetUser hive from $ntuser as HKU\$targetSid"
|
||||
$proc = Start-Process -FilePath 'reg.exe' -ArgumentList "load `"HKU\$targetSid`" `"$ntuser`"" `
|
||||
-Wait -PassThru -NoNewWindow -ErrorAction SilentlyContinue
|
||||
if ($proc.ExitCode -eq 0) {
|
||||
$tempHiveLoaded = $true
|
||||
} else {
|
||||
Write-Warning " Failed to load $TargetUser hive (exit $($proc.ExitCode)) - per-user prefs will be SKIPPED"
|
||||
$targetSid = $null
|
||||
}
|
||||
} else {
|
||||
Write-Warning " $ntuser not found and HKU\$targetSid not loaded - per-user prefs will be SKIPPED"
|
||||
$targetSid = $null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Method 1: .reg file import. If target SID resolved, rewrite the
|
||||
# source SID in the .reg content to the target SID before importing.
|
||||
# If not resolved, skip HKEY_USERS .reg files.
|
||||
$regFiles = Get-ChildItem -Path $regDir -Filter '*.reg' -ErrorAction SilentlyContinue
|
||||
foreach ($rf in $regFiles) {
|
||||
$isUserReg = ($rf.Name -match 'HKEY_USERS')
|
||||
if ($HKEYUsersOnly -and -not $isUserReg) {
|
||||
Write-Host " [SKIP] HKEY_USERS-only mode, skipping HKLM .reg: $($rf.Name)" -ForegroundColor DarkGray
|
||||
$counters.Skipped++
|
||||
continue
|
||||
}
|
||||
if ($isUserReg -and -not $targetSid) {
|
||||
Write-Host " [SKIP] HKEY_USERS .reg file - target SID not resolved: $($rf.Name)" -ForegroundColor DarkGray
|
||||
$counters.Skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
$importPath = $rf.FullName
|
||||
if ($isUserReg) {
|
||||
# Read .reg, detect src SID (first S-1-5-21-* match), substitute
|
||||
# target SID, write to a temp file. reg.exe import the temp.
|
||||
try {
|
||||
$content = Get-Content -LiteralPath $rf.FullName -Raw
|
||||
$m = [regex]::Match($content, $srcSidRegex)
|
||||
if ($m.Success) {
|
||||
$srcSid = $m.Value
|
||||
if ($srcSid -ne $targetSid) {
|
||||
$newContent = $content -replace [regex]::Escape($srcSid), $targetSid
|
||||
$tmp = Join-Path $env:TEMP ("rewrite-" + $rf.Name)
|
||||
# .reg files MUST be UTF-16-LE (Unicode) for reg.exe import.
|
||||
# Set-Content -Encoding Unicode does that.
|
||||
Set-Content -LiteralPath $tmp -Value $newContent -Encoding Unicode -Force
|
||||
$importPath = $tmp
|
||||
Write-Host " [REMAP] $($rf.Name): SID $srcSid -> $targetSid"
|
||||
}
|
||||
} else {
|
||||
Write-Host " [INFO] No SID found in $($rf.Name) - importing as-is" -ForegroundColor DarkGray
|
||||
}
|
||||
} catch {
|
||||
Write-Warning " [ERR] SID rewrite failed for $($rf.Name): $_ - skipping"
|
||||
$counters.Errors++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Host " [DRYRUN] REG IMPORT $($rf.FullName)" -ForegroundColor DarkGray
|
||||
Write-Host " [DRYRUN] REG IMPORT $importPath" -ForegroundColor DarkGray
|
||||
} else {
|
||||
try {
|
||||
$proc = Start-Process -FilePath 'reg.exe' -ArgumentList "import `"$($rf.FullName)`"" `
|
||||
$proc = Start-Process -FilePath 'reg.exe' -ArgumentList "import `"$importPath`"" `
|
||||
-Wait -PassThru -NoNewWindow -ErrorAction Stop
|
||||
if ($proc.ExitCode -eq 0) {
|
||||
Write-Host " [OK] Imported $($rf.Name)" -ForegroundColor Green
|
||||
@@ -402,6 +526,33 @@ if ($RestoreRegistry) {
|
||||
continue
|
||||
}
|
||||
|
||||
# HKEYUsersOnly mode: skip HKLM rows entirely (already restored
|
||||
# during imaging by the in-line Step 3b run).
|
||||
if ($HKEYUsersOnly -and $regPath -notmatch 'HKEY_USERS\\') {
|
||||
$counters.Skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
# HKEY_USERS\<src-sid> rows: rewrite SID to target user's SID
|
||||
# so per-user prefs from the source bay's interactive user land
|
||||
# under the new bay's ShopFloor (or whichever $TargetUser is).
|
||||
# If target SID couldn't be resolved, skip the row instead of
|
||||
# erroring on the orphan path.
|
||||
if ($regPath -match 'HKEY_USERS\\') {
|
||||
if (-not $targetSid) {
|
||||
Write-Host " [SKIP] HKEY_USERS row, no target SID: $regPath\$regName" -ForegroundColor DarkGray
|
||||
$counters.Skipped++
|
||||
continue
|
||||
}
|
||||
$srcMatch = [regex]::Match($regPath, $srcSidRegex)
|
||||
if ($srcMatch.Success) {
|
||||
$srcSid = $srcMatch.Value
|
||||
if ($srcSid -ne $targetSid) {
|
||||
$regPath = $regPath -replace [regex]::Escape($srcSid), $targetSid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($regPath -match '^HKLM' -and -not $isAdmin -and -not $DryRun) {
|
||||
Write-Host " [SKIP] HKLM key requires elevation: $regPath\$regName" -ForegroundColor DarkYellow
|
||||
$counters.Skipped++
|
||||
@@ -433,6 +584,23 @@ if ($RestoreRegistry) {
|
||||
$counters.RegistryKeys++
|
||||
}
|
||||
}
|
||||
|
||||
# Unload the target user's hive if we loaded it ourselves. Otherwise
|
||||
# leave it alone (might be the live logged-in user). If we don't unload
|
||||
# what we loaded, NTUSER.DAT stays locked and the user can't log in
|
||||
# cleanly until reboot.
|
||||
if ($tempHiveLoaded -and $targetSid) {
|
||||
Write-Host " Unloading temporary $TargetUser hive at HKU\$targetSid"
|
||||
# Force a GC pass to release any registry handles PowerShell is
|
||||
# holding so reg unload doesn't fail with "access denied".
|
||||
[GC]::Collect()
|
||||
[GC]::WaitForPendingFinalizers()
|
||||
$proc = Start-Process -FilePath 'reg.exe' -ArgumentList "unload `"HKU\$targetSid`"" `
|
||||
-Wait -PassThru -NoNewWindow -ErrorAction SilentlyContinue
|
||||
if ($proc.ExitCode -ne 0) {
|
||||
Write-Warning " reg.exe unload returned exit $($proc.ExitCode) - $TargetUser NTUSER.DAT may stay locked until reboot"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------- Summary ----------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
# Schedule-WaxTracePerUserRestore.ps1
|
||||
#
|
||||
# Registers a SYSTEM-context scheduled task that fires on $TargetUser's
|
||||
# first logon and restores HKEY_USERS per-user FormTracePak prefs from
|
||||
# the per-asset backup ZIP into the now-loaded target user's hive.
|
||||
#
|
||||
# Why scheduled task + SYSTEM:
|
||||
# At imaging time, the target user (ShopFloor) hasn't logged in yet so
|
||||
# their NTUSER.DAT doesn't exist - can't reg-load their hive. By
|
||||
# deferring to first-logon, the hive is loaded automatically by Windows.
|
||||
# Running as SYSTEM means the task is not subject to lockdown policies
|
||||
# applied to ShopFloor's user-context tokens.
|
||||
#
|
||||
# The task self-removes after one successful run (via a flag file +
|
||||
# Unregister-ScheduledTask in the task action). If the restore errors,
|
||||
# the flag is NOT written so the task retries on next logon.
|
||||
#
|
||||
# Usage (from 09-Setup-WaxAndTrace Step 3b after the in-line HKLM restore):
|
||||
# & .\Schedule-WaxTracePerUserRestore.ps1 `
|
||||
# -BackupPath C:\WaxTrace-Install\backup\<asset>.zip `
|
||||
# -InstallScript C:\WaxTrace-Install\Install-FormtracepakSettings.ps1 `
|
||||
# -TargetUser ShopFloor `
|
||||
# -AssetNumber <asset>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$BackupPath,
|
||||
[string]$InstallScript = 'C:\WaxTrace-Install\Install-FormtracepakSettings.ps1',
|
||||
[string]$TargetUser = 'ShopFloor',
|
||||
[string]$AssetNumber = '',
|
||||
[string]$RunnerScript = 'C:\WaxTrace-Install\Run-WaxTracePerUserRestore.ps1',
|
||||
[string]$TaskName = 'WaxTrace-PerUser-Restore'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $BackupPath)) {
|
||||
Write-Warning "Backup ZIP not found at $BackupPath - skipping per-user restore task registration."
|
||||
exit 1
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $InstallScript)) {
|
||||
Write-Warning "Install script not found at $InstallScript - skipping per-user restore task registration."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Drop the task-runner script alongside Install. It's small + self-contained.
|
||||
# Reads the same params we register the task with, invokes Install with
|
||||
# -HKEYUsersOnly + writes flag + unregisters self.
|
||||
$runnerDir = Split-Path -Parent $RunnerScript
|
||||
if (-not (Test-Path -LiteralPath $runnerDir)) {
|
||||
New-Item -ItemType Directory -Path $runnerDir -Force | Out-Null
|
||||
}
|
||||
|
||||
@"
|
||||
# Run-WaxTracePerUserRestore.ps1 - task action, runs as SYSTEM at logon
|
||||
# Auto-generated by Schedule-WaxTracePerUserRestore.ps1; do not hand-edit.
|
||||
|
||||
param(
|
||||
[string]`$BackupPath = '$BackupPath',
|
||||
[string]`$InstallScript = '$InstallScript',
|
||||
[string]`$TargetUser = '$TargetUser',
|
||||
[string]`$AssetNumber = '$AssetNumber',
|
||||
[string]`$TaskName = '$TaskName',
|
||||
[string]`$FlagFile = 'C:\WaxTrace-Install\per-user-restore-$TargetUser.flag'
|
||||
)
|
||||
|
||||
`$ErrorActionPreference = 'Continue'
|
||||
`$logDir = 'C:\Logs\WaxTrace'
|
||||
if (-not (Test-Path `$logDir)) { New-Item -ItemType Directory -Path `$logDir -Force | Out-Null }
|
||||
`$log = Join-Path `$logDir ('per-user-restore-' + `$TargetUser + '-' + (Get-Date -Format 'yyyyMMdd_HHmmss') + '.log')
|
||||
Start-Transcript -LiteralPath `$log -Append -IncludeInvocationHeader | Out-Null
|
||||
|
||||
try {
|
||||
Write-Host "=== Per-user restore task ==="
|
||||
Write-Host " TargetUser = `$TargetUser"
|
||||
Write-Host " AssetNumber = `$AssetNumber"
|
||||
Write-Host " BackupPath = `$BackupPath"
|
||||
Write-Host " InstallScript = `$InstallScript"
|
||||
Write-Host " FlagFile = `$FlagFile"
|
||||
Write-Host " RunningAs = `$(`$env:USERNAME) ([Security.Principal.WindowsIdentity]::GetCurrent().Name)"
|
||||
|
||||
if (Test-Path -LiteralPath `$FlagFile) {
|
||||
Write-Host "Flag file present - already complete, exiting."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Give Windows a moment to finish loading the user hive after logon.
|
||||
Start-Sleep -Seconds 8
|
||||
|
||||
if (-not (Test-Path -LiteralPath `$BackupPath)) { Write-Warning "Backup ZIP missing: `$BackupPath"; exit 2 }
|
||||
if (-not (Test-Path -LiteralPath `$InstallScript)) { Write-Warning "Install script missing: `$InstallScript"; exit 2 }
|
||||
|
||||
& `$InstallScript -BackupPath `$BackupPath -RestoreRegistry -HKEYUsersOnly -Force -TargetUser `$TargetUser
|
||||
`$rc = `$LASTEXITCODE
|
||||
if (`$null -eq `$rc) { `$rc = 0 }
|
||||
|
||||
if (`$rc -eq 0) {
|
||||
Set-Content -LiteralPath `$FlagFile -Value (Get-Date -Format 'o') -Encoding ascii -Force
|
||||
Write-Host "Restore complete - flag file written. Unregistering task '`$TaskName'."
|
||||
Unregister-ScheduledTask -TaskName `$TaskName -Confirm:`$false -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
Write-Warning "Restore returned exit code `$rc - leaving task registered for retry on next logon."
|
||||
}
|
||||
exit `$rc
|
||||
} catch {
|
||||
Write-Warning "Task action threw: `$_"
|
||||
exit 99
|
||||
} finally {
|
||||
Stop-Transcript | Out-Null
|
||||
}
|
||||
"@ | Set-Content -LiteralPath $RunnerScript -Encoding ascii -Force
|
||||
Write-Host "Wrote task action script: $RunnerScript"
|
||||
|
||||
# Register the task. AtLogOn trigger filtered to $TargetUser so it only fires
|
||||
# when that user logs on (other users logging on don't trigger). Principal
|
||||
# SYSTEM, RunLevel Highest = full privileges including HKLM writes that
|
||||
# survive lockdown policies on ShopFloor's user-context.
|
||||
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$RunnerScript`""
|
||||
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $TargetUser
|
||||
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
|
||||
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 30)
|
||||
|
||||
# Unregister any existing instance first so re-runs are clean.
|
||||
if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) {
|
||||
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
|
||||
}
|
||||
|
||||
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description "FormTracePak per-user prefs restore for $TargetUser (one-shot at first logon, then self-removes). Asset $AssetNumber." | Out-Null
|
||||
|
||||
Write-Host "Registered scheduled task '$TaskName':"
|
||||
Write-Host " Trigger: AtLogOn UserId=$TargetUser"
|
||||
Write-Host " Principal: SYSTEM (RunLevel Highest)"
|
||||
Write-Host " Action: $RunnerScript"
|
||||
Write-Host " Self-removes after one successful restore."
|
||||
@@ -81,6 +81,14 @@ fi
|
||||
if [ -f "$WAXTRACE_DIR/scripts/Install-FormtracepakSettings.ps1" ]; then
|
||||
cp "$WAXTRACE_DIR/scripts/Install-FormtracepakSettings.ps1" "$STAGE/"
|
||||
fi
|
||||
# Schedule-WaxTracePerUserRestore.ps1 - registers a SYSTEM scheduled task
|
||||
# that fires on first ShopFloor logon to restore HKEY_USERS per-user prefs
|
||||
# (deferred from imaging time because ShopFloor's hive isn't loaded yet).
|
||||
# Path on the bay post-startnet-robocopy:
|
||||
# C:\WaxTrace-Install\Schedule-WaxTracePerUserRestore.ps1.
|
||||
if [ -f "$WAXTRACE_DIR/scripts/Schedule-WaxTracePerUserRestore.ps1" ]; then
|
||||
cp "$WAXTRACE_DIR/scripts/Schedule-WaxTracePerUserRestore.ps1" "$STAGE/"
|
||||
fi
|
||||
cp "$WAXTRACE_DIR/captured-binary/prereqs/"*.exe "$STAGE/prereqs/"
|
||||
|
||||
# FormTracePak vendor installer ISOs - all available versions get pushed.
|
||||
|
||||
Reference in New Issue
Block a user