Backup-FormtracepakSettings: empirically-grounded version + model detection, HKEY_USERS sweep, manifest evidence stamp
Installed FormTracePak v6.213 on the win11 VM (picking FORMTRACER Avant
in the dialogs) and probed the resulting registry / disk layout to find
out what evidence a real FormTracePak install actually carries. Two
empirical findings:
1. ACTIVE MODEL lives at
HKLM:\SOFTWARE\WOW6432Node\Mitutoyo\FORMPAK\Config\device map\DeviceName
(string value). For the AVANT install the value is "FORMTRACER Avant";
for the CV/SV/CS controllers the value contains the matching model id.
The Surfpak\FormMes\MachineInfo\Machine\Machine* subtree lists EVERY
supported machine and is NOT the active selection - the previous
heuristic that scanned uninstall-entry DisplayName picked up bogus
WinUSB driver-package entries from "Mitutoyo Corporation" instead.
2. INSTALLED BINARY VERSION is in Formtracepak.exe VersionInfo:
FileVersion=6.2.0.51, ProductVersion=6.2.0.0. This does NOT match the
Mitutoyo MSI release label ("6.213") that bay-config.csv uses. The
uninstall entry's DisplayVersion is empty. So bay-config.csv stays
canonical for the per-asset marketing version; exe FileVersion is a
concrete cross-check.
Backup rewrites:
- Replace the previous one-shot version detection with evidence reading:
bay-config.csv (asset->version+model), Formtracepak.exe VersionInfo,
device-map\DeviceName. The over-broad uninstall-reg regex is gone.
- Normalize DeviceName ("FORMTRACER Avant" / "CV-4500" / "CV-3200" /
"Contracer" / "Surftest") to bay-config notation (AVANT / CV-4500 / ...).
- Emit BayConfigMatch flag - true when bay-config-predicted model agrees
with the device-map\DeviceName on disk. False = drift, tech rechecks
before restoring to a new bay.
- manifest.json now stamps: AssetNumber, FormtracepakVersion, Model,
BayConfigVersion, BayConfigModel, BayConfigSource, InstalledExeVersion,
InstalledExePath, InstalledDeviceName, InstalledModelNormalized,
BayConfigMatch.
HKEY_USERS sweep:
- Wax/Trace bays log in as a per-site user (lg782713sd at WJ today,
ShopFloor post-SFLD-2.0, other accounts at other sites). The previous
HKCU:\ scan only captured the script's running user. Sweep every
loaded HKEY_USERS hive whose SID matches S-1-5-21-* (real user SIDs)
for Software\Mitutoyo / FORMTRACEPAK / FORMPAK / SURFPAK subkeys,
add them to $RegistryRoots. Username-agnostic - works at any site
without changes.
reg.exe export now also accepts the Registry::HKEY_USERS\<sid>\... PSPath
form by stripping the "Registry::" prefix when building the reg.exe
argument (previously emitted "Invalid key name" errors on HKEY_USERS roots).
Smoke tested against a real v6.213 / FORMTRACER Avant install on win11
VM: bay-config lookup matches, exe FileVersion read, device-map
normalized to AVANT, all four .reg files (HKLM, HKLM-WOW6432Node, HKCU,
HKEY_USERS\<interactive-sid>) exported clean, 0 errors.
Restore-side SID translation (HKEY_USERS\<src-sid>\... -> target user's
SID or HKCU on the new bay) is a follow-up. HKLM tree carries the
critical device-map\DeviceName, controller config, and machine settings;
the HKEY_USERS hive captures per-user UI prefs only.
This commit is contained in:
@@ -149,6 +149,28 @@ $RegistryRoots = @(
|
|||||||
"HKLM:\SOFTWARE\WOW6432Node\FORMPAK"
|
"HKLM:\SOFTWARE\WOW6432Node\FORMPAK"
|
||||||
"HKCU:\SOFTWARE\FORMPAK"
|
"HKCU:\SOFTWARE\FORMPAK"
|
||||||
"HKLM:\SOFTWARE\SURFPAK"
|
"HKLM:\SOFTWARE\SURFPAK"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sweep every loaded user hive under HKEY_USERS for Mitutoyo/FORMTRACEPAK/
|
||||||
|
# FORMPAK/SURFPAK subkeys. Wax/Trace bays run as a domain "Shopfloor" account,
|
||||||
|
# whose HKCU prefs are NOT visible via HKCU:\ when the script runs as
|
||||||
|
# SupportUser / SYSTEM. As long as the Shopfloor user has a logged-in session
|
||||||
|
# (interactive or disconnected) the hive is mounted under HKEY_USERS\<SID>.
|
||||||
|
# Skip the special SIDs (S-1-5-18 = SYSTEM, S-1-5-19/20 = LocalService /
|
||||||
|
# NetworkService) and the *_Classes subhives.
|
||||||
|
$userHiveSubpaths = @('Software\Mitutoyo','Software\FORMTRACEPAK','Software\FORMPAK','Software\SURFPAK')
|
||||||
|
foreach ($u in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
|
||||||
|
$sid = $u.PSChildName
|
||||||
|
if ($sid -notmatch '^S-1-5-21-' ) { continue } # only real user SIDs
|
||||||
|
if ($sid -match '_Classes$') { continue }
|
||||||
|
foreach ($sp in $userHiveSubpaths) {
|
||||||
|
$candidate = "Registry::HKEY_USERS\$sid\$sp"
|
||||||
|
if (Test-Path -LiteralPath $candidate) {
|
||||||
|
$RegistryRoots += $candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$RegistryRoots = $RegistryRoots + @(
|
||||||
"HKCU:\SOFTWARE\SURFPAK"
|
"HKCU:\SOFTWARE\SURFPAK"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -304,8 +326,13 @@ foreach ($root in $RegistryRoots) {
|
|||||||
$safeName = ($root -replace '[:\\]', '_').Trim('_')
|
$safeName = ($root -replace '[:\\]', '_').Trim('_')
|
||||||
$regFilePath = Join-Path $regStage "${safeName}.reg"
|
$regFilePath = Join-Path $regStage "${safeName}.reg"
|
||||||
|
|
||||||
$nativeRoot = $root -replace '^HKLM:', 'HKEY_LOCAL_MACHINE' `
|
# Normalize the PSPath-style root into a form reg.exe accepts.
|
||||||
-replace '^HKCU:', 'HKEY_CURRENT_USER'
|
# HKLM:\... -> HKEY_LOCAL_MACHINE\...
|
||||||
|
# HKCU:\... -> HKEY_CURRENT_USER\...
|
||||||
|
# Registry::HKEY_USERS\<sid>\... -> HKEY_USERS\<sid>\...
|
||||||
|
$nativeRoot = $root -replace '^HKLM:\\', 'HKEY_LOCAL_MACHINE\' `
|
||||||
|
-replace '^HKCU:\\', 'HKEY_CURRENT_USER\' `
|
||||||
|
-replace '^Registry::', ''
|
||||||
try {
|
try {
|
||||||
$proc = Start-Process -FilePath 'reg.exe' `
|
$proc = Start-Process -FilePath 'reg.exe' `
|
||||||
-ArgumentList "export `"$nativeRoot`" `"$regFilePath`" /y" `
|
-ArgumentList "export `"$nativeRoot`" `"$regFilePath`" /y" `
|
||||||
@@ -359,47 +386,154 @@ if ($regCsvRows.Count -gt 0) {
|
|||||||
-NoTypeInformation -Encoding UTF8
|
-NoTypeInformation -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------- 5. Detect installed version ----------------------------------------------------------------------
|
# ---------------------------------------------------------------------- 5. Detect installed version + model ----------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Evidence sources, established empirically by installing FormTracePak v6.213
|
||||||
|
# on the win11 VM and probing the resulting registry / disk layout
|
||||||
|
# (2026-05-24):
|
||||||
|
#
|
||||||
|
# - bay-config.csv: authoritative for the per-asset marketing version
|
||||||
|
# ("6.213", "6.103", ...) AND model ("AVANT", "CV-4500", "CV-3200").
|
||||||
|
# This is the source of truth for the 15 known wax/trace bays.
|
||||||
|
#
|
||||||
|
# - Formtracepak.exe VersionInfo (FileVersion / ProductVersion): the
|
||||||
|
# binary version on disk (e.g. 6.2.0.51). Does NOT match the
|
||||||
|
# bay-config marketing format directly (Mitutoyo numbers their
|
||||||
|
# binary releases differently from their MSI release labels), but is
|
||||||
|
# concrete on-disk evidence the install is real.
|
||||||
|
#
|
||||||
|
# - HKLM:\SOFTWARE\WOW6432Node\Mitutoyo\FORMPAK\Config\device map\DeviceName:
|
||||||
|
# the active controller / model picked at install time. Real values
|
||||||
|
# include "FORMTRACER Avant", "CV-4500", "CV-3200", "Contracer CV-3200".
|
||||||
|
# The Mitutoyo "Surfpak\FormMes\MachineInfo\Machine\Machine*" rows are
|
||||||
|
# installer templates listing every supported machine; ignore them.
|
||||||
|
#
|
||||||
|
# - Mitutoyo uninstall registry entries: useless for version. The
|
||||||
|
# "Formtracepak" uninstall entry has DisplayVersion = '' (empty),
|
||||||
|
# and the WinUSB driver-package entries falsely match a broad
|
||||||
|
# "Mitutoyo" regex with bogus dates like "01/26/2014 1.0.0.0".
|
||||||
|
# Not consulted.
|
||||||
|
#
|
||||||
|
# manifest.json now records both the bay-config-predicted values and the
|
||||||
|
# on-disk evidence, plus a Match flag so any drift between what's
|
||||||
|
# supposed to be installed and what actually is gets surfaced at restore
|
||||||
|
# time without needing a separate audit.
|
||||||
|
|
||||||
$detectedVersion = 'Unknown'
|
# --- bay-config.csv predicted values ---
|
||||||
$uninstallPaths = @(
|
$bayConfigVersion = $null
|
||||||
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
|
$bayConfigModel = $null
|
||||||
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall',
|
$bayConfigSource = $null
|
||||||
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
|
if ($AssetNumber) {
|
||||||
)
|
$bayCfg = @(
|
||||||
foreach ($uPath in $uninstallPaths) {
|
(Join-Path $scriptDir 'bay-config.csv'),
|
||||||
if (-not (Test-Path $uPath)) { continue }
|
'C:\WaxTrace-Install\bay-config.csv',
|
||||||
$entries = Get-ChildItem -Path $uPath -ErrorAction SilentlyContinue
|
'Y:\installers-post\waxtrace\bay-config.csv'
|
||||||
foreach ($entry in $entries) {
|
) | Where-Object { $_ -and (Test-Path -LiteralPath $_) } | Select-Object -First 1
|
||||||
|
if ($bayCfg) {
|
||||||
try {
|
try {
|
||||||
$props = Get-ItemProperty -Path $entry.PSPath -ErrorAction SilentlyContinue
|
$row = Import-Csv -LiteralPath $bayCfg |
|
||||||
if ($props.DisplayName -and $props.DisplayName -match 'FORMTRACEPAK|FormTrace') {
|
Where-Object { $_.asset_tag -ieq $AssetNumber } | Select-Object -First 1
|
||||||
$detectedVersion = $props.DisplayVersion
|
if ($row) {
|
||||||
break
|
$bayConfigVersion = $row.ftpak_version
|
||||||
|
$bayConfigModel = $row.model
|
||||||
|
$bayConfigSource = $bayCfg
|
||||||
}
|
}
|
||||||
} catch { }
|
} catch { }
|
||||||
}
|
}
|
||||||
if ($detectedVersion -ne 'Unknown') { break }
|
}
|
||||||
|
|
||||||
|
# --- Formtracepak.exe FileVersion ---
|
||||||
|
$installedExeVersion = $null
|
||||||
|
$installedExePath = $null
|
||||||
|
$exeCandidates = @(
|
||||||
|
"${env:ProgramFiles(x86)}\MitutoyoApp\Formtracepak\Formtracepak.exe"
|
||||||
|
"${env:ProgramFiles}\MitutoyoApp\Formtracepak\Formtracepak.exe"
|
||||||
|
"${env:ProgramFiles(x86)}\Mitutoyo\Formtracepak\Formtracepak.exe"
|
||||||
|
"${env:ProgramFiles}\Mitutoyo\Formtracepak\Formtracepak.exe"
|
||||||
|
'C:\Mitutoyo\Formtracepak\Formtracepak.exe'
|
||||||
|
'D:\Mitutoyo\Formtracepak\Formtracepak.exe'
|
||||||
|
)
|
||||||
|
foreach ($exe in $exeCandidates) {
|
||||||
|
if (Test-Path -LiteralPath $exe) {
|
||||||
|
try {
|
||||||
|
$vi = (Get-Item -LiteralPath $exe).VersionInfo
|
||||||
|
$installedExeVersion = if ($vi.FileVersion) { $vi.FileVersion.Trim() } elseif ($vi.ProductVersion) { $vi.ProductVersion.Trim() } else { $null }
|
||||||
|
$installedExePath = $exe
|
||||||
|
break
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Active device-map DeviceName + normalized model ---
|
||||||
|
$installedDeviceName = $null
|
||||||
|
$installedModelNormalized = $null
|
||||||
|
$deviceMapKey = 'HKLM:\SOFTWARE\WOW6432Node\Mitutoyo\FORMPAK\Config\device map'
|
||||||
|
if (Test-Path -LiteralPath $deviceMapKey) {
|
||||||
|
try {
|
||||||
|
$dm = Get-ItemProperty -LiteralPath $deviceMapKey -ErrorAction Stop
|
||||||
|
if ($dm.PSObject.Properties['DeviceName']) {
|
||||||
|
$installedDeviceName = [string]$dm.DeviceName
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
# Normalize DeviceName to bay-config.csv notation (AVANT / CV-4500 / CV-3200 / ...).
|
||||||
|
if ($installedDeviceName) {
|
||||||
|
switch -Regex ($installedDeviceName) {
|
||||||
|
'(?i)FORMTRACER\s*Avant|^AVANT$' { $installedModelNormalized = 'AVANT'; break }
|
||||||
|
'(?i)CV-?4500' { $installedModelNormalized = 'CV-4500'; break }
|
||||||
|
'(?i)CV-?3200' { $installedModelNormalized = 'CV-3200'; break }
|
||||||
|
'(?i)CV-?3100' { $installedModelNormalized = 'CV-3100'; break }
|
||||||
|
'(?i)CV-?4100' { $installedModelNormalized = 'CV-4100'; break }
|
||||||
|
'(?i)CV-?4200' { $installedModelNormalized = 'CV-4200'; break }
|
||||||
|
'(?i)Contracer|^CS-' { $installedModelNormalized = 'Contracer'; break }
|
||||||
|
'(?i)Surftest|^SV-' { $installedModelNormalized = 'Surftest'; break }
|
||||||
|
default { $installedModelNormalized = $installedDeviceName }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Reconcile + log ---
|
||||||
|
$bayConfigMatch = $true
|
||||||
|
if ($bayConfigModel -and $installedModelNormalized -and ($bayConfigModel -ine $installedModelNormalized)) {
|
||||||
|
$bayConfigMatch = $false
|
||||||
|
}
|
||||||
|
$detectedVersion = if ($bayConfigVersion) { $bayConfigVersion } elseif ($installedExeVersion) { $installedExeVersion } else { 'Unknown' }
|
||||||
|
$detectedModel = if ($bayConfigModel) { $bayConfigModel } elseif ($installedModelNormalized) { $installedModelNormalized } else { 'Unknown' }
|
||||||
|
|
||||||
|
Write-Host "Bay config lookup : $(if ($bayConfigSource) { "$bayConfigSource (asset='$AssetNumber' -> ver='$bayConfigVersion' model='$bayConfigModel')" } else { 'asset not in any bay-config.csv' })"
|
||||||
|
Write-Host "Installed exe : $(if ($installedExePath) { "$installedExePath FileVersion=$installedExeVersion" } else { 'no Formtracepak.exe found on disk' })"
|
||||||
|
Write-Host "Installed device-map : $(if ($installedDeviceName) { "DeviceName='$installedDeviceName' -> normalized '$installedModelNormalized'" } else { "$deviceMapKey not present" })"
|
||||||
|
if (-not $bayConfigMatch) {
|
||||||
|
Write-Warning "Bay-config says model='$bayConfigModel' but device-map says '$installedModelNormalized' - DRIFT (recapture the bay-config.csv or the live install before restoring to a new bay)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------- Write manifest ----------------------------------------------------------------------
|
# ---------------------------------------------------------------------- Write manifest ----------------------------------------------------------------------
|
||||||
Write-Host "[4/4] Writing manifest..." -ForegroundColor Cyan
|
Write-Host "[4/4] Writing manifest..." -ForegroundColor Cyan
|
||||||
|
|
||||||
$manifest = [PSCustomObject]@{
|
$manifest = [PSCustomObject]@{
|
||||||
SourceComputer = $env:COMPUTERNAME
|
SourceComputer = $env:COMPUTERNAME
|
||||||
BackupTimestamp = (Get-Date -Format 'o')
|
AssetNumber = $AssetNumber
|
||||||
FormtracepakVersion = $detectedVersion
|
BackupTimestamp = (Get-Date -Format 'o')
|
||||||
OSVersion = [System.Environment]::OSVersion.VersionString
|
FormtracepakVersion = $detectedVersion
|
||||||
PowerShellVersion = $PSVersionTable.PSVersion.ToString()
|
Model = $detectedModel
|
||||||
IncludesConfig = ($counters.Config -gt 0)
|
BayConfigVersion = $bayConfigVersion
|
||||||
IncludesData = ($counters.Data -gt 0)
|
BayConfigModel = $bayConfigModel
|
||||||
IncludesBinaries = ($counters.Binaries -gt 0)
|
BayConfigSource = $bayConfigSource
|
||||||
IncludesRegistry = ($counters.RegKeys -gt 0)
|
InstalledExeVersion = $installedExeVersion
|
||||||
ConfigFileCount = $counters.Config
|
InstalledExePath = $installedExePath
|
||||||
DataFileCount = $counters.Data
|
InstalledDeviceName = $installedDeviceName
|
||||||
BinaryFileCount = $counters.Binaries
|
InstalledModelNormalized= $installedModelNormalized
|
||||||
RegistryValueCount = $counters.RegKeys
|
BayConfigMatch = $bayConfigMatch
|
||||||
ErrorCount = $counters.Errors
|
OSVersion = [System.Environment]::OSVersion.VersionString
|
||||||
|
PowerShellVersion = $PSVersionTable.PSVersion.ToString()
|
||||||
|
IncludesConfig = ($counters.Config -gt 0)
|
||||||
|
IncludesData = ($counters.Data -gt 0)
|
||||||
|
IncludesBinaries = ($counters.Binaries -gt 0)
|
||||||
|
IncludesRegistry = ($counters.RegKeys -gt 0)
|
||||||
|
ConfigFileCount = $counters.Config
|
||||||
|
DataFileCount = $counters.Data
|
||||||
|
BinaryFileCount = $counters.Binaries
|
||||||
|
RegistryValueCount = $counters.RegKeys
|
||||||
|
ErrorCount = $counters.Errors
|
||||||
}
|
}
|
||||||
|
|
||||||
$manifest | ConvertTo-Json -Depth 3 |
|
$manifest | ConvertTo-Json -Depth 3 |
|
||||||
|
|||||||
Reference in New Issue
Block a user