I am trying to run a command via PowerShell and capture its stdout and stderr without printing them on screen (command is incredibly noisy and pollutes the console).
I want to capture the stdout and stderr in a variable and then throw an exception if particular strings are found.
My logic seems to be working and I can make the cmdlet fail/pass when I expect it to, however the output does not match what I expect, instead of returning the error message that I am specifying I get what I believe is the stderr from the command instead?
(Simplified for easier reading)
First cmdlet:
function Test-Validation
{
[CmdletBinding()]
param(
[Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, Position = 0)]
[Array]
$ValidExitCodes = @(0),
[Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, Position = 1)]
[bool]
$FailOnWarning = $true
)
$Validation = . pdk validate 2>&1
if ($LASTEXITCODE -notin $ValidExitCodes)
{
throw "Module validation has failed. Exit code: $($LASTEXITCODE)."
}
$Warnings = $Validation -match 'pdk \(WARNING\)'
if (($Warnings) -and ($FailOnWarning -eq $true))
{
throw "Module validation contains warnings.`n$Warnings"
}
Write-Verbose "Module successfully passed validation"
}
Which is called by a second cmdlet:
function Test-Module
{
[CmdletBinding()]
param
()
try
{
Test-Validation
}
catch
{
throw "$($_.Exception.Message)"
}
try
{
Test-Unit
}
catch
{
throw "$($_.Exception.Message)"
}
}
PS /opt/script/test/> Test-Module
Exception: /opt/script/test/Test-Module.ps1:13:9
Line |
13 | throw "$($_.Exception.Message)"
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Module validation has failed. Exit code: 1.
PS /opt/script/test/> Test-Module
Exception: /opt/script/test/Test-Module.ps1:13:9
Line |
13 | throw "$($_.Exception.Message)"
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| pdk (INFO): Using Ruby 2.5.8
As you can see it seems to be returning the output from the command I'm running (pdk) instead of the "Module validation has failed. Exit code: $($LASTEXITCODE)."
message I am defining in the Test-Validation cmdlet.
Why am I not getting the error message I want and is there any way for me the achieve what I'm looking for? Alongside any code suggestions I would very much appreciate any further readings around my issues to so I can better understand these things.
N.B.: This is being run by PoSh Core on MacOS
Preface:
The answer below applies to console / terminals, including Visual Studio Code' integrated terminal. In these PowerShell hosts, the problem at hand is fixed in PowerShell v7.2+.
However, the behavior persists in the context of remoting and background jobs - see GitHub issue #3996 for a discussion.
Your symptom implies that $ErrorActionPreference = 'Stop'
is in effect at the time function
Test-Validation
executes.
(Temporarily) set it to 'Continue'
to fix your problem - which in PowerShell 7.2+ is no longer required (see below).
The reason for the observed behavior is that, in Windows PowerShell and in PowerShell (Core) 7 up to v7.1.x, using an error-stream redirection (2>
) makes PowerShell route an external program's stderr output through PowerShell's error stream (see about_Redirection), and $ErrorActionPreference = 'Stop'
therefore throws a script-terminating error once the first stderr line is received.
This behavior is unfortunate, because stderr output from external programs cannot be assumed to represent an error condition, given that external programs in effect use stderr, the standard error stream, for anything that other than data, which includes status information, for instance.
PowerShell 7.2 and above change this behavior for the better: stderr output is no longer routed through PowerShell's error stream, which means that:
Stderr lines are (fortunately) no longer collected in the automatic $Error
variable.
Preference variable $ErrorActionPreference
no longer has any impact on stderr output from external programs.
The automatic $?
variable, which indicates the success status of the most recently executed statement, is no longer incorrectly set to $false
when the process exit code is 0
and there also happens to be stderr output - though note that you can always infer success vs. failure of external programs via the automatic $LASTEXITCODE
variable