Update-MachineNumber: pull per-bay udc_settings.json from SFLD on placeholder->real
When the tech transitions a 9999-placeholder PC to its real machine number, also restore the per-bay udc_settings_<num>.json from \\tsgwp00525\shared\spc\udc\settings_backups\. PXE-time preinstall can't reach this share (no SFLD creds yet), so 00-PreInstall uses the local C:\Enrollment mirror; post-config the share is reachable, so the renumber path goes direct to the canonical source. Adds udcSettingsSharePath to site-config.json under Standard-Machine. Bundles in prior uncommitted work in the same file: ntlars reg restore, UDC data restore (CurrentData.json + ArchivedData/), MTConnect Devices.xml inline rewrite + service restart, and one-shot consume of per-bay UDC backup -> migrated/<timestamp>/.
This commit is contained in:
@@ -96,6 +96,133 @@ function Update-MachineNumber {
|
||||
$out.Errors += "ntlars restore failed: $_"
|
||||
}
|
||||
}
|
||||
|
||||
# --- UDC settings JSON restore: pull udc_settings_<NewNumber>.json
|
||||
# from the SFLD UDC settings_backups share. At imaging time
|
||||
# 00-PreInstall-MachineApps.ps1 pulls this from the local
|
||||
# C:\Enrollment mirror, but a 9999-placeholder PC has no real
|
||||
# pre-stage. Once the tech sets the real number we have SFLD
|
||||
# creds, so go direct to the canonical share. ---
|
||||
$udcSettingsSharePath = $null
|
||||
if ($cfg) {
|
||||
try { $udcSettingsSharePath = $cfg.pcProfiles.'Standard-Machine'.udcSettingsSharePath } catch {}
|
||||
}
|
||||
if ($udcSettingsSharePath) {
|
||||
try {
|
||||
$mountedUdcSet = Mount-SFLDShare -SharePath $udcSettingsSharePath -DriveLetter 'X:'
|
||||
if ($mountedUdcSet) {
|
||||
try {
|
||||
$srcSettings = Join-Path 'X:\' "udc_settings_$NewNumber.json"
|
||||
if (Test-Path -LiteralPath $srcSettings) {
|
||||
Get-Process UDC -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
try { $_.Kill(); $_.WaitForExit(5000) | Out-Null } catch {}
|
||||
}
|
||||
Start-Sleep -Milliseconds 500
|
||||
$localUdcDir = 'C:\ProgramData\UDC'
|
||||
if (-not (Test-Path $localUdcDir)) { New-Item -ItemType Directory -Path $localUdcDir -Force | Out-Null }
|
||||
Copy-Item -LiteralPath $srcSettings -Destination $script:UdcSettingsPath -Force -ErrorAction Stop
|
||||
$out.UdcSettingsRestored = $true
|
||||
Write-Host " Update-MachineNumber: UDC settings restored from $srcSettings"
|
||||
} else {
|
||||
Write-Host " Update-MachineNumber: no udc_settings_$NewNumber.json on settings_backups share"
|
||||
}
|
||||
} finally {
|
||||
& net use X: /delete /y 2>$null | Out-Null
|
||||
}
|
||||
} else {
|
||||
Write-Host " Update-MachineNumber: UDC settings_backups share unreachable - skipping settings restore."
|
||||
}
|
||||
} catch {
|
||||
$out.Errors += "UDC settings restore failed: $_"
|
||||
}
|
||||
}
|
||||
|
||||
# --- UDC data restore: pull CurrentData.json + ArchivedData/ from
|
||||
# the per-bay backup at <udcBackupSharePath>\<NewNumber>\.
|
||||
# One-shot: after successful restore, the live backup at the root
|
||||
# is moved into <NewNumber>\migrated\<timestamp>\ so it can't be
|
||||
# replayed on subsequent reboots or reused for a future PC at the
|
||||
# same bay. The 'isPlaceholder' guard above ensures this whole
|
||||
# block only ever fires once per PC's lifetime (placeholder->real
|
||||
# transition). ---
|
||||
$udcSharePath = $null
|
||||
if ($cfg) {
|
||||
try { $udcSharePath = $cfg.pcProfiles.'Standard-Machine'.udcBackupSharePath } catch {}
|
||||
}
|
||||
if ($udcSharePath) {
|
||||
try {
|
||||
$mountedUdc = Mount-SFLDShare -SharePath $udcSharePath -DriveLetter 'W:'
|
||||
if ($mountedUdc) {
|
||||
try {
|
||||
$bayDir = Join-Path 'W:\' $NewNumber
|
||||
$srcCur = Join-Path $bayDir 'CurrentData.json'
|
||||
$srcArc = Join-Path $bayDir 'ArchivedData'
|
||||
if (Test-Path -LiteralPath $srcCur) {
|
||||
Write-Host " Update-MachineNumber: UDC backup found at $bayDir - restoring."
|
||||
|
||||
# Stop UDC pre-emptively so CurrentData.json isn't locked
|
||||
Get-Process UDC -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
try { $_.Kill(); $_.WaitForExit(5000) | Out-Null } catch {}
|
||||
}
|
||||
Start-Sleep -Milliseconds 500
|
||||
|
||||
$localUdcDir = 'C:\ProgramData\UDC'
|
||||
if (-not (Test-Path $localUdcDir)) { New-Item -ItemType Directory -Path $localUdcDir -Force | Out-Null }
|
||||
|
||||
$localArc = Join-Path $localUdcDir 'ArchivedData'
|
||||
|
||||
# Copy
|
||||
Copy-Item -LiteralPath $srcCur -Destination (Join-Path $localUdcDir 'CurrentData.json') -Force -ErrorAction Stop
|
||||
if (Test-Path -LiteralPath $srcArc) {
|
||||
if (Test-Path -LiteralPath $localArc) { Remove-Item -LiteralPath $localArc -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
Copy-Item -LiteralPath $srcArc -Destination $localArc -Recurse -Force -ErrorAction Stop
|
||||
}
|
||||
|
||||
# Move live backup -> migrated/<timestamp>/ (one-shot consumption)
|
||||
$stamp = (Get-Date -Format 'yyyy-MM-ddTHH-mm-ssZ')
|
||||
$migDir = Join-Path $bayDir 'migrated'
|
||||
$migStamp = Join-Path $migDir $stamp
|
||||
if (-not (Test-Path -LiteralPath $migDir)) { New-Item -ItemType Directory -Path $migDir -Force | Out-Null }
|
||||
if (-not (Test-Path -LiteralPath $migStamp)) { New-Item -ItemType Directory -Path $migStamp -Force | Out-Null }
|
||||
|
||||
Move-Item -LiteralPath $srcCur -Destination (Join-Path $migStamp 'CurrentData.json') -Force -ErrorAction Stop
|
||||
if (Test-Path -LiteralPath $srcArc) {
|
||||
Move-Item -LiteralPath $srcArc -Destination (Join-Path $migStamp 'ArchivedData') -Force -ErrorAction Stop
|
||||
}
|
||||
$bakManifest = Join-Path $bayDir 'backup.manifest.json'
|
||||
if (Test-Path -LiteralPath $bakManifest) {
|
||||
Move-Item -LiteralPath $bakManifest -Destination (Join-Path $migStamp 'backup.manifest.json') -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Audit manifest in migrated/<stamp>/
|
||||
$localArcInfo = if (Test-Path $localArc) { Get-ChildItem $localArc -Recurse -File -ErrorAction SilentlyContinue } else { @() }
|
||||
$restoreManifest = [ordered]@{
|
||||
RestoredAt = (Get-Date -Format 'o')
|
||||
DestinationHostname = $env:COMPUTERNAME
|
||||
DestinationUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
||||
MachineNumber = $NewNumber
|
||||
CurrentDataBytes = (Get-Item (Join-Path $localUdcDir 'CurrentData.json')).Length
|
||||
ArchivedDataFiles = $localArcInfo.Count
|
||||
ArchivedDataBytes = ($localArcInfo | Measure-Object Length -Sum).Sum
|
||||
RestoredVia = 'Update-MachineNumber.ps1'
|
||||
}
|
||||
$restoreManifest | ConvertTo-Json | Set-Content -Path (Join-Path $migStamp 'restore.manifest.json') -Encoding UTF8
|
||||
|
||||
$out.UdcRestored = $true
|
||||
Write-Host " Update-MachineNumber: UDC restore OK (consumed -> migrated\$stamp\)"
|
||||
} else {
|
||||
Write-Host " Update-MachineNumber: no UDC backup at $bayDir (fresh PC, no prior data)"
|
||||
}
|
||||
} finally {
|
||||
& net use W: /delete /y 2>$null | Out-Null
|
||||
}
|
||||
} else {
|
||||
Write-Host " Update-MachineNumber: UDC backup share unreachable - skipping UDC restore."
|
||||
}
|
||||
} catch {
|
||||
$out.Errors += "UDC restore failed: $_"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Stop UDC before editing its JSON (avoid stale shutdown write) ---
|
||||
@@ -137,5 +264,61 @@ function Update-MachineNumber {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Update MTConnect Devices.xml (per-variant) + restart agent services ---
|
||||
# MTConnect deploys via the GE-Enforce manifest engine + the
|
||||
# Install_MTConnect_ExisDir_BatConvert.ps1 wrapper. Devices.xml on disk
|
||||
# has the machine number embedded in the <Device name= uuid= id=> attrs.
|
||||
# When the tech changes 9999 -> real number, those attrs need to follow,
|
||||
# OR MTConnect data will keep reporting under the old/wrong identity.
|
||||
#
|
||||
# We do an inline substitution rather than re-running the full wrapper
|
||||
# because the tech may not have credential delegation to the SFLD share
|
||||
# from their interactive session.
|
||||
$out.MTConnectUpdated = @()
|
||||
# Drive this off "is the service installed?" rather than file existence:
|
||||
# Fanuc and Okuma both deploy to C:\MTConnect\Agent\, distinguishable only
|
||||
# by the registered service name (NTFS is case-insensitive so the two
|
||||
# devices.xml / Devices.xml entries collapse to the same file). Without
|
||||
# this filter, the Okuma branch on an Okuma PC sees the file already
|
||||
# rewritten by the (no-op) Fanuc branch and skips the service restart.
|
||||
$mtcVariants = @(
|
||||
@{ Service='MTConnect Agent Fanuc'; Path='C:\MTConnect\Agent\devices.xml' },
|
||||
@{ Service='MTConnect Agent Okuma'; Path='C:\MTConnect\Agent\Devices.xml' },
|
||||
@{ Service='MTConnect eDNC Agent'; Path='C:\MTConnect_eDNC\Agent\Devices.xml' },
|
||||
@{ Service='Makino MTConnect Agent'; Path='C:\Makino-MTConnect\Agent\Devices.xml' }
|
||||
)
|
||||
foreach ($v in $mtcVariants) {
|
||||
$svc = Get-Service -Name $v.Service -ErrorAction SilentlyContinue
|
||||
if (-not $svc) { continue }
|
||||
if (-not (Test-Path -LiteralPath $v.Path)) { continue }
|
||||
try {
|
||||
$content = Get-Content -LiteralPath $v.Path -Raw -ErrorAction Stop
|
||||
# Find the Device root's name= attr to discover the OLD identifier
|
||||
if ($content -notmatch '<Device[^>]+name="([^"]+)"') { continue }
|
||||
$oldName = $matches[1]
|
||||
# Most variants encode machine number as the trailing digit run:
|
||||
# Fanuc/Makino: name="9999" -> trailing 9999
|
||||
# OKUMA: name="loc9999" -> trailing 9999, prefix 'loc'
|
||||
# eDNC: name="eDNC_OKUMA9999" -> trailing 9999, prefix 'eDNC_OKUMA'
|
||||
if ($oldName -notmatch '^(.*?)(\d+)$') { continue }
|
||||
$prefix = $matches[1]
|
||||
$oldDigits = $matches[2]
|
||||
if ($oldDigits -eq $NewNumber) { continue } # already correct
|
||||
$oldFull = "$prefix$oldDigits"
|
||||
$newFull = "$prefix$NewNumber"
|
||||
$oldEsc = [regex]::Escape($oldFull)
|
||||
# Quoted attr value: name="X" / uuid="X" / id="X"
|
||||
$content = $content -replace ('"' + $oldEsc + '"'), ('"' + $newFull + '"')
|
||||
# OKUMA-style with serial after dot: uuid="locX.<serial>"
|
||||
$content = $content -replace ('"' + $oldEsc + '\.'), ('"' + $newFull + '.')
|
||||
Set-Content -LiteralPath $v.Path -Value $content -NoNewline -ErrorAction Stop
|
||||
$out.MTConnectUpdated += "$($v.Path) ($oldFull -> $newFull)"
|
||||
try { Restart-Service -Name $v.Service -Force -ErrorAction Stop }
|
||||
catch { $out.Errors += "Restart $($v.Service) failed: $_" }
|
||||
} catch {
|
||||
$out.Errors += "MTConnect Devices.xml update failed for $($v.Path): $_"
|
||||
}
|
||||
}
|
||||
|
||||
return $out
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
|
||||
"common": {
|
||||
"_comment": "Cross-PC-type share paths used by logon enforcers (Acrobat-Enforce, future analogues). One SFLD share path per app; enforcer mounts the share with SFLD creds from HKLM:\\SOFTWARE\\GE\\SFLD\\Credentials and applies acrobat-manifest.json etc.",
|
||||
"_comment": "Cross-PC-type share paths used by GE-Enforce. The dispatcher mounts the share with SFLD creds from HKLM:\\SOFTWARE\\GE\\SFLD\\Credentials and applies common/manifest.json (and per-pctype manifests below) on every user logon.",
|
||||
"commonAppsSharePath": "\\\\tsgwp00525.wjs.geaerospace.net\\shared\\dt\\shopfloor\\common\\apps"
|
||||
},
|
||||
|
||||
@@ -78,13 +78,15 @@
|
||||
},
|
||||
|
||||
"Standard-Machine": {
|
||||
"machineappsSharePath": "\\\\tsgwp00525.wjs.geaerospace.net\\shared\\dt\\shopfloor\\main\\machineapps",
|
||||
"ntlarsBackupSharePath": "\\\\tsgwp00525.wjs.geaerospace.net\\shared\\dt\\shopfloor\\main\\ntlars-backups",
|
||||
"machineappsSharePath": "\\\\tsgwp00525.wjs.geaerospace.net\\shared\\dt\\shopfloor\\main\\machineapps",
|
||||
"ntlarsBackupSharePath": "\\\\tsgwp00525.wjs.geaerospace.net\\shared\\dt\\shopfloor\\main\\ntlars-backups",
|
||||
"udcBackupSharePath": "\\\\tsgwp00525.wjs.geaerospace.net\\shared\\dt\\shopfloor\\backup\\udc",
|
||||
"udcSettingsSharePath": "\\\\tsgwp00525.wjs.geaerospace.net\\shared\\spc\\udc\\settings_backups",
|
||||
"_startupItems_comment": "UDC removed 2026-04-28 - the UDC vendor installer registers UDC.exe in HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Run, which Task Manager surfaces as a Startup item. Our duplicate Startup\\UDC.lnk created a race-condition single-instance conflict (Run key fires from Explorer init, our .lnk fires slightly later, the second launch silently exits) that left UDC running in a not-fully-attached session and invisible to the ShopFloor user. Vendor autostart is the canonical autostart - we no longer add our own.",
|
||||
"startupItems": [
|
||||
{ "label": "WJ Shopfloor", "type": "existing", "sourceLnk": "WJ Shopfloor.lnk" },
|
||||
{ "label": "Plant Apps", "type": "url", "urlKey": "plantApps" },
|
||||
{ "label": "eDNC", "type": "exe", "target": "C:\\Program Files (x86)\\Dnc\\bin\\DncMain.exe" },
|
||||
{ "label": "UDC", "type": "exe", "target": "C:\\Program Files\\UDC\\UDC.exe" }
|
||||
{ "label": "eDNC", "type": "exe", "target": "C:\\Program Files (x86)\\Dnc\\bin\\DncMain.exe" }
|
||||
],
|
||||
"taskbarPins": [
|
||||
{ "name": "Microsoft Edge", "lnkPath": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\Microsoft Edge.lnk" },
|
||||
@@ -104,7 +106,7 @@
|
||||
},
|
||||
|
||||
"CMM": {
|
||||
"_comment": "Hexagon CMM apps (CLM 1.8, goCMM, PC-DMIS 2016, PC-DMIS 2019 R2). At imaging time they install from a WinPE-staged local bootstrap at C:\\CMM-Install (put there by startnet.cmd when pc-type=CMM, source is the PXE server enrollment share). Post-imaging, the 'GE CMM Enforce' scheduled task runs CMM-Enforce.ps1 on user logon and enforces versions against the tsgwp00525 share below (the SFLD creds Azure DSC provisions unlock the mount). cmmSharePath is the ongoing-enforcement source, not the imaging-time source.",
|
||||
"_comment": "Hexagon CMM apps (CLM 1.8, goCMM, PC-DMIS 2016, PC-DMIS 2019 R2). At imaging time they install from a WinPE-staged local bootstrap at C:\\CMM-Install (put there by startnet.cmd when pc-type=CMM, source is the PXE server enrollment share). Post-imaging, the unified GE-Enforce dispatcher reads cmm/manifest.json on the tsgwp00525 share below and enforces versions on every user logon (the SFLD creds Azure DSC provisions unlock the mount). cmmSharePath is the ongoing-enforcement source, not the imaging-time source.",
|
||||
"cmmSharePath": "\\\\tsgwp00525.wjs.geaerospace.net\\shared\\dt\\shopfloor\\cmm\\machineapps",
|
||||
"startupItems": [],
|
||||
"taskbarPins": [
|
||||
@@ -153,7 +155,7 @@
|
||||
},
|
||||
|
||||
"Keyence": {
|
||||
"_comment": "Keyence VR-6000 microscope/profilometer PCs. At imaging time, 09-Setup-Keyence.ps1 installs VR-6000 Series Software MSI + KEYENCE VR USB driver from the WinPE-staged shopfloor-setup\\Keyence\\ bundle. Post-imaging, the 'GE Keyence Enforce' scheduled task runs Keyence-Enforce.ps1 on user logon and enforces versions against the tsgwp00525 share below (SFLD creds provisioned by Azure DSC unlock the mount). keyenceSharePath is the ongoing-enforcement source; bump the manifest + MSI on the share to push updates fleet-wide.",
|
||||
"_comment": "Keyence VR-6000 microscope/profilometer PCs. At imaging time, 09-Setup-Keyence.ps1 installs VR-6000 Series Software MSI + KEYENCE VR USB driver from the WinPE-staged shopfloor-setup\\Keyence\\ bundle. Post-imaging, the unified GE-Enforce dispatcher reads keyence/manifest.json on the tsgwp00525 share below and enforces versions on every user logon (SFLD creds provisioned by Azure DSC unlock the mount). keyenceSharePath is the ongoing-enforcement source; bump the manifest + MSI on the share to push updates fleet-wide.",
|
||||
"keyenceSharePath": "\\\\tsgwp00525.wjs.geaerospace.net\\shared\\dt\\shopfloor\\keyence\\machineapps",
|
||||
"startupItems": [
|
||||
{ "label": "WJ Shopfloor", "type": "existing", "sourceLnk": "WJ Shopfloor.lnk" }
|
||||
|
||||
Reference in New Issue
Block a user