Invoke-NativeApplication.psm1
|
$csPath = Join-Path -Path $PSScriptRoot -ChildPath 'OutputLine.cs' Add-Type -Path $csPath <# .SYNOPSIS Invokes a native application with proper STDERR handling and exit code validation. .DESCRIPTION Executes a native application (external command) via a ScriptBlock, captures both STDOUT and STDERR streams, and validates the process exit code. Each output line is returned as a InvokeNativeApplication.OutputLine object that behaves like a string but carries an IsError property indicating whether it originated from STDERR. When called from the PowerShell prompt, STDERR is not redirected so that it displays with the default console formatting. When called from a script, STDERR is captured via 2>&1 redirection. .PARAMETER ScriptBlock The script block containing the native application invocation. .PARAMETER ArgumentList A hashtable of arguments to splat into the script block. .PARAMETER AllowedExitCodes An array of exit codes considered successful. Defaults to @(0). .PARAMETER IgnoreExitCode When specified, the function does not throw on non-zero exit codes. .EXAMPLE Invoke-NativeApplication { git status } Runs 'git status' and throws if git returns a non-zero exit code. .EXAMPLE Invoke-NativeApplication { robocopy source dest /MIR } -AllowedExitCodes @(0, 1) Treats exit codes 0 and 1 as successful. .EXAMPLE Invoke-NativeApplication { robocopy source dest /MIR } -AllowedExitCodes (0..3) Treats exit codes 0 through 3 as successful using the range operator. .EXAMPLE Invoke-NativeApplication { robocopy source dest /MIR } -AllowedExitCodes ((0..3) + (8, 10) + (20..30)) Combines ranges with individual codes. Treats 0-3, 8, 10, and 20-30 as successful. .EXAMPLE $output = Invoke-NativeApplication { dotnet build } -IgnoreExitCode $errors = $output | Where-Object { $_.IsError } Captures all output including errors without throwing, then filters for lines that came from STDERR. .OUTPUTS InvokeNativeApplication.OutputLine One object per output line. Each behaves like a string but carries an IsError property indicating whether it originated from STDERR. .NOTES Alias: exec .LINK https://mnaoumov.wordpress.com/2015/01/11/execution-of-external-commands-in-powershell-done-right/ .LINK https://mnaoumov.wordpress.com/2015/03/31/execution-of-external-commands-native-applications-in-powershell-done-right-part-2/ #> function Invoke-NativeApplication { param ( [Parameter(Position=0)][ScriptBlock] $ScriptBlock, [Parameter(Position=1)][HashTable] $ArgumentList, [Parameter()][int[]] $AllowedExitCodes = @(0), [Parameter()][switch] $IgnoreExitCode ) $backupErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = "Continue" try { Write-Verbose ('Executing native application {0} with parameters: {1}' -f $ScriptBlock, ([PSCustomObject] $ArgumentList)) if (Test-CalledFromPrompt) { $wrapperScriptBlock = { & $ScriptBlock @ArgumentList }.GetNewClosure() } else { $wrapperScriptBlock = { & $ScriptBlock @ArgumentList 2>&1 }.GetNewClosure() } & $wrapperScriptBlock | ForEach-Object -Process { $isError = $_ -is [System.Management.Automation.ErrorRecord] if ($isError) { $message = $_.Exception.Message } else { $message = "$_" } New-Object -TypeName InvokeNativeApplication.OutputLine -ArgumentList $message, $isError } if ((-not $IgnoreExitCode) -and (Test-Path -Path Variable:LASTEXITCODE) -and ($AllowedExitCodes -notcontains $LASTEXITCODE)) { throw ('Native application {0} with parameters {1} failed at {2} with exit code {3}' -f $ScriptBlock, ([PSCustomObject] $ArgumentList), (Get-PSCallStack -ErrorAction SilentlyContinue)[1].Location, $LASTEXITCODE) } } finally { $ErrorActionPreference = $backupErrorActionPreference } } <# .SYNOPSIS Invokes a native application, ignoring exit codes and filtering out STDERR lines. .DESCRIPTION A convenience wrapper around Invoke-NativeApplication that always ignores the exit code and returns only STDOUT lines (lines where IsError is false). Useful when you want to silently capture the successful output of a command without error noise. .PARAMETER ScriptBlock The script block containing the native application invocation. .PARAMETER ArgumentList A hashtable of arguments to splat into the script block. .EXAMPLE $branches = Invoke-NativeApplicationSafe { git branch } Gets the list of git branches, ignoring any STDERR output and exit code. .OUTPUTS InvokeNativeApplication.OutputLine One object per STDOUT line. Each behaves like a string with IsError always set to False (STDERR lines are filtered out). .NOTES Alias: safeexec #> function Invoke-NativeApplicationSafe { param ( [Parameter(Position=0)][ScriptBlock] $ScriptBlock, [Parameter(Position=1)][HashTable] $ArgumentList ) Invoke-NativeApplication -ScriptBlock $ScriptBlock -IgnoreExitCode -ArgumentList $ArgumentList | Where-Object -FilterScript { -not $_.IsError } } function Test-CalledFromPrompt { foreach ($frame in Get-PSCallStack) { if ($frame.Command -eq "prompt") { return $true } } return $false } Set-Alias -Name exec -Value Invoke-NativeApplication Set-Alias -Name safeexec -Value Invoke-NativeApplicationSafe |