Wax/Trace triad: fix registry corruption + cover v6.213 vendor install path

Three fixes in Backup / Export / Install, validated end-to-end on the win11 VM
against a seeded HKCU\SOFTWARE\Mitutoyo\Formtracepak key carrying all five
registry value types (String, DWord, ExpandString, MultiString, Binary).

1. Registry corruption on REG_BINARY / REG_MULTI_SZ restore
   Backup wrote those values to registry_values.csv via [string]$val, which
   lossily coerces a byte[] to "System.Byte[]" and a string[] to a
   space-joined scalar. Install's CSV restore loop runs AFTER the .reg file
   import (which is lossless), so the CSV pass overwrites the good values
   with corrupted strings. Two-part fix:
   - Backup: skip Binary / MultiString / None / Unknown when writing the CSV.
     Only String, ExpandString, DWord, QWord roundtrip cleanly through
     New-ItemProperty -PropertyType, so capture only those. The .reg file
     remains authoritative for the rest.
   - Install: defensive filter on the CSV restore loop that skips any row
     whose Type is not in {String, ExpandString, DWord, QWord}. This catches
     legacy CSVs already on the share that were taken before this fix.

2. v6.213 vendor install path not scanned / not restored to
   The per-bay FormTracePak install (commit 54dddaa) lands under
   C:\Program Files (x86)\MitutoyoApp\Formtracepak, but the search-path
   lists in Backup + Export only covered C:\...\Mitutoyo (no MitutoyoApp).
   Result: a backup taken on a freshly imaged v6.213 bay produced Config
   Files = 0 because the script never looked at the actual install dir.
   Added MitutoyoApp (x86 + native ProgramFiles) ahead of the legacy
   paths in all three scripts.

3. Install $DefaultAppTargets fallback didn't include MitutoyoApp either,
   so a restore from an OLDER bay (source path C:\Mitutoyo\...) onto a
   freshly imaged v6.213 bay would fall back to ProgramFiles\Mitutoyo
   (does not exist), miss the MitutoyoApp\Formtracepak tree, and write
   the restored files into the first existing legacy path. Added the
   MitutoyoApp entries at the top of the ordered fallback table.

Smoke tested on win11 VM: backup of all 5 reg types, then corrupt every
value, then Install -RestoreAll restores all 5 byte-exact (incl. REG_BINARY
DE-AD-BE-EF-CA-FE-BA-BE-01-02-03-04 and REG_MULTI_SZ alpha.smp/beta.smp/
gamma.smp). Verified legacy poison-CSV path triggers the defensive filter
and the .reg-imported values survive untouched. -DryRun confirmed
non-mutating. Idempotency confirmed via hash-skip.
This commit is contained in:
cproudlock
2026-05-24 07:29:27 -04:00
parent b57ba0fb6f
commit a104cfdebb
3 changed files with 39 additions and 3 deletions

View File

