gea-shopfloor-waxtrace: stage Mitutoyo Backup/Install/Export triad

User received three PowerShell scripts from a Mitutoyo source for
backing up + restoring FormTracePak settings per asset:

  Export-FormtracepakInventory.ps1 - audit: enumerate files + reg keys
  Backup-FormtracepakSettings.ps1  - capture: config + data + reg into
                                     timestamped ZIP, manifest-driven
  Install-FormtracepakSettings.ps1 - restore: replay ZIP to a new bay,
                                     hash-skip identicals, backup
                                     existing as .pre_restore_bak

Cleanup pass over the vendor-shipped versions:
- Strip Unicode box-drawing characters from banners (ASCII-only policy)
- Install: switch to [ordered]@{} for DefaultAppTargets/DefaultDataTargets
  so fallback priority is deterministic
- Install: add -AssetNumber gate that defaults to per-asset SFLD path
  \\tsgwp00525...\Shopfloor\backup\formtracepac\<AssetNumber>
- Install: timestamp the .pre_restore_bak filename so re-runs don't
  clobber the previous backup
- Install: handle BackupPath being a directory containing
  formtracepak_backup_*.zip files (picks newest)
- .bat launchers for each PS1 (bypass execution policy, double-click)

Not yet wired into 09-Setup-WaxAndTrace.ps1; pending reference-backup
capture from a known-good bay before promoting to imaging path. Today
the V6.213 vendor MSI install + per-asset cal ISO still handle the
imaging-time setup directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-05-21 19:39:24 -04:00
parent 02499cf74b
commit 44554b95b0
6 changed files with 1102 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
@echo off
REM Bypass launcher for Backup-FormtracepakSettings.ps1.
REM Forwards any args to the PS1 (e.g. -AssetNumber WJRP2335 -IncludeExecutables).
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0Backup-FormtracepakSettings.ps1" %*
exit /b %ERRORLEVEL%

View File

