Wax/Trace triad: relocate backup path + harden service enum against SCM hangs

Two operator-driven fixes.

1. Backup target moves from S:\2 WJ Scans Record Retention\backup\waxtrace
   to S:\DT\Shopfloor\backup\waxandtrace per the canonical SFLD layout.
   Backup creates the per-asset folder if missing; Install reads from the
   same path by default.

2. Export-FormtracepakInventory hung on step [5/5] when run on a shopfloor
   PC. The original `Get-Service | Where-Object { DisplayName -match ... }`
   pattern materializes every service via the Service Control Manager + post
   filters in PowerShell, which can block indefinitely when any single
   service (Sentinel HASP driver, GE-Enforce agent, etc.) is in a degraded
   state. Two-part fix:
   - Switch to Get-Service -DisplayName 'Mitutoyo*','*FORMTRACEPAK*',...
     so the SCM only materializes matching services (server-side wildcard
     filter, faster + lower blast radius).
   - Wrap the enumeration in Start-Job + Wait-Job -Timeout 30 so a
     degraded SCM aborts gracefully with a warning rather than wedging
     the whole inventory pass.

Smoke tested on win11 VM: full Export run with the new code completes in
2.9 s and emits the inventory CSV correctly.
This commit is contained in:
cproudlock
2026-05-24 08:51:30 -04:00
parent ed12988591
commit fce6680c6f
3 changed files with 31 additions and 7 deletions

View File

@@ -16,13 +16,13 @@
Asset number (e.g. WJRP1234) used to namespace the backup on the shopfloor Asset number (e.g. WJRP1234) used to namespace the backup on the shopfloor
share. If omitted, the script prompts interactively and defaults to the share. If omitted, the script prompts interactively and defaults to the
current $env:COMPUTERNAME. The asset number is the leaf folder under current $env:COMPUTERNAME. The asset number is the leaf folder under
S:\2 WJ Scans Record Retention\backup\waxtrace\ S:\DT\Shopfloor\backup\waxandtrace\
Ignored if -Destination is supplied. Ignored if -Destination is supplied.
.PARAMETER Destination .PARAMETER Destination
Where to write the ZIP file. Can be a USB drive, network share, or local path. 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 mapped S: drive: If omitted, defaults to the per-asset path on the mapped S: drive:
S:\2 WJ Scans Record Retention\backup\waxtrace\<AssetNumber> S:\DT\Shopfloor\backup\waxandtrace\<AssetNumber>
The destination directory is created if it does not exist. The destination directory is created if it does not exist.
.PARAMETER NoZip .PARAMETER NoZip
@@ -45,7 +45,7 @@ param(
[switch]$IncludeExecutables [switch]$IncludeExecutables
) )
$SharedRoot = 'S:\2 WJ Scans Record Retention\backup\waxtrace' $SharedRoot = 'S:\DT\Shopfloor\backup\waxandtrace'
# Resolve the script's own directory robustly. $PSScriptRoot can come through # Resolve the script's own directory robustly. $PSScriptRoot can come through
# empty in some hosts (IEX / dot-source / certain remote wrappers), which # empty in some hosts (IEX / dot-source / certain remote wrappers), which

View File

@@ -304,8 +304,32 @@ foreach ($pp in $processPatterns) {
} }
} }
$services = Get-Service -ErrorAction SilentlyContinue | # Service enumeration. The original `Get-Service | Where-Object { DisplayName -match ... }`
Where-Object { $_.DisplayName -match 'Mitutoyo|FORMTRACEPAK|FORMPAK|SURFPAK' } # materializes every service via the Service Control Manager + post-filters
# in PowerShell and has been observed to hang for minutes on shopfloor PCs
# where the SCM or a single service is in a degraded state (Sentinel HASP
# driver / GE-Enforce agent service have both blocked Get-Service before).
# Replace with -DisplayName server-side wildcard filter (the SCM only
# materializes matching services) and wrap in a 30-second runspace fence so
# any further hang aborts gracefully without taking the whole script down.
$serviceJob = Start-Job -ScriptBlock {
Get-Service -DisplayName 'Mitutoyo*','*FORMTRACEPAK*','*FORMPAK*','*SURFPAK*' -ErrorAction SilentlyContinue |
ForEach-Object {
[PSCustomObject]@{
Name = $_.Name
DisplayName = $_.DisplayName
Status = [string]$_.Status
}
}
}
$services = @()
if (Wait-Job -Job $serviceJob -Timeout 30) {
$services = @(Receive-Job -Job $serviceJob -ErrorAction SilentlyContinue)
} else {
Write-Warning " Service enumeration exceeded 30s timeout - skipping (SCM degraded?)"
Stop-Job -Job $serviceJob -ErrorAction SilentlyContinue
}
Remove-Job -Job $serviceJob -Force -ErrorAction SilentlyContinue
foreach ($svc in $services) { foreach ($svc in $services) {
Add-Item -Category 'Service' -ItemType 'Service' ` Add-Item -Category 'Service' -ItemType 'Service' `
-Path $svc.Name -Name $svc.DisplayName ` -Path $svc.Name -Name $svc.DisplayName `

View File

@@ -21,7 +21,7 @@
Path to the backup ZIP, directory containing a backup ZIP, or already-extracted Path to the backup ZIP, directory containing a backup ZIP, or already-extracted
backup folder created by Backup-FormtracepakSettings.ps1. If omitted, defaults backup folder created by Backup-FormtracepakSettings.ps1. If omitted, defaults
to the per-asset path on the mapped S: drive: to the per-asset path on the mapped S: drive:
S:\2 WJ Scans Record Retention\backup\waxtrace\<AssetNumber> S:\DT\Shopfloor\backup\waxandtrace\<AssetNumber>
If the path is a directory, the newest formtracepak_backup_*.zip inside it is used. If the path is a directory, the newest formtracepak_backup_*.zip inside it is used.
.PARAMETER RestoreRegistry .PARAMETER RestoreRegistry
@@ -60,7 +60,7 @@ param(
[switch]$Force [switch]$Force
) )
$SharedRoot = 'S:\2 WJ Scans Record Retention\backup\waxtrace' $SharedRoot = 'S:\DT\Shopfloor\backup\waxandtrace'
# Resolve the script's own directory robustly. $PSScriptRoot can come through # Resolve the script's own directory robustly. $PSScriptRoot can come through
# empty in some hosts (IEX / dot-source / certain remote wrappers), which # empty in some hosts (IEX / dot-source / certain remote wrappers), which