How can I redirect stdout and stderr without polluting PowerShell error output
Asked Answered
T

1

2

Problem:

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?

My code:

(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)"
    }
}

Expected output

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.

Actual output

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

Tepefy answered 20/3, 2021 at 20:13 Comment(4)
What is pdk, is it a native command or a PowerShell function? If it is native, then this Q&A may apply: https://mcmap.net/q/16008/-lastexitcode-0-but-false-in-powershell-redirecting-stderr-to-stdout-gives-nativecommanderror/7571258Service
pdk is the 'Puppet Development Kit' it's an external binary, hence my dot calling of it.Tepefy
Does this answer your question? $LastExitCode=0, but $?=False in PowerShell. Redirecting stderr to stdout gives NativeCommandErrorService
Thanks for the response, unfortunately not. It turns out it was a bug in PowerShell Core, see mklement0's response. Thanks again.Tepefy
K
9

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 future versions will hopefully no longer required (see below).

The reason for the observed behavior is that, in Windows PowerShell and up to PowerShell (Coe) 7.1, 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:

Kay answered 20/3, 2021 at 21:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.