问题
My application should write it's errors as literal JSON objects on stderr. This is proving difficult with PowerShell (5, 6 or 7) since PowerShell seems to want to prevent you from writing to stderr and, if you do succeed, it changes what you write.
In all examples we are running the following from within a powershell/pwsh console:
./test.ps1 2> out.json
test.ps1
Write-Error '{"code": "foo"}'
out.json
[91mWrite-Error: [91m{"code": "foo"}[0m
PowerShell is changing my stderr output. Bad PowerShell.
test.ps1
$Host.UI.WriteErrorLine('{"code": "foo"}')
out.json
PowerShell not writing to stderr (or >2 is not capturing it). Bad PowerShell.
test.ps1
[Console]::Error.WriteLine('{"code": "foo"}')
out.json
PowerShell not writing to stderr (or >2 is not capturing it). Bad PowerShell.
Update
I now understand that PowerShell does not have a stderr but rather numbered streams of which 2 corresponds to Write-Error and [Console]::Error.WriteLine() output and is sent to stderr of the pwsh/powershell.exe process IFF that output is redirected.
In short, stderr only exists outside of powershell and you can only access it via redirection:
pwsh ./test.ps1 2> out.json
Inside of powershell you can only redirect 2> the output from Write-Error. [Console]::Error.WriteLine() is not captured internally but sent to the console.
回答1:
Problem
When you write to the error stream, Powershell creates an ErrorRecord object for each message. When you redirect the error stream and output it, Powershell formats it like an error message by default. The sub strings like [91m are ANSI escape sequences that colorize the message when written to the console.
Solution
To output plain text messages, convert the error records to strings before redirecting them to the file:
./test.ps1 2>&1 | ForEach-Object {
if( $_ -is [System.Management.Automation.ErrorRecord] ) {
# Message from the error stream
# -> convert error message to plain text and redirect (append) to file
"$_" >> out.json
}
else {
# Message from the success stream
# -> already a String, so output it directly
$_ # Shortcut for Write-Output $_
}
}
Remarks:
2>&1merges the error stream with the success stream, so we can process both by the pipeline.$_is the current object processed by ForEach-Object. It is of typeErrorRecord, when the message is from the error stream of "test.ps1". It is of typeString, when the message is from the success stream of "test.ps1".- Using the
-isoperator we check the type of the message to handle messages originating from the error stream differently than those from the success stream. "$_"uses string interpolation to convert theErrorRecordto the plain text message.- The
>>operator redirects to the given file, but appends instead of overwriting.
Bonus code - a reusable cmdlet
If we regularly need to redirect error streams as plain text to a file, it makes sense to wrap the whole thing in a reusable cmdlet:
Function Out-ErrorMessageToFile {
[CmdletBinding()]
param (
[Parameter( Mandatory )] [String] $FilePath,
[Parameter( Mandatory, ValueFromPipeline )] [PSObject] $InputObject,
[Parameter( )] [Switch] $Append
)
begin {
if( ! $Append ) {
$null > $FilePath # Create / clear the file
}
}
process {
if( $InputObject -is [System.Management.Automation.ErrorRecord] ) {
# Message from the error stream
# -> convert error message to plain text and redirect (append) to file
"$InputObject" >> $FilePath
}
else {
# Message from the success stream
# -> already a String, so output it directly
$InputObject # Shortcut for Write-Output $InputObject
}
}
}
Usage examples:
# Overwrite "out.json"
./test.ps1 2>&1 | Out-ErrorMessageToFile out.json
# Append to "out.json"
./test.ps1 2>&1 | Out-ErrorMessageToFile out.json -Append
来源:https://stackoverflow.com/questions/65481006/capture-a-literal-string-on-stderr-in-powershell