Wax/Trace: classify restore .reg by content root, not filename

Install-FormtracepakSettings decided per-user vs HKLM by matching the
filename against 'HKEY_USERS'. The backup names files after their source
path, so an operator pref captured from HKCU:\ lands in a file named
"HKCU_..." with content root HKEY_CURRENT_USER - which the filename match
missed entirely, dropping it from the restore.

Read each .reg once and classify by its content root(s):
  [HKEY_CURRENT_USER...  -> per-user, remap root to HKEY_USERS\<targetSid>
  [HKEY_USERS\<srcSid>... -> per-user, remap srcSid -> targetSid
  [HKEY_LOCAL_MACHINE...  -> HKLM
The temp rewrite (UTF-16-LE for reg.exe import) is only written when the
content actually changes; otherwise the file imports as-is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-06-01 11:54:00 -04:00
parent 1a5852f7ff
commit c88b2b0ab8

View File

@@ -444,41 +444,64 @@ if ($RestoreRegistry) {
# If not resolved, skip HKEY_USERS .reg files. # If not resolved, skip HKEY_USERS .reg files.
$regFiles = Get-ChildItem -Path $regDir -Filter '*.reg' -ErrorAction SilentlyContinue $regFiles = Get-ChildItem -Path $regDir -Filter '*.reg' -ErrorAction SilentlyContinue
foreach ($rf in $regFiles) { foreach ($rf in $regFiles) {
$isUserReg = ($rf.Name -match 'HKEY_USERS') # Decide per-user vs HKLM by the .reg CONTENT root, NOT the filename.
# The backup names files after the source path, so an operator pref
# captured via HKCU:\ lands in a file named "HKCU_..." with content
# root HKEY_CURRENT_USER - which a filename match on 'HKEY_USERS'
# misses entirely. Read the content once and classify by its root(s):
# [HKEY_CURRENT_USER... -> per-user, remap root to HKEY_USERS\<targetSid>
# [HKEY_USERS\<srcSid>... -> per-user, remap srcSid -> targetSid
# [HKEY_LOCAL_MACHINE... -> HKLM
$content = $null
try { $content = Get-Content -LiteralPath $rf.FullName -Raw } catch {
Write-Warning " [ERR] Could not read $($rf.Name): $_ - skipping"
$counters.Errors++
continue
}
$hasCU = $content -match '\[HKEY_CURRENT_USER'
$hasHKU = $content -match '\[HKEY_USERS\\S-1-5-21'
$isUserReg = ($hasCU -or $hasHKU)
if ($HKEYUsersOnly -and -not $isUserReg) { if ($HKEYUsersOnly -and -not $isUserReg) {
Write-Host " [SKIP] HKEY_USERS-only mode, skipping HKLM .reg: $($rf.Name)" -ForegroundColor DarkGray Write-Host " [SKIP] HKEY_USERS-only mode, skipping HKLM .reg: $($rf.Name)" -ForegroundColor DarkGray
$counters.Skipped++ $counters.Skipped++
continue continue
} }
if ($isUserReg -and -not $targetSid) { if ($isUserReg -and -not $targetSid) {
Write-Host " [SKIP] HKEY_USERS .reg file - target SID not resolved: $($rf.Name)" -ForegroundColor DarkGray Write-Host " [SKIP] per-user .reg - target SID not resolved: $($rf.Name)" -ForegroundColor DarkGray
$counters.Skipped++ $counters.Skipped++
continue continue
} }
$importPath = $rf.FullName $importPath = $rf.FullName
if ($isUserReg) { if ($isUserReg) {
# Read .reg, detect src SID (first S-1-5-21-* match), substitute # Rewrite the root(s) so the import lands in the TARGET user's hive,
# target SID, write to a temp file. reg.exe import the temp. # then write a temp UTF-16-LE file (reg.exe import requires Unicode).
try { try {
$content = Get-Content -LiteralPath $rf.FullName -Raw $newContent = $content
$m = [regex]::Match($content, $srcSidRegex) if ($hasHKU) {
if ($m.Success) { # HKEY_USERS\<srcSid> -> HKEY_USERS\<targetSid>
$srcSid = $m.Value $m = [regex]::Match($content, $srcSidRegex)
if ($srcSid -ne $targetSid) { if ($m.Success -and $m.Value -ne $targetSid) {
$newContent = $content -replace [regex]::Escape($srcSid), $targetSid $newContent = $newContent -replace [regex]::Escape($m.Value), $targetSid
$tmp = Join-Path $env:TEMP ("rewrite-" + $rf.Name) Write-Host " [REMAP] $($rf.Name): SID $($m.Value) -> $targetSid"
# .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"
} }
}
if ($hasCU) {
# HKEY_CURRENT_USER -> HKEY_USERS\<targetSid> (this is the
# case the old filename-based logic dropped on the floor).
$newContent = $newContent -replace '\[HKEY_CURRENT_USER', "[HKEY_USERS\$targetSid"
Write-Host " [REMAP] $($rf.Name): HKEY_CURRENT_USER -> HKEY_USERS\$targetSid"
}
if ($newContent -ne $content) {
$tmp = Join-Path $env:TEMP ("rewrite-" + $rf.Name)
Set-Content -LiteralPath $tmp -Value $newContent -Encoding Unicode -Force
$importPath = $tmp
} else { } else {
Write-Host " [INFO] No SID found in $($rf.Name) - importing as-is" -ForegroundColor DarkGray Write-Host " [INFO] $($rf.Name): per-user root already matches target - importing as-is" -ForegroundColor DarkGray
} }
} catch { } catch {
Write-Warning " [ERR] SID rewrite failed for $($rf.Name): $_ - skipping" Write-Warning " [ERR] root rewrite failed for $($rf.Name): $_ - skipping"
$counters.Errors++ $counters.Errors++
continue continue
} }