private/functions/Invoke-CheckedCommand.ps1
|
<# .SYNOPSIS Runs a target command with the caller's arguments, untouched, and reports a failure. .DESCRIPTION Usage: Invoke-CheckedCommand <command path> @targetArgs Deliberately a simple function with no param block: the arguments must arrive by splat and leave by splat. Passing them through a declared [object[]] parameter reshapes a one-element array, which breaks a lone -Switch, a lone array argument and a lone $null. Splatting also keeps the marker PowerShell uses to bind '-Name' as a parameter name. Uses the call operator rather than Invoke-Expression, so metacharacters in arguments are never interpreted. .INPUTS None. You cannot pipe objects to this function. .OUTPUTS Whatever the target command outputs. #> function Invoke-CheckedCommand { $command = $args[0] $global:LASTEXITCODE = 0 $exitCode = 0 # Slice, do not copy element by element: see the description $targetArgs = @() if ($args.Count -gt 1) { $targetArgs = $args[1..($args.Count - 1)] } # '-Switch:$false' is the one form splatting cannot forward: it arrives as the two # arguments '-Switch:' and $false, and a switch does not take the second one, so the # target would see the switch as ON. Move such pairs into a named splat instead. # Only applies to PowerShell targets; an executable has no parameters to resolve. $switchValues = @{} $commandInfo = $null $i = 0 while ($i -lt ($targetArgs.Count - 1)) { $parameter = $null if ($targetArgs[$i] -is [string] -and $targetArgs[$i] -match '^-([^:]+):$') { if (-not $commandInfo) { $commandInfo = Get-Command -Name $command -ErrorAction SilentlyContinue } if ($commandInfo.Parameters) { try { $parameter = $commandInfo.ResolveParameter($Matches[1]) } catch { # Not a parameter of the target (or ambiguous): forward it untouched Write-Debug "Not resolving '$($targetArgs[$i])': $_" } } } if ($parameter -and $parameter.SwitchParameter) { $switchValues[$parameter.Name] = [bool]$targetArgs[$i + 1] # Remove the pair using slices only, for the same reason as above $head = @() $tail = @() if ($i -gt 0) { $head = $targetArgs[0..($i - 1)] } if (($i + 2) -lt $targetArgs.Count) { $tail = $targetArgs[($i + 2)..($targetArgs.Count - 1)] } $targetArgs = $head + $tail } else { $i++ } } & $command @switchValues @targetArgs $success = $? if (Test-Path VARIABLE:GLOBAL:LASTEXITCODE) { $exitCode = $GLOBAL:LASTEXITCODE; } else { if (Test-Path VARIABLE:LASTEXITCODE) { $exitCode = $LASTEXITCODE; } else { $exitCode = 0; } } if (!$success -or ($exitCode -ne 0)) { Write-Debug $("$command exited with error code " + $exitCode) # Extract just the command name for cleaner error message $cmdName = Split-Path -Leaf $command # Use Write-Host for clean output without stack trace Write-Host "$cmdName exited with error code $exitCode" -ForegroundColor Red # Set LASTEXITCODE so callers can check it $global:LASTEXITCODE = $exitCode } } |