@@ -0,0 +1,395 @@
<#
.SYNOPSIS
Backs up all Mitutoyo Formtracepak settings, data, and registry to a ZIP archive.
.DESCRIPTION
Collects application config files, user data (part programs, layouts, measurements,
design values, calibration records), and registry keys from a Formtracepak host and
packages them into a portable ZIP file suitable for network storage or USB transfer.
The backup includes a manifest.json and per-folder file_manifest.csv files that
Install-FormtracepakSettings.ps1 uses to restore files to their original locations.
Supports Formtracepak v6.1 through current, FORMPAK-1000, SURFPAK-SV, DUAL TRACEPAK.
.PARAMETER AssetNumber
Asset number (e.g. WJRP1234) used to namespace the backup on the shopfloor
share. If omitted, the script prompts interactively and defaults to the
current $env:COMPUTERNAME. The asset number is the leaf folder under
\\tsgwp00525.wjs.geaerospace.net\shared\DT\Shopfloor\backup\formtracepac\
Ignored if -Destination is supplied.
.PARAMETER Destination
Where to write the ZIP file. Can be a USB drive, network share, or local path.
If omitted, defaults to the per-asset path on the GE shopfloor share:
\\tsgwp00525.wjs.geaerospace.net\shared\DT\Shopfloor\backup\formtracepac\<AssetNumber>
The destination directory is created if it does not exist.
.PARAMETER NoZip
Keep the backup as an uncompressed folder instead of creating a ZIP archive.
.PARAMETER IncludeExecutables
Also back up EXE and DLL files from the application directories. Off by default
since the application should be reinstalled via installer on the new host.
.EXAMPLE
.\Backup-FormtracepakSettings.ps1
.\Backup-FormtracepakSettings.ps1 -AssetNumber WJRP2335
.\Backup-FormtracepakSettings.ps1 -Destination "F:\mitutoyo_backups" -IncludeExecutables
#>
[CmdletBinding()]
param(
[string]$AssetNumber,
[string]$Destination,
[switch]$NoZip,
[switch]$IncludeExecutables
)
$SharedRoot = '\\tsgwp00525.wjs.geaerospace.net\shared\DT\Shopfloor\backup\formtracepac'
if (-not $Destination) {
if (-not $AssetNumber) {
$defaultAsset = $env:COMPUTERNAME
if ([Environment]::UserInteractive -and -not [Console]::IsInputRedirected) {
$input = Read-Host "Asset number for this backup (e.g. WJRP1234) [$defaultAsset]"
if ([string]::IsNullOrWhiteSpace($input)) {
$AssetNumber = $defaultAsset
} else {
$AssetNumber = $input.Trim()
}
} else {
# Non-interactive (qga / SYSTEM / scheduled task): fall back to COMPUTERNAME silently.
$AssetNumber = $defaultAsset
Write-Host "Non-interactive session - defaulting AssetNumber to $AssetNumber. Pass -AssetNumber to override."
}
}
$Destination = Join-Path $SharedRoot $AssetNumber
}
if (-not (Test-Path -LiteralPath $Destination)) {
try {
New-Item -Path $Destination -ItemType Directory -Force -ErrorAction Stop | Out-Null
Write-Host "Created destination: $Destination"
} catch {
Write-Error "Destination not reachable / not creatable: $Destination`n$_"
exit 1
}
}
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Continue'
# ----------------------------------------------------------------------
# CONFIGURABLE: Directories to scan. Add or remove to match your environment.
# ----------------------------------------------------------------------
$ApplicationPaths = @(
"${env:ProgramFiles}\Mitutoyo"
"${env:ProgramFiles(x86)}\Mitutoyo"
"${env:ProgramFiles}\FORMTRACEPAK"
"${env:ProgramFiles(x86)}\FORMTRACEPAK"
"${env:ProgramFiles}\Formtracepak"
"${env:ProgramFiles(x86)}\Formtracepak"
"C:\Mitutoyo"
"D:\Mitutoyo"
"C:\FORMTRACEPAK"
"D:\FORMTRACEPAK"
"C:\FORMPAK"
"C:\SURFPAK"
"C:\MtFormTracer"
"C:\MtSurfMeas"
)
$UserDataPaths = @(
"${env:APPDATA}\Mitutoyo"
"${env:LOCALAPPDATA}\Mitutoyo"
"${env:ProgramData}\Mitutoyo"
"${env:APPDATA}\FORMTRACEPAK"
"${env:LOCALAPPDATA}\FORMTRACEPAK"
"${env:ProgramData}\FORMTRACEPAK"
"${env:USERPROFILE}\Documents\Mitutoyo"
"${env:USERPROFILE}\Documents\FORMTRACEPAK"
"${env:USERPROFILE}\Documents\FORMPAK"
"${env:USERPROFILE}\Documents\SURFPAK"
"${env:PUBLIC}\Documents\Mitutoyo"
"${env:PUBLIC}\Documents\FORMTRACEPAK"
)
$RegistryRoots = @(
"HKLM:\SOFTWARE\Mitutoyo"
"HKLM:\SOFTWARE\WOW6432Node\Mitutoyo"
"HKCU:\SOFTWARE\Mitutoyo"
"HKLM:\SOFTWARE\FORMTRACEPAK"
"HKLM:\SOFTWARE\WOW6432Node\FORMTRACEPAK"
"HKCU:\SOFTWARE\FORMTRACEPAK"
"HKLM:\SOFTWARE\FORMPAK"
"HKLM:\SOFTWARE\WOW6432Node\FORMPAK"
"HKCU:\SOFTWARE\FORMPAK"
"HKLM:\SOFTWARE\SURFPAK"
"HKCU:\SOFTWARE\SURFPAK"
)
# Config file patterns (always backed up)
$ConfigExtensions = @('*.ini', '*.cfg', '*.xml', '*.json', '*.config', '*.settings', '*.prefs')
# Data file patterns (part programs, measurements, layouts, design data)
$DataExtensions = @(
'*.smp', '*.prn', '*.dat', '*.csv', '*.txt',
'*.tpl', '*.ppg', '*.ppt', '*.prg',
'*.fta', '*.srf', '*.ctr', '*.ctt',
'*.dxf', '*.igs', '*.iges',
'*.rpt', '*.lay', '*.lyt',
'*.html', '*.mhtml',
'*.cal', '*.stl', '*.ref',
'*.bmp', '*.jpg', '*.png'
)
$BinaryExtensions = @('*.exe', '*.dll')
# ----------------------------------------------------------------------
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$backupName = "formtracepak_backup_${env:COMPUTERNAME}_${timestamp}"
$stagingDir = Join-Path $env:TEMP $backupName
$configStage = Join-Path $stagingDir 'config'
$dataStage = Join-Path $stagingDir 'data'
$regStage = Join-Path $stagingDir 'registry'
$binStage = Join-Path $stagingDir 'binaries'
New-Item -ItemType Directory -Path $configStage -Force | Out-Null
New-Item -ItemType Directory -Path $dataStage -Force | Out-Null
New-Item -ItemType Directory -Path $regStage -Force | Out-Null
if ($IncludeExecutables) {
New-Item -ItemType Directory -Path $binStage -Force | Out-Null
}
$counters = @{ Config = 0; Data = 0; Binaries = 0; RegKeys = 0; Errors = 0 }
Write-Host "`n----------------------------------------------------------------------" -ForegroundColor Yellow
Write-Host " FORMTRACEPAK Settings Backup" -ForegroundColor Yellow
Write-Host " Host: $env:COMPUTERNAME" -ForegroundColor Yellow
Write-Host "----------------------------------------------------------------------`n" -ForegroundColor Yellow
# ---------------------------------------------------------------------- Helper: collect files into staging ----------------------------------------------------------------------
function Copy-ToStaging {
param(
[string]$SourceRoot,
[string]$StageRoot,
[string[]]$Patterns,
[string]$CounterKey
)
$manifestRows = [System.Collections.Generic.List[PSObject]]::new()
foreach ($pattern in $Patterns) {
$files = Get-ChildItem -Path $SourceRoot -Filter $pattern -Recurse -File -ErrorAction SilentlyContinue
foreach ($f in $files) {
$relativePath = $f.FullName.Substring($SourceRoot.Length).TrimStart('\', '/')
$safeRelBase = ($SourceRoot -replace '[:\\]', '_').Trim('_')
$destRelPath = Join-Path $safeRelBase $relativePath
$destFull = Join-Path $StageRoot $destRelPath
$destDir = Split-Path $destFull -Parent
try {
if (-not (Test-Path $destDir)) {
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
}
Copy-Item -Path $f.FullName -Destination $destFull -Force
$counters[$CounterKey]++
$manifestRows.Add([PSCustomObject]@{
RelativePath = $destRelPath
OriginalPath = $f.FullName
SizeBytes = $f.Length
LastModified = $f.LastWriteTime.ToString('o')
Hash = (Get-FileHash $f.FullName -Algorithm MD5 -ErrorAction SilentlyContinue).Hash
})
} catch {
Write-Warning " Failed to copy $($f.FullName): $_"
$counters.Errors++
}
}
}
if ($manifestRows.Count -gt 0) {
$manifestFile = Join-Path $StageRoot 'file_manifest.csv'
if (Test-Path $manifestFile) {
$manifestRows | Export-Csv -Path $manifestFile -NoTypeInformation -Encoding UTF8 -Append
} else {
$manifestRows | Export-Csv -Path $manifestFile -NoTypeInformation -Encoding UTF8
}
}
}
# ---------------------------------------------------------------------- 1. Backup Config Files ----------------------------------------------------------------------
Write-Host "[1/4] Backing up configuration files..." -ForegroundColor Cyan
foreach ($path in ($ApplicationPaths + $UserDataPaths)) {
if (-not (Test-Path $path)) { continue }
Write-Host " Scanning: $path" -ForegroundColor Green
Copy-ToStaging -SourceRoot $path -StageRoot $configStage `
-Patterns $ConfigExtensions -CounterKey 'Config'
}
# ---------------------------------------------------------------------- 2. Backup Data Files ----------------------------------------------------------------------
Write-Host "[2/4] Backing up data files (part programs, layouts, measurements)..." -ForegroundColor Cyan
foreach ($path in ($ApplicationPaths + $UserDataPaths)) {
if (-not (Test-Path $path)) { continue }
Write-Host " Scanning: $path" -ForegroundColor Green
Copy-ToStaging -SourceRoot $path -StageRoot $dataStage `
-Patterns $DataExtensions -CounterKey 'Data'
}
# ---------------------------------------------------------------------- 3. Backup Executables (optional) ----------------------------------------------------------------------
if ($IncludeExecutables) {
Write-Host "[2b/4] Backing up binaries (EXE/DLL)..." -ForegroundColor Cyan
foreach ($path in $ApplicationPaths) {
if (-not (Test-Path $path)) { continue }
Write-Host " Scanning: $path" -ForegroundColor Green
Copy-ToStaging -SourceRoot $path -StageRoot $binStage `
-Patterns $BinaryExtensions -CounterKey 'Binaries'
}
}
# ---------------------------------------------------------------------- 4. Backup Registry ----------------------------------------------------------------------
Write-Host "[3/4] Backing up registry keys..." -ForegroundColor Cyan
$regCsvRows = [System.Collections.Generic.List[PSObject]]::new()
foreach ($root in $RegistryRoots) {
if (-not (Test-Path $root)) { continue }
Write-Host " Found: $root" -ForegroundColor Green
# Export as .reg file for easy manual import
$safeName = ($root -replace '[:\\]', '_').Trim('_')
$regFilePath = Join-Path $regStage "${safeName}.reg"
$nativeRoot = $root -replace '^HKLM:', 'HKEY_LOCAL_MACHINE' `
-replace '^HKCU:', 'HKEY_CURRENT_USER'
try {
$proc = Start-Process -FilePath 'reg.exe' `
-ArgumentList "export `"$nativeRoot`" `"$regFilePath`" /y" `
-Wait -PassThru -NoNewWindow -ErrorAction SilentlyContinue
if ($proc.ExitCode -eq 0) {
Write-Host " Exported: $regFilePath" -ForegroundColor Green
}
} catch {
Write-Warning " reg.exe export failed for ${root}: $_"
}
# Also capture as CSV for granular restore
try {
$allKeys = @(Get-Item -Path $root -ErrorAction SilentlyContinue)
$allKeys += Get-ChildItem -Path $root -Recurse -ErrorAction SilentlyContinue
foreach ($key in $allKeys) {
foreach ($valName in $key.GetValueNames()) {
$val = $key.GetValue($valName)
$kind = $key.GetValueKind($valName)
$regCsvRows.Add([PSCustomObject]@{
Path = $key.PSPath
Name = $valName
Value = [string]$val
Type = [string]$kind
})
$counters.RegKeys++
}
}
} catch {
Write-Warning " Registry read error at ${root}: $_"
$counters.Errors++
}
}
if ($regCsvRows.Count -gt 0) {
$regCsvRows | Export-Csv -Path (Join-Path $regStage 'registry_values.csv') `
-NoTypeInformation -Encoding UTF8
}
# ---------------------------------------------------------------------- 5. Detect installed version ----------------------------------------------------------------------
$detectedVersion = 'Unknown'
$uninstallPaths = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
)
foreach ($uPath in $uninstallPaths) {
if (-not (Test-Path $uPath)) { continue }
$entries = Get-ChildItem -Path $uPath -ErrorAction SilentlyContinue
foreach ($entry in $entries) {
try {
$props = Get-ItemProperty -Path $entry.PSPath -ErrorAction SilentlyContinue
if ($props.DisplayName -and $props.DisplayName -match 'FORMTRACEPAK|FormTrace') {
$detectedVersion = $props.DisplayVersion
break
}
} catch { }
}
if ($detectedVersion -ne 'Unknown') { break }
}
# ---------------------------------------------------------------------- Write manifest ----------------------------------------------------------------------
Write-Host "[4/4] Writing manifest..." -ForegroundColor Cyan
$manifest = [PSCustomObject]@{
SourceComputer = $env:COMPUTERNAME
BackupTimestamp = (Get-Date -Format 'o')
FormtracepakVersion = $detectedVersion
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 |
Set-Content -Path (Join-Path $stagingDir 'manifest.json') -Encoding UTF8
# ---------------------------------------------------------------------- Create ZIP or copy folder ----------------------------------------------------------------------
if (-not (Test-Path $Destination)) {
New-Item -ItemType Directory -Path $Destination -Force | Out-Null
}
if ($NoZip) {
$finalPath = Join-Path $Destination $backupName
Copy-Item -Path $stagingDir -Destination $finalPath -Recurse -Force
Write-Host "`n Backup folder: $finalPath" -ForegroundColor Green
} else {
$zipPath = Join-Path $Destination "${backupName}.zip"
Compress-Archive -Path "$stagingDir\*" -DestinationPath $zipPath -Force
Write-Host "`n Backup archive: $zipPath" -ForegroundColor Green
}
# Cleanup staging
Remove-Item -Path $stagingDir -Recurse -Force -ErrorAction SilentlyContinue
# ---------------------------------------------------------------------- Summary ----------------------------------------------------------------------
Write-Host "`n----------------------------------------------------------------------" -ForegroundColor Yellow
Write-Host " Backup Summary" -ForegroundColor Yellow
Write-Host "----------------------------------------------------------------------" -ForegroundColor Yellow
Write-Host (" Config Files : {0}" -f $counters.Config) -ForegroundColor White
Write-Host (" Data Files : {0}" -f $counters.Data) -ForegroundColor White
Write-Host (" Binaries : {0}" -f $counters.Binaries) -ForegroundColor White
Write-Host (" Registry Values : {0}" -f $counters.RegKeys) -ForegroundColor White
Write-Host (" Errors : {0}" -f $counters.Errors) -ForegroundColor $(if ($counters.Errors -gt 0) { 'Red' } else { 'White' })
Write-Host (" Detected Version : {0}" -f $detectedVersion) -ForegroundColor White
Write-Host "----------------------------------------------------------------------" -ForegroundColor Yellow
if ($counters.Config -eq 0 -and $counters.Data -eq 0 -and $counters.RegKeys -eq 0) {
Write-Warning "`n No Formtracepak artifacts found. Verify the search paths in the script"
Write-Warning " match your installation, or add custom paths to the configuration section."
}
Write-Host "`n REMINDER: Stylus calibration data lives on the probe hardware." -ForegroundColor Magenta
Write-Host " Re-calibrate after restoring to a new host.`n" -ForegroundColor Magenta

View File

@@ -0,0 +1,5 @@
@echo off
REM Bypass launcher for Export-FormtracepakInventory.ps1.
REM Forwards any args to the PS1 (e.g. -OutputPath E:\inventories).
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0Export-FormtracepakInventory.ps1" %*
exit /b %ERRORLEVEL%

View File

@@ -0,0 +1,273 @@
<#
.SYNOPSIS
Inventories Mitutoyo Formtracepak installation artifacts on a Windows host.
.DESCRIPTION
Scans known and configurable paths for Formtracepak v6.1+ application files,
registry keys, part programs, calibration data, layout templates, design values,
and measurement data. Produces a CSV inventory and console summary.
Supports FORMTRACEPAK, FORMPAK-1000, SURFPAK-SV, and DUAL TRACEPAK components.
.PARAMETER OutputPath
Directory where the inventory CSV will be written. Defaults to script directory.
.PARAMETER ComputerName
Hostname to stamp on the inventory rows. Defaults to $env:COMPUTERNAME.
.EXAMPLE
.\Export-FormtracepakInventory.ps1
.\Export-FormtracepakInventory.ps1 -OutputPath "E:\inventories"
#>
[CmdletBinding()]
param(
[string]$OutputPath = $PSScriptRoot,
[string]$ComputerName = $env:COMPUTERNAME
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Continue'
# ----------------------------------------------------------------------
# CONFIGURABLE: Add or remove paths to match your environment.
# The script scans every path that exists on the target machine.
# ----------------------------------------------------------------------
$ApplicationSearchPaths = @(
"${env:ProgramFiles}\Mitutoyo"
"${env:ProgramFiles(x86)}\Mitutoyo"
"${env:ProgramFiles}\FORMTRACEPAK"
"${env:ProgramFiles(x86)}\FORMTRACEPAK"
"${env:ProgramFiles}\Formtracepak"
"${env:ProgramFiles(x86)}\Formtracepak"
"C:\Mitutoyo"
"D:\Mitutoyo"
"C:\FORMTRACEPAK"
"D:\FORMTRACEPAK"
"C:\FORMPAK"
"C:\SURFPAK"
"C:\MtFormTracer"
"C:\MtSurfMeas"
)
$UserDataSearchPaths = @(
"${env:APPDATA}\Mitutoyo"
"${env:LOCALAPPDATA}\Mitutoyo"
"${env:ProgramData}\Mitutoyo"
"${env:APPDATA}\FORMTRACEPAK"
"${env:LOCALAPPDATA}\FORMTRACEPAK"
"${env:ProgramData}\FORMTRACEPAK"
"${env:USERPROFILE}\Documents\Mitutoyo"
"${env:USERPROFILE}\Documents\FORMTRACEPAK"
"${env:USERPROFILE}\Documents\FORMPAK"
"${env:USERPROFILE}\Documents\SURFPAK"
"${env:PUBLIC}\Documents\Mitutoyo"
"${env:PUBLIC}\Documents\FORMTRACEPAK"
)
$RegistrySearchRoots = @(
"HKLM:\SOFTWARE\Mitutoyo"
"HKLM:\SOFTWARE\WOW6432Node\Mitutoyo"
"HKCU:\SOFTWARE\Mitutoyo"
"HKLM:\SOFTWARE\FORMTRACEPAK"
"HKLM:\SOFTWARE\WOW6432Node\FORMTRACEPAK"
"HKCU:\SOFTWARE\FORMTRACEPAK"
"HKLM:\SOFTWARE\FORMPAK"
"HKLM:\SOFTWARE\WOW6432Node\FORMPAK"
"HKCU:\SOFTWARE\FORMPAK"
"HKLM:\SOFTWARE\SURFPAK"
"HKCU:\SOFTWARE\SURFPAK"
)
# File extensions associated with Formtracepak / Mitutoyo form-measurement software
$MitutoyoFileExtensions = @(
'*.smp', '*.prn', '*.dat', '*.csv', '*.txt',
'*.tpl', '*.ppg', '*.ppt', '*.prg',
'*.fta', '*.srf', '*.ctr', '*.ctt',
'*.dxf', '*.igs', '*.iges',
'*.rpt', '*.lay', '*.lyt', '*.html', '*.mhtml',
'*.ini', '*.cfg', '*.xml', '*.json', '*.config',
'*.cal', '*.stl', '*.ref',
'*.bmp', '*.jpg', '*.png',
'*.exe', '*.dll'
)
# ----------------------------------------------------------------------
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$csvFile = Join-Path $OutputPath "formtracepak_inventory_${ComputerName}_${timestamp}.csv"
$inventory = [System.Collections.Generic.List[PSObject]]::new()
function Add-Item {
param(
[string]$Category,
[string]$ItemType,
[string]$Path,
[string]$Name,
[string]$Value,
[long]$SizeBytes = 0
)
$inventory.Add([PSCustomObject]@{
ComputerName = $ComputerName
Category = $Category
ItemType = $ItemType
Path = $Path
Name = $Name
Value = $Value
SizeBytes = $SizeBytes
ScanTime = (Get-Date -Format 'o')
})
}
# ---------------------------------------------------------------------- 1. Scan Application Directories ----------------------------------------------------------------------
Write-Host "`n[1/5] Scanning application directories..." -ForegroundColor Cyan
foreach ($searchPath in $ApplicationSearchPaths) {
if (-not (Test-Path $searchPath)) { continue }
Write-Host " Found: $searchPath" -ForegroundColor Green
Add-Item -Category 'AppDirectory' -ItemType 'Directory' `
-Path $searchPath -Name (Split-Path $searchPath -Leaf) -Value 'EXISTS'
foreach ($pattern in $MitutoyoFileExtensions) {
try {
$files = Get-ChildItem -Path $searchPath -Filter $pattern -Recurse -File -ErrorAction SilentlyContinue
foreach ($f in $files) {
Add-Item -Category 'AppFile' -ItemType $f.Extension `
-Path $f.FullName -Name $f.Name `
-Value $f.LastWriteTime.ToString('o') `
-SizeBytes $f.Length
}
} catch { }
}
}
# ---------------------------------------------------------------------- 2. Scan User / Shared Data Directories ----------------------------------------------------------------------
Write-Host "[2/5] Scanning user and shared data directories..." -ForegroundColor Cyan
foreach ($searchPath in $UserDataSearchPaths) {
if (-not (Test-Path $searchPath)) { continue }
Write-Host " Found: $searchPath" -ForegroundColor Green
Add-Item -Category 'DataDirectory' -ItemType 'Directory' `
-Path $searchPath -Name (Split-Path $searchPath -Leaf) -Value 'EXISTS'
foreach ($pattern in $MitutoyoFileExtensions) {
try {
$files = Get-ChildItem -Path $searchPath -Filter $pattern -Recurse -File -ErrorAction SilentlyContinue
foreach ($f in $files) {
Add-Item -Category 'DataFile' -ItemType $f.Extension `
-Path $f.FullName -Name $f.Name `
-Value $f.LastWriteTime.ToString('o') `
-SizeBytes $f.Length
}
} catch { }
}
}
# ---------------------------------------------------------------------- 3. Scan Registry ----------------------------------------------------------------------
Write-Host "[3/5] Scanning registry..." -ForegroundColor Cyan
function Export-RegistryTree {
param([string]$RegPath)
if (-not (Test-Path $RegPath)) { return }
Write-Host " Found: $RegPath" -ForegroundColor Green
try {
$key = Get-Item -Path $RegPath -ErrorAction SilentlyContinue
if ($key) {
foreach ($valName in $key.GetValueNames()) {
$val = $key.GetValue($valName)
Add-Item -Category 'Registry' -ItemType 'Value' `
-Path $RegPath -Name $valName `
-Value ([string]$val)
}
}
$subkeys = Get-ChildItem -Path $RegPath -Recurse -ErrorAction SilentlyContinue
foreach ($sk in $subkeys) {
foreach ($valName in $sk.GetValueNames()) {
$val = $sk.GetValue($valName)
Add-Item -Category 'Registry' -ItemType 'Value' `
-Path $sk.PSPath -Name $valName `
-Value ([string]$val)
}
}
} catch {
Write-Warning " Registry error at ${RegPath}: $_"
}
}
foreach ($root in $RegistrySearchRoots) {
Export-RegistryTree -RegPath $root
}
# ---------------------------------------------------------------------- 4. Locate Executables via Uninstall Keys ----------------------------------------------------------------------
Write-Host "[4/5] Checking installed programs (Uninstall registry)..." -ForegroundColor Cyan
$uninstallPaths = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
)
foreach ($uPath in $uninstallPaths) {
if (-not (Test-Path $uPath)) { continue }
$entries = Get-ChildItem -Path $uPath -ErrorAction SilentlyContinue
foreach ($entry in $entries) {
try {
$displayName = (Get-ItemProperty -Path $entry.PSPath -ErrorAction SilentlyContinue).DisplayName
if ($displayName -and ($displayName -match 'Mitutoyo|FORMTRACEPAK|FORMPAK|SURFPAK|FormTrace|SurfPak|DUAL\s*TRACEPAK')) {
$props = Get-ItemProperty -Path $entry.PSPath
Add-Item -Category 'InstalledProgram' -ItemType 'Uninstall' `
-Path $entry.PSPath -Name $displayName `
-Value ($props.InstallLocation, $props.DisplayVersion, $props.Publisher -join ' | ')
}
} catch { }
}
}
# ---------------------------------------------------------------------- 5. Locate Running Processes / Services ----------------------------------------------------------------------
Write-Host "[5/5] Checking running processes and services..." -ForegroundColor Cyan
$processPatterns = @('Formpak', 'Formtracepak', 'SurfPak', 'MtSurf', 'MtForm', 'Mitutoyo')
foreach ($pp in $processPatterns) {
$procs = Get-Process -Name "*${pp}*" -ErrorAction SilentlyContinue
foreach ($p in $procs) {
Add-Item -Category 'RunningProcess' -ItemType 'Process' `
-Path $p.Path -Name $p.ProcessName `
-Value "PID=$($p.Id)"
}
}
$services = Get-Service -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -match 'Mitutoyo|FORMTRACEPAK|FORMPAK|SURFPAK' }
foreach ($svc in $services) {
Add-Item -Category 'Service' -ItemType 'Service' `
-Path $svc.Name -Name $svc.DisplayName `
-Value $svc.Status
}
# ---------------------------------------------------------------------- Output ----------------------------------------------------------------------
if (-not (Test-Path $OutputPath)) {
New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null
}
$inventory | Export-Csv -Path $csvFile -NoTypeInformation -Encoding UTF8
# Summary
$summary = $inventory | Group-Object Category | Select-Object Name, Count
Write-Host "`n----------------------------------------------------------------------" -ForegroundColor Yellow
Write-Host " FORMTRACEPAK Inventory Summary" -ForegroundColor Yellow
Write-Host " Host: $ComputerName" -ForegroundColor Yellow
Write-Host "----------------------------------------------------------------------" -ForegroundColor Yellow
foreach ($g in $summary) {
Write-Host (" {0,-22} {1,6} items" -f $g.Name, $g.Count) -ForegroundColor White
}
$totalSize = 0
$measure = $inventory | Measure-Object SizeBytes -Sum -ErrorAction SilentlyContinue
if ($measure -and $null -ne $measure.Sum) { $totalSize = $measure.Sum }
Write-Host (" {0,-22} {1,6:N2} MB" -f 'Total File Size', ($totalSize / 1MB)) -ForegroundColor White
Write-Host "----------------------------------------------------------------------" -ForegroundColor Yellow
Write-Host " Inventory saved to: $csvFile" -ForegroundColor Green
Write-Host ""

