goCMM showed an empty parts list after restore though the bay reached the share. Decompiled goCMM: PartGroupViewModel matches the registry Selected Part Group against ApplicationSettings.xml <PartGroup FullName> with a CASE-SENSITIVE compare, then enumerates that FullName for the parts. The host-canon rewrite fixed only the hostname, leaving xml '\shared' (lowercase) vs registry '\SHARED' (uppercase) -> Find null -> SelectedPartGroup null -> empty list. Fix spans the share segment too, pinning both to \tsgwp00525.wjs.geaerospace.net\SHARED. Verified in PowerShell (-ceq True). Runs at imaging in Restore-CMM, so all captured backups are fixed on restore with no re-backup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
240 lines
12 KiB
PowerShell
240 lines
12 KiB
PowerShell
<#
|
|
Install-goCMMSettings.ps1
|
|
|
|
Restore a goCMM bay's settings from a zip produced by Backup-goCMMSettings.ps1.
|
|
Mirrors Install-FormtracepakSettings.ps1.
|
|
|
|
Lays back both halves:
|
|
1. Registry pointers -> HKLM\SOFTWARE\WOW6432Node\General Electric\goCMM
|
|
2. Shared Data Directory (C:\geaofi\, incl ApplicationSettings.xml = all 7
|
|
Settings tabs: PC-DMIS, Quindos, Modus, Machine Definition, User Input,
|
|
Notifications, Part Groups).
|
|
|
|
Then grants the access goCMM needs so it works under lockdown:
|
|
- BUILTIN\Users ReadKey+WriteKey on the goCMM reg key. goCMM's
|
|
RegistrySettings.GetRegistryString opens that key with writable:true even
|
|
to READ, so a read-only operator hits a SecurityException without this.
|
|
- BUILTIN\Users Modify on the Shared Data Directory (goCMM writes
|
|
ApplicationSettings.xml back there when settings are saved).
|
|
|
|
Run as administrator / SYSTEM (imaging context). If the lockdown pass strips
|
|
the registry ACE, re-run this (or just its ACL section) AFTER lockdown.
|
|
|
|
NOTE: this restores goCMM only. PC-DMIS probe calibrations / custom tip angles /
|
|
machine comp are owned by PC-DMIS (Hexagon) and are NOT in this backup.
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory=$true)][string]$BackupPath, # zip or already-extracted dir
|
|
[string]$SelectedPartGroup, # optional per-bay override of the part-group UNC
|
|
[string]$ReplaceFrom, # optional EXTRA find/replace across reg + xml
|
|
[string]$ReplaceTo, # ... replacement. Case-insensitive.
|
|
[switch]$NoDefaultRewrite # skip the built-in legacy->new FQDN swaps below
|
|
)
|
|
|
|
# ============================================================================
|
|
# Built-in FQDN migration - applied AUTOMATICALLY on every restore (no flag).
|
|
# Add pairs here as more legacy domains retire. -NoDefaultRewrite disables them.
|
|
# ============================================================================
|
|
$DefaultRewrites = @(
|
|
@{ From = 'rd.ds.ge.com'; To = 'wjs.geaerospace.net' } # WJ legacy domain -> new domain
|
|
)
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Part-group share host canonicalization. Bays were captured with three
|
|
# inconsistent forms of the share host in the goCMM 'Selected Part Group' /
|
|
# ApplicationSettings.xml <PartGroup FullName>:
|
|
# \\tsgwp00525\... (bare hostname - DNS-suffix dependent)
|
|
# \\tsgwp00525.rd.ds.ge.com\... (legacy GE corp domain - dead on the
|
|
# air-gapped shopfloor net)
|
|
# \\tsgwp00525.wjs.geaerospace.net\... (correct)
|
|
# The DefaultRewrites above only fix the middle form. goCMM DISPLAYS the part
|
|
# group from ApplicationSettings.xml, so the bare form survived into the UI.
|
|
# Pin every form to the FQDN in BOTH the registry and the XML. The regex
|
|
# matches the UNC host with an optional domain suffix and is idempotent (an
|
|
# already-correct FQDN maps to itself). Disabled by -NoDefaultRewrite.
|
|
$PartGroupHostShort = 'tsgwp00525'
|
|
$PartGroupHostFqdn = 'tsgwp00525.wjs.geaerospace.net'
|
|
# \\HOST or \\HOST.any.domain PLUS the \shared share segment (any case) ->
|
|
# the exact canonical \\FQDN\SHARED. The share segment case MUST be normalized
|
|
# too, not just the host: goCMM matches the registry 'Selected Part Group'
|
|
# against the ApplicationSettings.xml <PartGroup FullName> with a CASE-SENSITIVE
|
|
# string compare (PartGroupViewModel: Find(pg => pg.FullName == regValue)). If
|
|
# only the host were fixed, the xml kept '\shared' (lowercase, as captured) while
|
|
# the override/09-Setup form is '\SHARED' (uppercase) -> Find returns null ->
|
|
# SelectedPartGroup null -> goCMM enumerates nothing -> EMPTY parts list even
|
|
# though the bay can reach the share. Match the whole host+share span and pin
|
|
# both to '\\tsgwp00525.wjs.geaerospace.net\SHARED'. Idempotent.
|
|
$PartGroupHostRx = '(?i)\\\\' + [regex]::Escape($PartGroupHostShort) + '(?:\.[A-Za-z0-9.\-]+)?\\shared(?=\\|$)'
|
|
$PartGroupHostTo = '\\' + $PartGroupHostFqdn + '\SHARED'
|
|
$ErrorActionPreference = 'Continue'
|
|
$ts = Get-Date -Format 'yyyyMMdd-HHmmss'
|
|
$logDir = 'C:\Logs\CMM'
|
|
New-Item -ItemType Directory -Path $logDir -Force -ErrorAction SilentlyContinue | Out-Null
|
|
$log = Join-Path $logDir "gocmm-restore-$ts.log"
|
|
function Log($m){ Write-Host $m; $m | Out-File -FilePath $log -Append }
|
|
|
|
$goCmmKey = 'HKLM:\SOFTWARE\WOW6432Node\General Electric\goCMM'
|
|
Log "==== goCMM restore on $env:COMPUTERNAME from $BackupPath ===="
|
|
|
|
# --- Resolve the backup to a directory ---
|
|
$src = $BackupPath
|
|
$extracted = $false
|
|
if ($BackupPath -match '\.zip$') {
|
|
$src = Join-Path $env:TEMP "gocmm-rs-$ts"
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
[System.IO.Compression.ZipFile]::ExtractToDirectory($BackupPath, $src)
|
|
$extracted = $true
|
|
}
|
|
if (-not (Test-Path $src)) { Log "ERROR: backup path not found: $src"; exit 1 }
|
|
|
|
# --- Read manifest for the shared-dir target ---
|
|
$sharedDir = 'C:\geaofi'
|
|
$mani = Join-Path $src 'manifest.json'
|
|
if (Test-Path $mani) {
|
|
try {
|
|
$m = Get-Content $mani -Raw | ConvertFrom-Json
|
|
if ($m.SharedDataDirectory) { $sharedDir = $m.SharedDataDirectory }
|
|
Log "manifest: shared=$sharedDir partGroup=$($m.SelectedPartGroup) ver=$($m.goCMMVersion)"
|
|
} catch { Log "WARN: could not parse manifest.json: $($_.Exception.Message)" }
|
|
}
|
|
|
|
# --- Import the registry pointers ---
|
|
$reg = Join-Path $src 'registry\goCMM.reg'
|
|
if (Test-Path $reg) {
|
|
reg import "$reg" 2>&1 | Out-Null
|
|
Log "Imported registry key from goCMM.reg"
|
|
} else {
|
|
Log "WARN: registry\goCMM.reg missing in backup - creating an empty key so the ACL still lands"
|
|
if (-not (Test-Path $goCmmKey)) { New-Item -Path $goCmmKey -Force | Out-Null }
|
|
}
|
|
|
|
# --- Optional per-bay Selected Part Group override (use when restoring to a different bay) ---
|
|
if ($SelectedPartGroup) {
|
|
if (-not (Test-Path $goCmmKey)) { New-Item -Path $goCmmKey -Force | Out-Null }
|
|
New-ItemProperty -Path $goCmmKey -Name 'Selected Part Group' -Value $SelectedPartGroup -PropertyType String -Force | Out-Null
|
|
Log "Override Selected Part Group = $SelectedPartGroup"
|
|
}
|
|
|
|
# --- Lay down the Shared Data Directory (ApplicationSettings.xml = all tabs) ---
|
|
$geaofiSrc = Join-Path $src 'geaofi'
|
|
if (Test-Path $geaofiSrc) {
|
|
New-Item -ItemType Directory -Path $sharedDir -Force -ErrorAction SilentlyContinue | Out-Null
|
|
robocopy $geaofiSrc $sharedDir /E /R:1 /W:1 /NFL /NDL /NJH /NJS | Out-Null
|
|
Log "Restored Shared Data Directory -> $sharedDir"
|
|
if (Test-Path (Join-Path $sharedDir 'ApplicationSettings.xml')) {
|
|
Log "ApplicationSettings.xml in place (PC-DMIS + all Settings tabs)"
|
|
} else {
|
|
Log "WARN: ApplicationSettings.xml not present after restore"
|
|
}
|
|
} else {
|
|
Log "WARN: geaofi payload missing in backup - settings tabs NOT restored"
|
|
}
|
|
|
|
# --- Find/replace across ALL restored data (registry values + every text file
|
|
# under the Shared Data Directory). The built-in legacy->new FQDN swaps run
|
|
# AUTOMATICALLY; -ReplaceFrom/-ReplaceTo adds one more; case-insensitive. ---
|
|
$rewrites = @()
|
|
if (-not $NoDefaultRewrite) { $rewrites += $DefaultRewrites }
|
|
if ($ReplaceFrom -and $ReplaceTo) { $rewrites += @{ From = $ReplaceFrom; To = $ReplaceTo } }
|
|
|
|
if ($rewrites.Count -gt 0) {
|
|
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
|
foreach ($rw in $rewrites) {
|
|
$from = $rw.From; $to = $rw.To
|
|
if (-not $from) { continue }
|
|
Log "Find/replace: '$from' -> '$to' (case-insensitive)"
|
|
$rxFrom = [regex]::Escape($from)
|
|
|
|
# registry: every string value under the goCMM key
|
|
if (Test-Path $goCmmKey) {
|
|
try {
|
|
$props = Get-ItemProperty -Path $goCmmKey
|
|
foreach ($p in $props.PSObject.Properties) {
|
|
if ($p.Name -like 'PS*') { continue }
|
|
if (($p.Value -is [string]) -and ([regex]::IsMatch($p.Value, $rxFrom, 'IgnoreCase'))) {
|
|
$new = [regex]::Replace($p.Value, $rxFrom, $to, 'IgnoreCase')
|
|
Set-ItemProperty -Path $goCmmKey -Name $p.Name -Value $new
|
|
Log " reg [$($p.Name)] -> $new"
|
|
}
|
|
}
|
|
} catch { Log " WARN: registry rewrite failed: $($_.Exception.Message)" }
|
|
}
|
|
|
|
# files: every text file under the Shared Data Directory
|
|
if (Test-Path $sharedDir) {
|
|
Get-ChildItem -Path $sharedDir -Recurse -File -Include *.xml,*.txt,*.csv,*.config,*.ini,*.bas -ErrorAction SilentlyContinue | ForEach-Object {
|
|
try {
|
|
$c = [System.IO.File]::ReadAllText($_.FullName)
|
|
if ([regex]::IsMatch($c, $rxFrom, 'IgnoreCase')) {
|
|
$nc = [regex]::Replace($c, $rxFrom, $to, 'IgnoreCase')
|
|
[System.IO.File]::WriteAllText($_.FullName, $nc, $utf8NoBom)
|
|
Log " file [$($_.Name)] rewritten"
|
|
}
|
|
} catch { Log " WARN: file rewrite $($_.FullName): $($_.Exception.Message)" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# --- Canonicalize the part-group share host to the FQDN in reg + XML ---
|
|
# Runs AFTER the geaofi robocopy (so ApplicationSettings.xml is in place)
|
|
# and AFTER the literal rewrites above. Idempotent. This is what makes
|
|
# goCMM show the FQDN regardless of which form the bay was captured with.
|
|
if (-not $NoDefaultRewrite) {
|
|
# registry: every string value under the goCMM key
|
|
if (Test-Path $goCmmKey) {
|
|
try {
|
|
$props = Get-ItemProperty -Path $goCmmKey
|
|
foreach ($p in $props.PSObject.Properties) {
|
|
if ($p.Name -like 'PS*') { continue }
|
|
if (($p.Value -is [string]) -and ([regex]::IsMatch($p.Value, $PartGroupHostRx))) {
|
|
$new = [regex]::Replace($p.Value, $PartGroupHostRx, $PartGroupHostTo)
|
|
if ($new -ne $p.Value) {
|
|
Set-ItemProperty -Path $goCmmKey -Name $p.Name -Value $new
|
|
Log " host-canon reg [$($p.Name)] -> $new"
|
|
}
|
|
}
|
|
}
|
|
} catch { Log " WARN: host canonicalize (reg) failed: $($_.Exception.Message)" }
|
|
}
|
|
# files: every text file under the Shared Data Directory (incl ApplicationSettings.xml)
|
|
if (Test-Path $sharedDir) {
|
|
$utf8NoBomHost = New-Object System.Text.UTF8Encoding($false)
|
|
Get-ChildItem -Path $sharedDir -Recurse -File -Include *.xml,*.txt,*.csv,*.config,*.ini,*.bas -ErrorAction SilentlyContinue | ForEach-Object {
|
|
try {
|
|
$c = [System.IO.File]::ReadAllText($_.FullName)
|
|
if ([regex]::IsMatch($c, $PartGroupHostRx)) {
|
|
$nc = [regex]::Replace($c, $PartGroupHostRx, $PartGroupHostTo)
|
|
if ($nc -ne $c) {
|
|
[System.IO.File]::WriteAllText($_.FullName, $nc, $utf8NoBomHost)
|
|
Log " host-canon file [$($_.Name)] rewritten"
|
|
}
|
|
}
|
|
} catch { Log " WARN: host canonicalize $($_.FullName): $($_.Exception.Message)" }
|
|
}
|
|
}
|
|
}
|
|
|
|
# --- Grant BUILTIN\Users ReadKey+WriteKey on the reg key (goCMM opens it writable:true to read) ---
|
|
if (Test-Path $goCmmKey) {
|
|
try {
|
|
$acl = Get-Acl -Path $goCmmKey
|
|
$rule = New-Object System.Security.AccessControl.RegistryAccessRule(
|
|
'BUILTIN\Users','ReadKey,WriteKey','ContainerInherit','None','Allow')
|
|
$acl.AddAccessRule($rule)
|
|
Set-Acl -Path $goCmmKey -AclObject $acl -ErrorAction Stop
|
|
Log "Granted BUILTIN\Users ReadKey,WriteKey on $goCmmKey"
|
|
} catch { Log "WARN: registry ACL grant failed: $($_.Exception.Message)" }
|
|
}
|
|
|
|
# --- Grant BUILTIN\Users Modify on the Shared Data Directory (goCMM saves settings there) ---
|
|
if (Test-Path $sharedDir) {
|
|
& icacls $sharedDir /grant 'BUILTIN\Users:(OI)(CI)M' /T /C 2>&1 | Out-Null
|
|
Log "Granted BUILTIN\Users Modify on $sharedDir"
|
|
}
|
|
|
|
if ($extracted) { Remove-Item $src -Recurse -Force -ErrorAction SilentlyContinue }
|
|
Log "==== DONE ===="
|
|
Write-Host ""
|
|
Write-Host "goCMM restore complete. Log: $log" -ForegroundColor Green
|