Wax/Trace triad: harden against empty $PSScriptRoot

Tech ran Export-FormtracepakInventory.ps1 from S:\DT\shopfloor\scripts\
waxandtrace\ and the picker fired correctly but Export-Csv failed with
'Cannot bind argument to parameter Path because it is an empty string'.
Root cause: $OutputPath defaulted to $PSScriptRoot and $PSScriptRoot came
through empty in that invocation path (suspected ISE / IEX-style host or
remote wrapper). On a [string] param, $null/empty default coerces to ''
and Join-Path then errors.

Fix in all three triad scripts: resolve a local $scriptDir via a fallback
chain ($PSScriptRoot -> $PSCommandPath -> Get-Location), and use that
instead of $PSScriptRoot for sibling lookups (Select-WaxtraceAsset.ps1,
bay-config.csv).

Export additionally:
- Drops the $OutputPath = $PSScriptRoot param default in favor of the
  same fallback chain.
- Tests / creates $OutputPath BEFORE the 90k-item registry scan so a bad
  output dir surfaces immediately instead of after a long scan.

Smoke tested on win11 VM: explicit -OutputPath '' now resolves to a
writable directory and the CSV writes successfully.
This commit is contained in:
cproudlock
2026-05-24 08:00:00 -04:00
parent b8bb00e2fe
commit ed12988591
3 changed files with 54 additions and 8 deletions

View File

@@ -47,6 +47,13 @@ param(
$SharedRoot = 'S:\2 WJ Scans Record Retention\backup\waxtrace'
# Resolve the script's own directory robustly. $PSScriptRoot can come through
# empty in some hosts (IEX / dot-source / certain remote wrappers), which
# would silently skip the picker invocation below.
$scriptDir = if ($PSScriptRoot) { $PSScriptRoot } `
elseif ($PSCommandPath) { Split-Path -Parent $PSCommandPath } `
else { (Get-Location).Path }
if (-not $Destination) {
if (-not $AssetNumber) {
$defaultAsset = $env:COMPUTERNAME
@@ -54,8 +61,8 @@ if (-not $Destination) {
# Prefer arrow-key picker against bay-config.csv if it is sitting
# next to this script. Falls through to a plain Read-Host prompt
# if no picker / no CSV available.
$picker = Join-Path $PSScriptRoot 'Select-WaxtraceAsset.ps1'
$cfg = Join-Path $PSScriptRoot 'bay-config.csv'
$picker = Join-Path $scriptDir 'Select-WaxtraceAsset.ps1'
$cfg = Join-Path $scriptDir '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) {

View File

@@ -27,15 +27,35 @@
#>
[CmdletBinding()]
param(
[string]$OutputPath = $PSScriptRoot,
[string]$OutputPath,
[string]$ComputerName = $env:COMPUTERNAME,
[string]$AssetNumber
)
# $PSScriptRoot can come through empty in some PowerShell hosts (e.g. when
# the script is invoked via IEX, dot-sourced, or run through certain remote
# wrappers). The param default = $PSScriptRoot then coerces to '' on a [string]
# parameter and Join-Path later fails with "Cannot bind argument to parameter
# 'Path' because it is an empty string". Resolve $OutputPath lazily with a
# fallback chain so the script always lands on a writable directory.
if (-not $OutputPath) {
if ($PSScriptRoot) {
$OutputPath = $PSScriptRoot
} elseif ($PSCommandPath) {
$OutputPath = Split-Path -Parent $PSCommandPath
} else {
$OutputPath = (Get-Location).Path
}
}
$scriptDir = if ($PSScriptRoot) { $PSScriptRoot } `
elseif ($PSCommandPath) { Split-Path -Parent $PSCommandPath } `
else { (Get-Location).Path }
if (-not $AssetNumber) {
if ([Environment]::UserInteractive -and -not [Console]::IsInputRedirected) {
$picker = Join-Path $PSScriptRoot 'Select-WaxtraceAsset.ps1'
$cfg = Join-Path $PSScriptRoot 'bay-config.csv'
$picker = Join-Path $scriptDir 'Select-WaxtraceAsset.ps1'
$cfg = Join-Path $scriptDir '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) {
@@ -128,6 +148,18 @@ $MitutoyoFileExtensions = @(
# ----------------------------------------------------------------------
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
# Ensure $OutputPath exists / is creatable BEFORE doing the slow scan, so
# the operator does not sit through a 90k-item registry walk only to have
# the final Export-Csv fail.
if (-not (Test-Path -LiteralPath $OutputPath)) {
try {
New-Item -ItemType Directory -Path $OutputPath -Force -ErrorAction Stop | Out-Null
} catch {
Write-Error "OutputPath '$OutputPath' does not exist and cannot be created: $_"
exit 1
}
}
$csvFile = Join-Path $OutputPath "formtracepak_inventory_${AssetNumber}_${ComputerName}_${timestamp}.csv"
$inventory = [System.Collections.Generic.List[PSObject]]::new()

View File

@@ -62,13 +62,20 @@ param(
$SharedRoot = 'S:\2 WJ Scans Record Retention\backup\waxtrace'
# Resolve the script's own directory robustly. $PSScriptRoot can come through
# empty in some hosts (IEX / dot-source / certain remote wrappers), which
# would silently skip the picker invocation below.
$scriptDir = if ($PSScriptRoot) { $PSScriptRoot } `
elseif ($PSCommandPath) { Split-Path -Parent $PSCommandPath } `
else { (Get-Location).Path }
if (-not $BackupPath) {
if (-not $AssetNumber) {
$defaultAsset = $env:COMPUTERNAME
if ([Environment]::UserInteractive -and -not [Console]::IsInputRedirected) {
# Prefer arrow-key picker against bay-config.csv if available.
$picker = Join-Path $PSScriptRoot 'Select-WaxtraceAsset.ps1'
$cfg = Join-Path $PSScriptRoot 'bay-config.csv'
$picker = Join-Path $scriptDir 'Select-WaxtraceAsset.ps1'
$cfg = Join-Path $scriptDir 'bay-config.csv'
if ((Test-Path -LiteralPath $picker) -and (Test-Path -LiteralPath $cfg)) {
$picked = & $picker -ConfigCsv $cfg -Title 'Restore FormTracePak - pick the bay being restored'
if ($picked) {