Wax/Trace triad: arrow-key bay picker + S: backup path

Two operator-UX improvements for the Backup / Export / Install triad.

1. Backup target moves from \\tsgwp00525\...\formtracepac to S:\2 WJ Scans
   Record Retention\backup\waxtrace\<asset>\. S: is mapped at shopfloor
   imaging time and stays mapped post-categorization, so the same default
   path works whether the operator runs the backup on an old bay (manual
   pre-image capture) or a freshly imaged one. The destination directory
   is created if missing.

2. New Select-WaxtraceAsset.ps1 - arrow-key bay picker patterned after
   the WinPE select-waxtrace-asset.ps1. Reads bay-config.csv (sibling
   file), shows asset_tag + ftpak_version + model + user_id per row, and
   returns the selected asset_tag via stdout. Falls back to a manual
   entry prompt if the CSV is missing or the operator picks "Other".

   Backup / Export / Install now invoke the picker when interactive AND
   bay-config.csv is alongside the script. Non-interactive paths
   (qga / SYSTEM / scheduled task) keep silently defaulting to
   COMPUTERNAME so unattended runs are unchanged.

   Export gained an -AssetNumber parameter and stamps it into the output
   CSV filename so multiple inventories from the same host stay
   distinguishable when the operator is auditing several bays in a row.

bay-config.csv is copied into the scripts\ dir so the picker has a
source of truth that ships next to the scripts (and into pxe-images
for tech distribution).

