3

I try to figure how to determine if a command throw with Invoke-Expression fail. Even the variable $?, $LASTEXITCODE or the -ErrorVariable don't help me.

For example :

PS C:\> $cmd="cat c:\xxx.txt"

Call $cmd with Invoke-Expression

PS C:\> Invoke-Expression $cmd -ErrorVariable err

Get-Content : Cannot find path 'C:\xxx.txt' because it does not exist. At line:1 char:4 + cat <<<< c:\xxx.txt + CategoryInfo : ObjectNotFound: (C:\xxx.txt:String) [Get-Content], ItemNotFoundExcep tion + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand

The $? is True

PS C:\> $?

True

The $LASTEXITCODE is 0

PS C:\> $LASTEXITCODE

0

And the $err is empty

PS C:\> $err

PS C:\>

The only way I found is to redirect STD_ERR in a file and test if this file is empty

PS C:\> Invoke-Expression $cmd 2>err.txt

PS C:\> cat err.txt

Get-Content : Cannot find path 'C:\xxx.txt' because it does not exist. At line:1 char:4 + cat <<<< c:\xxx.txt + CategoryInfo : ObjectNotFound: (C:\xxx.txt:String) [Get-Content], ItemNotFoundExcep tion + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand

Is it the only and best way to do this ?

user2867628
  • 41
  • 1
  • 3
  • It could be interesting to not that PowerShell version 2 and PowerShell version 4 behaves differently in this regard. In version 4, the $err variable is set as expected while in version 2 it is not. In version to I need to set the -ErrorVariable parameter on the command string $cmd = 'cat c:\xxx.txt -ErrorVariable err' which seems very unfortunate. Seems like a bug which was fixed between v2 and v4. – Robert Westerlund Feb 05 '14 at 21:33
  • http://stackoverflow.com/questions/24222088/powershell-capture-program-stdout-and-stderr-to-seperate-variables – Ohad Schneider Mar 22 '16 at 17:16

2 Answers2

6

I was going crazy trying to make capturing the STDERR stream to a variable work. I finally solved it. There is a quirk in the invoke-expression command that makes the whole 2&>1 redirect fail, but if you omit the 1 it does the right thing.

 function runDOScmd($cmd, $cmdargs)
 {
 # record the current ErrorActionPreference
     $ep_restore = $ErrorActionPreference
 # set the ErrorActionPreference  
     $ErrorActionPreference="SilentlyContinue"

 # initialize the output vars
     $errout = $stdout = ""

 # After hours of tweak and run I stumbled on this solution
 $null = iex "& $cmd $cmdargs 2>''"  -ErrorVariable errout -OutVariable stdout
 <#                       these are two apostrophes after the >
     From what I can tell, in order to catch the stderr stream you need to try to redirect it,
     the -ErrorVariable param won't get anything unless you do. It seems that powershell
     intercepts the redirected stream, but it must be redirected first.
 #>
 # restore the ErrorActionPreference
 $ErrorActionPreference=$ep_restore

 # I do this because I am only interested in the message portion
 # $errout is actually a full ErrorRecord object
     $errrpt = ""
     if($errout)
     {
         $errrpt = $errout[0].Exception
     }

 # return a 3 member arraylist with the results.
     $LASTEXITCODE, $stdout, $errrpt
 }
David Wing
  • 61
  • 1
  • 2
2

It sounds like you're trying to capture the error output of a native in a variable without also capturing stdout. If capturing stdout was acceptable, you'd use 2>&1.

Redirecting to a file might be the simplest. Using Invoke-Expression for it's -ErrorVariable parameter almost seems like a good idea, but Invoke-Expression has many problems and I usually discourage it.

Another option will look a little cumbersome, but it can be factored into a function. The idea is to merge output streams using 2>&1, but then split them again based on the type of the object. It might look like this:

function Split-Streams
{
    param([Parameter(ValueFromPipeline=$true)]$InputObject)

    begin
    {
        $stdOut = @()
        $stdErr = @()
    }

    process
    {
        if ($InputObject -is [System.Management.Automation.ErrorRecord])
        {
            # This works well with native commands but maybe not as well
            # for other commands that might write non-strings
            $stdErr += $InputObject.TargetObject
        }
        else
        {
            $stdOut += $InputObject
        }
    }

    end
    {
        ,$stdOut
        ,$stdErr
    }
}

$o, $e = cat.exe c:\xxx.txt 2>&1 | Split-Streams
Jason Shirk
  • 7,734
  • 2
  • 24
  • 29
  • This does not quite work even for the native commands, since `TargetObject` is not set on any records but first, so you will get a lot of missed information if you have more than one record there. – Andrew Savinykh Nov 01 '19 at 23:16