Home > other >  Suppress NativeCommandError output when redirecting stdout and stderr to separate files
Suppress NativeCommandError output when redirecting stdout and stderr to separate files

Time:09-04

I have the following files:

test.ps1

& e:\test.bat > stdout.txt 2> stderr.txt

test.bat

@echo off
echo write to stdout
echo write to stderr >&2

When I call test.ps1 like this:

powershell -ExecutionPolicy bypass e:\test.ps1

The output files look like this:

stdout.txt

write argument to stdout

stderr.txt

test.bat : write to stderr 
At E:\test.ps1:5 char:1
  & "$application" "$argument" > $stdout 2> $stderr
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      CategoryInfo          : NotSpecified: (write to stderr :String) [], RemoteException
      FullyQualifiedErrorId : NativeCommandError

There is an answer how to prevent the NativeCommandError output from being written to file when redirecting both stdout and stderr to the same file, but how can I achieve that when writing to different files?

CodePudding user response:

To build on the helpful comments:

  • In PowerShell (Core) 7 , your command would work as expected.

  • In Windows PowerShell, unfortunately, stderr lines are implicitly formatted as if they were PowerShell errors when a 2> redirection is involved, resulting in the noisy output you saw.

The solution is to merge stderr into stdout with 2>&1, and separate the collected lines into stdout and stderr by their type, allowing you to stringify the stderr lines, which PowerShell wraps in [System.Management.Automation.ErrorRecord] instances, via their .ToString() method, which results in their text content only.

Apart from being cumbersome, this approach requires collecting all output in memory first.

$stdout, $stderr = (& e:\test.bat 2>&1).Where({ $_ -is [string] }, 'Split')

$stdout > stdout.txt
$stderr.ForEach('ToString') > stderr.txt

CodePudding user response:

Complementing mklement0's helpful answer, here is a function Tee-StdErr for PowerShell 5 and older versions that you can chain in-between to redirect stderr to a file without NativeCommandError, while forwarding regular output to the next pipeline command (or redirect it to a file if > is used):

Function Tee-StdErr {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)] [string] $Path,
        [Parameter(Mandatory, ValueFromPipeline)] $InputObject
    )

    begin {
        $file = if( $item = New-Item -Path $Path -Force ) { 
            [IO.StreamWriter]::new( $item.FullName ) 
        }
    }
    process {
        if( $InputObject -is [Management.Automation.ErrorRecord] ) {
            # stringify error message and write to file
            if( $file ) { $file.WriteLine( "$InputObject" ) }   
        }
        else { 
            # pass stdout through
            $InputObject   
        }  
    }
    end {
        $file | ForEach-Object Dispose
    }
}

Usage:

& .\test.bat 2>&1 | Tee-StdErr stderr.txt > stdout.txt

# Alternatively pipe stdout to other commands:
& .\test.bat 2>&1 | Tee-StdErr stderr.txt | Set-Content stdout.txt
  • 2>&1 merges the error (#2) stream with the success (#1) stream, so both can be processed by the next pipeline command
  • Tee-StdErr tests whether the current pipeline object ($InputObject) is an ErrorRecord and if so, it stringifies ("$_") it and writes it to the error file. Otherwise the current object is a string from the success stream which is passed through by using PowerShell's implicit output feature (just naming the variable outputs it).
  • Related