Smoke tested on win11 VM: all four PS1 parse-clean, non-interactive
backup path still produces a valid ZIP (silent COMPUTERNAME default),
picker handles missing-CSV gracefully (manual-entry fallback). The
arrow-key UX itself is operator-verifiable only on a real terminal.
This commit is contained in:
cproudlock
2026-05-24 07:41:25 -04:00
parent a104cfdebb
commit b8bb00e2fe
5 changed files with 200 additions and 20 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
\\tsgwp00525.wjs.geaerospace.net\shared\DT\Shopfloor\backup\formtracepac\ S:\2 WJ Scans Record Retention\backup\waxtrace\
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 GE shopfloor share: If omitted, defaults to the per-asset path on the mapped S: drive:
\\tsgwp00525.wjs.geaerospace.net\shared\DT\Shopfloor\backup\formtracepac\<AssetNumber> S:\2 WJ Scans Record Retention\backup\waxtrace\<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,17 +45,30 @@ param(
[switch]$IncludeExecutables [switch]$IncludeExecutables
) )
$SharedRoot = '\\tsgwp00525.wjs.geaerospace.net\shared\DT\Shopfloor\backup\formtracepac' $SharedRoot = 'S:\2 WJ Scans Record Retention\backup\waxtrace'
if (-not $Destination) { if (-not $Destination) {
if (-not $AssetNumber) { if (-not $AssetNumber) {
$defaultAsset = $env:COMPUTERNAME $defaultAsset = $env:COMPUTERNAME
if ([Environment]::UserInteractive -and -not [Console]::IsInputRedirected) { if ([Environment]::UserInteractive -and -not [Console]::IsInputRedirected) {
$input = Read-Host "Asset number for this backup (e.g. WJRP1234) [$defaultAsset]" # Prefer arrow-key picker against bay-config.csv if it is sitting
if ([string]::IsNullOrWhiteSpace($input)) { # next to this script. Falls through to a plain Read-Host prompt
$AssetNumber = $defaultAsset # if no picker / no CSV available.
} else { $picker = Join-Path $PSScriptRoot 'Select-WaxtraceAsset.ps1'
$AssetNumber = $input.Trim() $cfg = Join-Path $PSScriptRoot 'bay-config.csv'
if ((Test-Path -LiteralPath $picker) -and (Test-Path -LiteralPath $cfg)) {
$picked = & $picker -ConfigCsv $cfg -Title 'Backup FormTracePak - pick the bay being captured'
if ($picked) {
$AssetNumber = $picked
}
}
if (-not $AssetNumber) {
$input = Read-Host "Asset number for this backup (e.g. WJRP1234) [$defaultAsset]"
if ([string]::IsNullOrWhiteSpace($input)) {
$AssetNumber = $defaultAsset
} else {
$AssetNumber = $input.Trim()
}
} }
} else { } else {
# Non-interactive (qga / SYSTEM / scheduled task): fall back to COMPUTERNAME silently. # Non-interactive (qga / SYSTEM / scheduled task): fall back to COMPUTERNAME silently.

View File

@@ -15,16 +15,46 @@
.PARAMETER ComputerName .PARAMETER ComputerName
Hostname to stamp on the inventory rows. Defaults to $env:COMPUTERNAME. Hostname to stamp on the inventory rows. Defaults to $env:COMPUTERNAME.
.PARAMETER AssetNumber
Asset number (e.g. WJRP1234) used in the output CSV filename so multiple
inventories from the same host stay distinguishable. If omitted, the script
prompts interactively (with arrow-key picker if bay-config.csv is alongside)
and defaults to $ComputerName non-interactively.
.EXAMPLE .EXAMPLE
.\Export-FormtracepakInventory.ps1 .\Export-FormtracepakInventory.ps1
.\Export-FormtracepakInventory.ps1 -OutputPath "E:\inventories" .\Export-FormtracepakInventory.ps1 -OutputPath "E:\inventories" -AssetNumber WJRP2335
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[string]$OutputPath = $PSScriptRoot, [string]$OutputPath = $PSScriptRoot,
[string]$ComputerName = $env:COMPUTERNAME [string]$ComputerName = $env:COMPUTERNAME,
[string]$AssetNumber
) )
if (-not $AssetNumber) {
if ([Environment]::UserInteractive -and -not [Console]::IsInputRedirected) {
$picker = Join-Path $PSScriptRoot 'Select-WaxtraceAsset.ps1'
$cfg = Join-Path $PSScriptRoot 'bay-config.csv'
if ((Test-Path -LiteralPath $picker) -and (Test-Path -LiteralPath $cfg)) {
$picked = & $picker -ConfigCsv $cfg -Title 'Inventory FormTracePak - pick the bay being audited'
if ($picked) {
$AssetNumber = $picked
}
}
if (-not $AssetNumber) {
$assetInput = Read-Host "Asset number to stamp on inventory (e.g. WJRP1234) [$ComputerName]"
if ([string]::IsNullOrWhiteSpace($assetInput)) {
$AssetNumber = $ComputerName
} else {
$AssetNumber = $assetInput.Trim()
}
}
} else {
$AssetNumber = $ComputerName
}
}
Set-StrictMode -Version Latest Set-StrictMode -Version Latest
$ErrorActionPreference = 'Continue' $ErrorActionPreference = 'Continue'
@@ -98,7 +128,7 @@ $MitutoyoFileExtensions = @(
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss' $timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$csvFile = Join-Path $OutputPath "formtracepak_inventory_${ComputerName}_${timestamp}.csv" $csvFile = Join-Path $OutputPath "formtracepak_inventory_${AssetNumber}_${ComputerName}_${timestamp}.csv"
$inventory = [System.Collections.Generic.List[PSObject]]::new() $inventory = [System.Collections.Generic.List[PSObject]]::new()
function Add-Item { function Add-Item {

View File

@@ -20,8 +20,8 @@
.PARAMETER BackupPath .PARAMETER BackupPath
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 GE shopfloor share: to the per-asset path on the mapped S: drive:
\\tsgwp00525.wjs.geaerospace.net\shared\DT\Shopfloor\backup\formtracepac\<AssetNumber> S:\2 WJ Scans Record Retention\backup\waxtrace\<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,17 +60,28 @@ param(
[switch]$Force [switch]$Force
) )
$SharedRoot = '\\tsgwp00525.wjs.geaerospace.net\shared\DT\Shopfloor\backup\formtracepac' $SharedRoot = 'S:\2 WJ Scans Record Retention\backup\waxtrace'
if (-not $BackupPath) { if (-not $BackupPath) {
if (-not $AssetNumber) { if (-not $AssetNumber) {
$defaultAsset = $env:COMPUTERNAME $defaultAsset = $env:COMPUTERNAME
if ([Environment]::UserInteractive -and -not [Console]::IsInputRedirected) { if ([Environment]::UserInteractive -and -not [Console]::IsInputRedirected) {
$assetInput = Read-Host "Asset number to restore (e.g. WJRP1234) [$defaultAsset]" # Prefer arrow-key picker against bay-config.csv if available.
if ([string]::IsNullOrWhiteSpace($assetInput)) { $picker = Join-Path $PSScriptRoot 'Select-WaxtraceAsset.ps1'
$AssetNumber = $defaultAsset $cfg = Join-Path $PSScriptRoot 'bay-config.csv'
} else { if ((Test-Path -LiteralPath $picker) -and (Test-Path -LiteralPath $cfg)) {
$AssetNumber = $assetInput.Trim() $picked = & $picker -ConfigCsv $cfg -Title 'Restore FormTracePak - pick the bay being restored'
if ($picked) {
$AssetNumber = $picked
}
}
if (-not $AssetNumber) {
$assetInput = Read-Host "Asset number to restore (e.g. WJRP1234) [$defaultAsset]"
if ([string]::IsNullOrWhiteSpace($assetInput)) {
$AssetNumber = $defaultAsset
} else {
$AssetNumber = $assetInput.Trim()
}
} }
} else { } else {
$AssetNumber = $defaultAsset $AssetNumber = $defaultAsset

View File

@@ -0,0 +1,110 @@
<#
.SYNOPSIS
Arrow-key bay picker for Wax/Trace Export / Backup / Install scripts.
.DESCRIPTION
Reads bay-config.csv (asset_tag,ftpak_version,model,user_id) and presents
an Up/Down picker. Returns the chosen asset_tag to stdout so the calling
script can capture it. Always appends an "Other (manual entry)" option.
Same UX as the WinPE select-waxtrace-asset.ps1 used by startnet.cmd.
Distinct from that script because it returns the asset tag via stdout
rather than writing to a file, and presents the richer per-bay columns
(FTPak version + model + USER ID) from bay-config.csv.
.PARAMETER ConfigCsv
Path to bay-config.csv. Defaults to a sibling file next to this script.
.PARAMETER Title
Header text shown above the picker. Defaults to "Wax/Trace Asset".
.OUTPUTS
String. The chosen asset_tag, printed to stdout. Empty string on cancel.
.EXAMPLE
$asset = & .\Select-WaxtraceAsset.ps1
$asset = & .\Select-WaxtraceAsset.ps1 -ConfigCsv 'C:\foo\bay-config.csv' -Title 'Pick bay to back up'
#>
[CmdletBinding()]
param(
[string]$ConfigCsv = (Join-Path $PSScriptRoot 'bay-config.csv'),
[string]$Title = 'Wax/Trace Asset'
)
$ErrorActionPreference = 'Continue'
function Read-BayList {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) { return @() }
try {
return Import-Csv -LiteralPath $Path |
Sort-Object -Property asset_tag
} catch {
return @()
}
}
function Show-Menu {
param([object[]]$Items, [int]$Selected, [string]$Title)
Clear-Host
Write-Host ''
Write-Host ' ============================================================'
Write-Host " $Title"
Write-Host ' ============================================================'
Write-Host ''
Write-Host ' Up / Down arrows = navigate, Enter = select, Esc = cancel'
Write-Host ''
Write-Host (' {0,-10} {1,-8} {2,-10} {3}' -f 'ASSET', 'FTPAK', 'MODEL', 'USER ID')
Write-Host (' {0,-10} {1,-8} {2,-10} {3}' -f '-----', '-----', '-----', '-------')
for ($i = 0; $i -lt $Items.Count; $i++) {
$item = $Items[$i]
if ($item -is [string]) {
$line = $item
} else {
$line = '{0,-10} {1,-8} {2,-10} {3}' -f $item.asset_tag, $item.ftpak_version, $item.model, $item.user_id
}
if ($i -eq $Selected) {
Write-Host (' > ' + $line) -ForegroundColor Black -BackgroundColor White
} else {
Write-Host (' ' + $line)
}
}
Write-Host ''
}
$bays = @(Read-BayList -Path $ConfigCsv)
$menuItems = @()
foreach ($b in $bays) { $menuItems += $b }
$menuItems += '** Other (enter asset tag manually) **'
if ($menuItems.Count -eq 1) {
# Only the manual-entry sentinel: CSV missing or unparseable. Fall back
# to a plain prompt rather than wedging the operator in an empty menu.
Write-Host " (bay-config.csv not readable at $ConfigCsv - falling back to manual entry)"
$manual = Read-Host " Enter asset tag (e.g. WJRP9999) or blank to abort"
if ($manual) { return $manual.Trim().ToUpper() } else { return '' }
}
$sel = 0
while ($true) {
Show-Menu -Items $menuItems -Selected $sel -Title $Title
$key = [System.Console]::ReadKey($true)
switch ($key.Key) {
'UpArrow' { if ($sel -gt 0) { $sel-- } }
'DownArrow' { if ($sel -lt ($menuItems.Count - 1)) { $sel++ } }
'Enter' {
if ($sel -eq ($menuItems.Count - 1)) {
Write-Host ''
$manual = Read-Host ' Enter asset tag (e.g. WJRP9999) or blank to abort'
if ($manual) {
return $manual.Trim().ToUpper()
} else {
return ''
}
} else {
return $bays[$sel].asset_tag
}
}
'Escape' { return '' }
}
}

View File

@@ -0,0 +1,16 @@
asset_tag,ftpak_version,model,user_id
WJF00159,6.103,AVANT,3974839712
WJRP3689,5.510,CV-4500,3744284509
WJRP2660,6.0,CV-4500,2365986521
WJRP2659,6.0,CV-4500,2709054503
WJF00545,6.213,AVANT,3878777362
WJRP3638,5.602,CV-4500,0720778210
WJF00052,6.103,AVANT,3270314998
WJF00084,6.103,AVANT,1476212857
WJF00083,6.103,AVANT,2934506987
WJRP3025,6.0,CV-3200,2292822471
WJF00197,6.104,AVANT,1191612605
WJRP4802,6.002,AVANT,0920866935
WJRP2347,6.0,CV-4500,3585172946
WJRP2035,6.0,CV-4500,2292822471
WJRP2335,6.0,CV-4500,1780916688
1 asset_tag ftpak_version model user_id
2 WJF00159 6.103 AVANT 3974839712
3 WJRP3689 5.510 CV-4500 3744284509
4 WJRP2660 6.0 CV-4500 2365986521
5 WJRP2659 6.0 CV-4500 2709054503
6 WJF00545 6.213 AVANT 3878777362
7 WJRP3638 5.602 CV-4500 0720778210
8 WJF00052 6.103 AVANT 3270314998
9 WJF00084 6.103 AVANT 1476212857
10 WJF00083 6.103 AVANT 2934506987
11 WJRP3025 6.0 CV-3200 2292822471
12 WJF00197 6.104 AVANT 1191612605
13 WJRP4802 6.002 AVANT 0920866935
14 WJRP2347 6.0 CV-4500 3585172946
15 WJRP2035 6.0 CV-4500 2292822471
16 WJRP2335 6.0 CV-4500 1780916688