@@ -84,6 +84,9 @@ $ErrorActionPreference = 'Continue'
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
$ApplicationPaths = @( $ApplicationPaths = @(
# v6.213 vendor MSI layout (the path the per-bay install lands on today).
"${env:ProgramFiles(x86)}\MitutoyoApp"
"${env:ProgramFiles}\MitutoyoApp"
"${env:ProgramFiles}\Mitutoyo" "${env:ProgramFiles}\Mitutoyo"
"${env:ProgramFiles(x86)}\Mitutoyo" "${env:ProgramFiles(x86)}\Mitutoyo"
"${env:ProgramFiles}\FORMTRACEPAK" "${env:ProgramFiles}\FORMTRACEPAK"
@@ -280,15 +283,28 @@ foreach ($root in $RegistryRoots) {
Write-Warning " reg.exe export failed for ${root}: $_" Write-Warning " reg.exe export failed for ${root}: $_"
} }
# Also capture as CSV for granular restore # Also capture as CSV for granular restore.
#
# CSV path only round-trips cleanly for String / ExpandString / DWord /
# QWord. Binary and MultiString lossily coerce through [string] (Binary
# becomes "System.Byte[]" literal; MultiString becomes space-joined),
# which then corrupts the registry if the CSV restore overwrites the
# .reg-import result. Skip those types in the CSV (the .reg file is
# authoritative for them).
$csvSafeTypes = @('String', 'ExpandString', 'DWord', 'QWord')
try { try {
$allKeys = @(Get-Item -Path $root -ErrorAction SilentlyContinue) $allKeys = @(Get-Item -Path $root -ErrorAction SilentlyContinue)
$allKeys += Get-ChildItem -Path $root -Recurse -ErrorAction SilentlyContinue $allKeys += Get-ChildItem -Path $root -Recurse -ErrorAction SilentlyContinue
foreach ($key in $allKeys) { foreach ($key in $allKeys) {
foreach ($valName in $key.GetValueNames()) { foreach ($valName in $key.GetValueNames()) {
$val = $key.GetValue($valName)
$kind = $key.GetValueKind($valName) $kind = $key.GetValueKind($valName)
if ($csvSafeTypes -notcontains [string]$kind) {
# Skip Binary / MultiString / None / Unknown. The .reg
# export already captured them losslessly.
continue
}
$val = $key.GetValue($valName)
$regCsvRows.Add([PSCustomObject]@{ $regCsvRows.Add([PSCustomObject]@{
Path = $key.PSPath Path = $key.PSPath
Name = $valName Name = $valName

View File

@@ -34,6 +34,9 @@ $ErrorActionPreference = 'Continue'
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
$ApplicationSearchPaths = @( $ApplicationSearchPaths = @(
# v6.213 vendor MSI layout (the path the per-bay install lands on today).
"${env:ProgramFiles(x86)}\MitutoyoApp"
"${env:ProgramFiles}\MitutoyoApp"
"${env:ProgramFiles}\Mitutoyo" "${env:ProgramFiles}\Mitutoyo"
"${env:ProgramFiles(x86)}\Mitutoyo" "${env:ProgramFiles(x86)}\Mitutoyo"
"${env:ProgramFiles}\FORMTRACEPAK" "${env:ProgramFiles}\FORMTRACEPAK"

View File

@@ -91,6 +91,11 @@ $ErrorActionPreference = 'Continue'
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
$DefaultAppTargets = [ordered]@{ $DefaultAppTargets = [ordered]@{
# v6.213 vendor MSI installs under MitutoyoApp\Formtracepak\. Order this
# first so restored config under e.g. C:\Mitutoyo\Formtracepak\ from an
# older bay lands in the right tree on a freshly imaged v6.213 bay.
'MitutoyoAppX86' = "${env:ProgramFiles(x86)}\MitutoyoApp"
'MitutoyoApp' = "${env:ProgramFiles}\MitutoyoApp"
'ProgramFiles' = "${env:ProgramFiles}\Mitutoyo" 'ProgramFiles' = "${env:ProgramFiles}\Mitutoyo"
'ProgramFilesX86' = "${env:ProgramFiles(x86)}\Mitutoyo" 'ProgramFilesX86' = "${env:ProgramFiles(x86)}\Mitutoyo"
'CMitutoyo' = 'C:\Mitutoyo' 'CMitutoyo' = 'C:\Mitutoyo'
@@ -347,7 +352,13 @@ if ($RestoreRegistry) {
$counters.RegistryKeys++ $counters.RegistryKeys++
} }
# Method 2: CSV-based key-by-key restore # Method 2: CSV-based key-by-key restore. CSV captures String /
# ExpandString / DWord / QWord only (Backup-FormtracepakSettings.ps1
# skips Binary + MultiString because [string] coercion corrupts them).
# The .reg file import above is authoritative for those types. Defend
# against older CSVs (pre-fix) that carry corrupt Binary/MultiString
# rows by skipping anything that is not a CSV-safe type.
$csvSafeTypes = @('String', 'ExpandString', 'DWord', 'QWord')
$regCsv = Join-Path $regDir 'registry_values.csv' $regCsv = Join-Path $regDir 'registry_values.csv'
if (Test-Path $regCsv) { if (Test-Path $regCsv) {
$regEntries = Import-Csv $regCsv $regEntries = Import-Csv $regCsv
@@ -357,6 +368,12 @@ if ($RestoreRegistry) {
$regValue = $re.Value $regValue = $re.Value
$regType = $re.Type $regType = $re.Type
if ($regType -and ($csvSafeTypes -notcontains $regType)) {
Write-Host " [SKIP] Non-CSV-safe type '$regType' at ${regPath}\$regName (the .reg file import handled it)" -ForegroundColor DarkGray
$counters.Skipped++
continue
}
if ($regPath -match '^HKLM' -and -not $isAdmin -and -not $DryRun) { if ($regPath -match '^HKLM' -and -not $isAdmin -and -not $DryRun) {
Write-Host " [SKIP] HKLM key requires elevation: $regPath\$regName" -ForegroundColor DarkYellow Write-Host " [SKIP] HKLM key requires elevation: $regPath\$regName" -ForegroundColor DarkYellow
$counters.Skipped++ $counters.Skipped++