diff --git a/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Backup-FormtracepakSettings.bat b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Backup-FormtracepakSettings.bat new file mode 100644 index 0000000..cfe5a04 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Backup-FormtracepakSettings.bat @@ -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% diff --git a/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Backup-FormtracepakSettings.ps1 b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Backup-FormtracepakSettings.ps1 new file mode 100755 index 0000000..5e050e7 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Backup-FormtracepakSettings.ps1 @@ -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\ + 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 diff --git a/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Export-FormtracepakInventory.bat b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Export-FormtracepakInventory.bat new file mode 100644 index 0000000..fe0e8bd --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Export-FormtracepakInventory.bat @@ -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% diff --git a/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Export-FormtracepakInventory.ps1 b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Export-FormtracepakInventory.ps1 new file mode 100755 index 0000000..d5ce56a --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Export-FormtracepakInventory.ps1 @@ -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 "" diff --git a/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Install-FormtracepakSettings.bat b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Install-FormtracepakSettings.bat new file mode 100644 index 0000000..216971e --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Install-FormtracepakSettings.bat @@ -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% diff --git a/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Install-FormtracepakSettings.ps1 b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Install-FormtracepakSettings.ps1 new file mode 100755 index 0000000..6423a20 --- /dev/null +++ b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Install-FormtracepakSettings.ps1 @@ -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\ + 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