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 commandTee-StdErr
tests whether the current pipeline object ($InputObject
) is anErrorRecord
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).