View File

@@ -0,0 +1,5 @@
@echo off
REM Bypass launcher for Install-FormtracepakSettings.ps1.
REM Forwards any args to the PS1 (e.g. -BackupPath E:\... -RestoreAll -DryRun).
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0Install-FormtracepakSettings.ps1" %*
exit /b %ERRORLEVEL%

View File

@@ -0,0 +1,419 @@
<#
.SYNOPSIS
Deploys backed-up Mitutoyo Formtracepak settings from USB/removable media to a new host.
.DESCRIPTION
Reads a backup package created by Backup-FormtracepakSettings.ps1 and restores
application files, user data, registry keys, and configuration to the target host.
Supports Formtracepak v6.1 through current versions, plus FORMPAK-1000, SURFPAK-SV,
and DUAL TRACEPAK components.
Designed to run directly from USB media. Does NOT install the Formtracepak application
itself - the application must already be installed on the target host.
.PARAMETER AssetNumber
Asset number (e.g. WJRP1234) used to locate a per-asset backup on the
shopfloor share. If neither -BackupPath nor -AssetNumber is supplied, the
script prompts interactively and defaults to the current $env:COMPUTERNAME.
Ignored if -BackupPath is supplied.
.PARAMETER BackupPath
Path to the backup ZIP, directory containing a backup ZIP, or already-extracted
backup folder created by Backup-FormtracepakSettings.ps1. If omitted, defaults
to the per-asset path on the GE shopfloor share:
\\tsgwp00525.wjs.geaerospace.net\shared\DT\Shopfloor\backup\formtracepac\<AssetNumber>
If the path is a directory, the newest formtracepak_backup_*.zip inside it is used.
.PARAMETER RestoreRegistry
If specified, restores registry keys from the backup. Requires elevation.
.PARAMETER RestoreData
If specified, restores user data files (part programs, layouts, measurements, etc.).
.PARAMETER RestoreConfig
If specified, restores application configuration files (INI, XML, CFG, etc.).
.PARAMETER RestoreAll
Shortcut to restore everything: registry, data, and config.
.PARAMETER DryRun
Simulates the restore without writing any files or registry keys.
.PARAMETER Force
Overwrites existing files without prompting.
.EXAMPLE
.\Install-FormtracepakSettings.ps1 -RestoreAll
.\Install-FormtracepakSettings.ps1 -AssetNumber WJRP2335 -RestoreAll
.\Install-FormtracepakSettings.ps1 -BackupPath "E:\mitutoyo\backup" -RestoreData -RestoreConfig
.\Install-FormtracepakSettings.ps1 -RestoreAll -DryRun
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[string]$AssetNumber,
[string]$BackupPath,
[switch]$RestoreRegistry,
[switch]$RestoreData,
[switch]$RestoreConfig,
[switch]$RestoreAll,
[switch]$DryRun,
[switch]$Force
)
$SharedRoot = '\\tsgwp00525.wjs.geaerospace.net\shared\DT\Shopfloor\backup\formtracepac'
if (-not $BackupPath) {
if (-not $AssetNumber) {
$defaultAsset = $env:COMPUTERNAME
if ([Environment]::UserInteractive -and -not [Console]::IsInputRedirected) {
$assetInput = Read-Host "Asset number to restore (e.g. WJRP1234) [$defaultAsset]"
if ([string]::IsNullOrWhiteSpace($assetInput)) {
$AssetNumber = $defaultAsset
} else {
$AssetNumber = $assetInput.Trim()
}
} else {
$AssetNumber = $defaultAsset
Write-Host "Non-interactive session - defaulting AssetNumber to $AssetNumber. Pass -AssetNumber or -BackupPath to override."
}
}
$BackupPath = Join-Path $SharedRoot $AssetNumber
}
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Continue'
# ----------------------------------------------------------------------
# CONFIGURABLE: Default restore targets. Edit these to match your environment.
# The backup manifest records the original paths; these are the fallback
# destinations if the original path does not exist on the new host.
# Ordered so fallback priority is deterministic.
# ----------------------------------------------------------------------
$DefaultAppTargets = [ordered]@{
'ProgramFiles' = "${env:ProgramFiles}\Mitutoyo"
'ProgramFilesX86' = "${env:ProgramFiles(x86)}\Mitutoyo"
'CMitutoyo' = 'C:\Mitutoyo'
'CFORMTRACEPAK' = 'C:\FORMTRACEPAK'
}
$DefaultDataTargets = [ordered]@{
'AppDataRoaming' = "${env:APPDATA}\Mitutoyo"
'AppDataLocal' = "${env:LOCALAPPDATA}\Mitutoyo"
'ProgramData' = "${env:ProgramData}\Mitutoyo"
'UserDocuments' = "${env:USERPROFILE}\Documents\Mitutoyo"
'PublicDocuments' = "${env:PUBLIC}\Documents\Mitutoyo"
}
# ----------------------------------------------------------------------
if ($RestoreAll) {
$RestoreRegistry = $true
$RestoreData = $true
$RestoreConfig = $true
}
if (-not $RestoreRegistry -and -not $RestoreData -and -not $RestoreConfig) {
Write-Warning "No restore scope specified. Use -RestoreAll, -RestoreData, -RestoreConfig, and/or -RestoreRegistry."
Write-Host "Run with -RestoreAll to restore everything, or combine flags as needed."
return
}
# ---------------------------------------------------------------------- Locate and unpack backup ----------------------------------------------------------------------
if (-not (Test-Path $BackupPath)) {
Write-Error "Backup path not found: $BackupPath"
return
}
$workingBackup = $BackupPath
$zipToExtract = $null
if ($BackupPath -match '\.zip$') {
$zipToExtract = $BackupPath
} elseif ((Get-Item $BackupPath).PSIsContainer) {
# Directory: pick newest formtracepak_backup_*.zip if any. Else assume it
# already contains an extracted backup (manifest.json at root).
$cand = Get-ChildItem -Path $BackupPath -Filter 'formtracepak_backup_*.zip' -File -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending | Select-Object -First 1
if ($cand) {
$zipToExtract = $cand.FullName
Write-Host "Using newest backup ZIP in $BackupPath -> $($cand.Name)" -ForegroundColor Cyan
}
}
if ($zipToExtract) {
$extractDir = Join-Path $env:TEMP "formtracepak_restore_$(Get-Date -Format 'yyyyMMddHHmmss')"
Write-Host "Extracting backup archive to $extractDir ..." -ForegroundColor Cyan
Expand-Archive -Path $zipToExtract -DestinationPath $extractDir -Force
$workingBackup = $extractDir
}
# ---------------------------------------------------------------------- Load manifest ----------------------------------------------------------------------
$manifestFile = Join-Path $workingBackup 'manifest.json'
if (-not (Test-Path $manifestFile)) {
Write-Error "No manifest.json found in backup at $workingBackup. Is this a valid Formtracepak backup?"
return
}
$manifest = Get-Content $manifestFile -Raw | ConvertFrom-Json
Write-Host "`n----------------------------------------------------------------------" -ForegroundColor Yellow
Write-Host " FORMTRACEPAK Settings Restore" -ForegroundColor Yellow
Write-Host "----------------------------------------------------------------------" -ForegroundColor Yellow
Write-Host " Source Host : $($manifest.SourceComputer)" -ForegroundColor White
Write-Host " Backup Date : $($manifest.BackupTimestamp)" -ForegroundColor White
Write-Host " Version : $($manifest.FormtracepakVersion)" -ForegroundColor White
Write-Host " Target Host : $env:COMPUTERNAME" -ForegroundColor White
Write-Host " Dry Run : $DryRun" -ForegroundColor $(if ($DryRun) { 'Yellow' } else { 'White' })
Write-Host "----------------------------------------------------------------------`n" -ForegroundColor Yellow
$counters = @{ Files = 0; Directories = 0; RegistryKeys = 0; Skipped = 0; Errors = 0 }
# ---------------------------------------------------------------------- Helper: Copy with logging ----------------------------------------------------------------------
function Restore-FileItem {
param(
[string]$SourceFile,
[string]$DestFile
)
$destDir = Split-Path $DestFile -Parent
if (-not (Test-Path $destDir)) {
if ($DryRun) {
Write-Host " [DRYRUN] MKDIR $destDir" -ForegroundColor DarkGray
} else {
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
}
$counters.Directories++
}
if ((Test-Path $DestFile) -and -not $Force) {
$srcHash = (Get-FileHash $SourceFile -Algorithm MD5).Hash
$destHash = (Get-FileHash $DestFile -Algorithm MD5).Hash
if ($srcHash -eq $destHash) {
Write-Host " [SKIP] Identical: $DestFile" -ForegroundColor DarkGray
$counters.Skipped++
return
}
if (-not $DryRun) {
$stamp = Get-Date -Format 'yyyyMMddHHmmss'
$backupDest = "${DestFile}.pre_restore_bak_${stamp}"
Copy-Item -Path $DestFile -Destination $backupDest -Force
Write-Host " [BAK] Existing file backed up: $backupDest" -ForegroundColor DarkYellow
}
}
if ($DryRun) {
Write-Host " [DRYRUN] COPY $SourceFile -> $DestFile" -ForegroundColor DarkGray
} else {
try {
Copy-Item -Path $SourceFile -Destination $DestFile -Force
Write-Host " [OK] $DestFile" -ForegroundColor Green
} catch {
Write-Warning " [ERR] Failed to copy to ${DestFile}: $_"
$counters.Errors++
return
}
}
$counters.Files++
}
# ---------------------------------------------------------------------- Restore Config Files ----------------------------------------------------------------------
if ($RestoreConfig) {
Write-Host "[CONFIG] Restoring configuration files..." -ForegroundColor Cyan
$configDir = Join-Path $workingBackup 'config'
if (Test-Path $configDir) {
$configManifest = Join-Path $configDir 'file_manifest.csv'
if (Test-Path $configManifest) {
$entries = Import-Csv $configManifest
foreach ($entry in $entries) {
$srcFile = Join-Path $configDir $entry.RelativePath
if (-not (Test-Path $srcFile)) {
Write-Warning " Source file missing from backup: $($entry.RelativePath)"
$counters.Errors++
continue
}
$destFile = $entry.OriginalPath
$destParent = Split-Path $destFile -Parent
if (-not (Test-Path $destParent)) {
foreach ($key in $DefaultAppTargets.Keys) {
$candidate = Join-Path $DefaultAppTargets[$key] $entry.RelativePath
$candidateParent = Split-Path $candidate -Parent
if (Test-Path (Split-Path $candidateParent -Parent)) {
$destFile = $candidate
break
}
}
}
Restore-FileItem -SourceFile $srcFile -DestFile $destFile
}
} else {
$configFiles = Get-ChildItem -Path $configDir -File -Recurse
foreach ($cf in $configFiles) {
$relPath = $cf.FullName.Substring($configDir.Length + 1)
$destFile = Join-Path $DefaultAppTargets['ProgramFiles'] $relPath
Restore-FileItem -SourceFile $cf.FullName -DestFile $destFile
}
}
} else {
Write-Host " No config directory in backup. Skipping." -ForegroundColor DarkGray
}
}
# ---------------------------------------------------------------------- Restore Data Files ----------------------------------------------------------------------
if ($RestoreData) {
Write-Host "[DATA] Restoring data files (part programs, layouts, measurements, designs)..." -ForegroundColor Cyan
$dataDir = Join-Path $workingBackup 'data'
if (Test-Path $dataDir) {
$dataManifest = Join-Path $dataDir 'file_manifest.csv'
if (Test-Path $dataManifest) {
$entries = Import-Csv $dataManifest
foreach ($entry in $entries) {
$srcFile = Join-Path $dataDir $entry.RelativePath
if (-not (Test-Path $srcFile)) {
Write-Warning " Source file missing from backup: $($entry.RelativePath)"
$counters.Errors++
continue
}
$destFile = $entry.OriginalPath
$destParent = Split-Path $destFile -Parent
if (-not (Test-Path $destParent)) {
foreach ($key in $DefaultDataTargets.Keys) {
$candidate = Join-Path $DefaultDataTargets[$key] $entry.RelativePath
$candidateParent = Split-Path $candidate -Parent
if (Test-Path (Split-Path $candidateParent -Parent)) {
$destFile = $candidate
break
}
}
}
Restore-FileItem -SourceFile $srcFile -DestFile $destFile
}
} else {
$dataFiles = Get-ChildItem -Path $dataDir -File -Recurse
foreach ($df in $dataFiles) {
$relPath = $df.FullName.Substring($dataDir.Length + 1)
$destFile = Join-Path $DefaultDataTargets['UserDocuments'] $relPath
Restore-FileItem -SourceFile $df.FullName -DestFile $destFile
}
}
} else {
Write-Host " No data directory in backup. Skipping." -ForegroundColor DarkGray
}
}
# ---------------------------------------------------------------------- Restore Registry ----------------------------------------------------------------------
if ($RestoreRegistry) {
Write-Host "[REG] Restoring registry keys..." -ForegroundColor Cyan
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin -and -not $DryRun) {
Write-Warning " Registry restore of HKLM keys requires elevation. HKCU keys will still be restored."
}
$regDir = Join-Path $workingBackup 'registry'
# Method 1: .reg file import
$regFiles = Get-ChildItem -Path $regDir -Filter '*.reg' -ErrorAction SilentlyContinue
foreach ($rf in $regFiles) {
if ($DryRun) {
Write-Host " [DRYRUN] REG IMPORT $($rf.FullName)" -ForegroundColor DarkGray
} else {
try {
$proc = Start-Process -FilePath 'reg.exe' -ArgumentList "import `"$($rf.FullName)`"" `
-Wait -PassThru -NoNewWindow -ErrorAction Stop
if ($proc.ExitCode -eq 0) {
Write-Host " [OK] Imported $($rf.Name)" -ForegroundColor Green
} else {
Write-Warning " [ERR] reg.exe returned exit code $($proc.ExitCode) for $($rf.Name)"
$counters.Errors++
}
} catch {
Write-Warning " [ERR] Failed to import $($rf.Name): $_"
$counters.Errors++
}
}
$counters.RegistryKeys++
}
# Method 2: CSV-based key-by-key restore
$regCsv = Join-Path $regDir 'registry_values.csv'
if (Test-Path $regCsv) {
$regEntries = Import-Csv $regCsv
foreach ($re in $regEntries) {
$regPath = $re.Path
$regName = $re.Name
$regValue = $re.Value
$regType = $re.Type
if ($regPath -match '^HKLM' -and -not $isAdmin -and -not $DryRun) {
Write-Host " [SKIP] HKLM key requires elevation: $regPath\$regName" -ForegroundColor DarkYellow
$counters.Skipped++
continue
}
if ($DryRun) {
Write-Host " [DRYRUN] SET $regPath\$regName = $regValue ($regType)" -ForegroundColor DarkGray
} else {
try {
if (-not (Test-Path $regPath)) {
New-Item -Path $regPath -Force | Out-Null
}
$splat = @{
Path = $regPath
Name = $regName
Value = $regValue
Force = $true
ErrorAction = 'Stop'
}
if ($regType) { $splat['PropertyType'] = $regType }
New-ItemProperty @splat | Out-Null
Write-Host " [OK] $regPath\$regName" -ForegroundColor Green
} catch {
Write-Warning " [ERR] Failed to set $regPath\$regName : $_"
$counters.Errors++
}
}
$counters.RegistryKeys++
}
}
}
# ---------------------------------------------------------------------- Summary ----------------------------------------------------------------------
Write-Host "`n----------------------------------------------------------------------" -ForegroundColor Yellow
Write-Host " Restore Summary" -ForegroundColor Yellow
Write-Host "----------------------------------------------------------------------" -ForegroundColor Yellow
Write-Host (" Files Restored : {0}" -f $counters.Files) -ForegroundColor White
Write-Host (" Directories Made : {0}" -f $counters.Directories) -ForegroundColor White
Write-Host (" Registry Entries : {0}" -f $counters.RegistryKeys) -ForegroundColor White
Write-Host (" Skipped : {0}" -f $counters.Skipped) -ForegroundColor DarkGray
Write-Host (" Errors : {0}" -f $counters.Errors) -ForegroundColor $(if ($counters.Errors -gt 0) { 'Red' } else { 'White' })
Write-Host "----------------------------------------------------------------------" -ForegroundColor Yellow
if ($counters.Errors -gt 0) {
Write-Warning "Some items failed to restore. Review warnings above."
}
if ($DryRun) {
Write-Host "`n This was a DRY RUN. No changes were made." -ForegroundColor Yellow
}
# Cleanup temp extraction if we unzipped
if ($workingBackup -ne $BackupPath -and (Test-Path $workingBackup) -and -not $DryRun) {
Remove-Item -Path $workingBackup -Recurse -Force -ErrorAction SilentlyContinue
}
Write-Host "`n NOTE: Stylus calibration data is stored on the probe hardware." -ForegroundColor Magenta
Write-Host " Re-run calibration on the new host after restoring settings.`n" -ForegroundColor Magenta