★ wanayoo — archive 1999 https://github.com/PowerShell/PowerShell/issues/3996Nouvelle recherche | Portail wanayoo
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistent handling of native command stderr #3996

Closed
BurtHarris opened this issue Jun 13, 2017 · 48 comments · Fixed by #13361
Closed

Inconsistent handling of native command stderr #3996

BurtHarris opened this issue Jun 13, 2017 · 48 comments · Fixed by #13361
Assignees
Labels
Committee-Reviewed PS-Committee has reviewed this and made a decision WG-Interactive-Console the console experience WG-Remoting PSRP issues with any transport layer
Milestone

Comments

@BurtHarris
Copy link

BurtHarris commented Jun 13, 2017

I discovered recently that the handling of stderr from native commands isn't being dealt with consistently between Windows PowerShell and Windows PowerShell ISE.

Steps to reproduce

$errorActionPreference="stop"; try { cmd /c nosuchexe } catch { 'dang!' }

Actual ISE behavior (expected)

dang!

Native commands writing to stderr while $errorActionPreference="stop" generate a catchable NativeCommandError which encapsulates the original error message in a ErrorRecord. It is easy to capture, analyze, and reword error messages in context.

Actual CLI behavior

'nosuchexe' is not recognized as an internal or external command,
operable program or batch file.

No NativeCommandError is generated, no matter how $errorActionPreference is set. The stderr from the native command is piped straight through to the default process stderr.

This behavior is a serious loss of functionality, leading some to believe that PowerShell can't distinguish between stderr and stdout. Lots of community confusion generated because of the discrepency. See https://stackoverflow.com/questions/44488202/powershell-streaming-output/44491420#44491420, among others.

Analysis

The difference isn't just the ISE, I think it is related to PowerShell remote runspaces! The full info on the caught error record (in the ISE) demonstrates the underlying exception is a RemoteException like this:

PS C:\Users\Burt_> $errorActionPreference="stop"; try { cmd /c nosuchexe } catch { Format-List * -force -InputObject $_ }



PSMessageDetails      : 
Exception             : System.Management.Automation.RemoteException: 'nosuchexe' is not recognized as an internal or external 
                        command,
TargetObject          : 'nosuchexe' is not recognized as an internal or external command,
CategoryInfo          : NotSpecified: ('nosuchexe' is ...ternal command,:String) [], RemoteException
FullyQualifiedErrorId : NativeCommandError
ErrorDetails          : 
InvocationInfo        : System.Management.Automation.InvocationInfo
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {}

Environment data

Both powershell.exe and powershell_ise.exe report the same version information:

Name                           Value
----                           -----
PSVersion                      5.1.15063.296
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.15063.296
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
@SteveL-MSFT SteveL-MSFT added the WG-Interactive-Console the console experience label Jun 13, 2017
@SteveL-MSFT
Copy link
Member

I believe #3415 is a way to address part of your concern. This repo isn't for reporting Windiws PowerShell nor ISE issues, that would be Uservoice.

@BurtHarris
Copy link
Author

Thanks @SteveL-MSFT, here's my Uservoice link: https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/19582069-inconsistant-handling-of-native-command-stderr. Unfortunately I don't think this will get much attention as many people have dismissed it as something PowerShell can't handle.

As to cross-platform PowerShell, in this case, I believe that the ISE provides the more desirable behavior, allowing easy access to trapping stderr. I believe such behavior is also available using PS remoting, but haven't tested it. Without this behavior (or something similar) it puts cross-platform PowerShell at a disadvantage from other environments which clearly separate stderr and stdout.

@BurtHarris
Copy link
Author

BurtHarris commented Jun 13, 2017

The parallel to set -eu is an interesting one. However the assertions you made in #3415 $errorActionPreference not working for native commands is NOT always true, nor documented as a feature. I suggest it's an (undesirable) quirk of the way local native commands are implemented.

The MSDN public API docs say:

Defines the Action Preference options. These options determine what will happen when a particular type of event occurs. For example, setting shell variable ErrorActionPreference to "Stop" will cause the command to stop when an otherwise non-terminating error occurs.

@lzybkr
Copy link
Member

lzybkr commented Jun 13, 2017

The difference between the ISE and the CLI should be fixed by 161940c - but that change would need to be back ported to Windows PowerShell.

With that change, I expect (but have not confirmed) that you'll get consistency, but not the behavior you desire.

I can say with certainty that tools do write to stderr under normal conditions, e.g., some tools write their logon/logo message to stderr to make it easy to redirect that message to /dev/null or nul, and that's why the default is to not report stderr output as an error in the cli.

@BurtHarris
Copy link
Author

BurtHarris commented Jun 13, 2017

Thanks, @lzybkr . That PR seems to further codify the undesirable separation between native command and cmdLet by adding a Boolean flag to _WriteErrorSkipAllowCheck. I suspect you are right, and it's not the behavior I want.

@mklement0
Copy link
Contributor

mklement0 commented Jun 13, 2017

@SteveL-MSFT: The ISE is incidental, the problem does affect PS Core:

  • When remoting a host other than the console host is involved (which includes the ISE and remoting contexts, even when invoked from the console), there is a fundamental difference in how external-utility calls are being handled; there is no obvious reason for this difference, and the inconsistency is problematic.

  • The remoting scenarios actually integrates much better with PowerShell's error handling when calling external utilities, whereas the current non-remoting behavior is unhelpful.

Undoubtedly, changing the behavior (at least by default) would be a high-profile breaking change.

While there is a documented difference in how remoting handles errors (from Invoke-Command's help),

Errors that result from the command that Invoke-Command runs are included in the command results. Errors that would be terminating errors in a local command are treated as non-terminating errors in a remote command.

, this difference does not explain the current behavior with external utilities.

Let me summarize Burt's findings for contrast:


(Non-remoting) console-hosts invocations:

& {
  $shell, $option = (('sh', '-c'), ('cmd', '/c'))[$env:OS -eq 'Windows_NT']; 
  $Error.Clear(); $ErrorActionPreference = 'Stop'
  try {  $null = & $shell $option nosuch } catch { 'dang!' }
  $?; $Error.Count
}

Output:

/bin/sh: nosuch: command not found   # (stderr printed straight to console in default color (Unix error message)
False   # no exception triggered, $? reflects nonzero exit code
0     # nothing was recorded in $Error
  • Stderr output is passed straight through to the console, not printed in red.
  • Since nothing is written to PowerShell's error stream - and therefore nothing getting captured in $Error - using $ErrorActionPreference = 'stop' with try / catch has no effect.
  • The only integration with PowerShell's error handling is that $? is set based on whether the exit code is zero ($True) or not ($False) (and the exit code is reflected in $LASTEXITCODE), but - unlike in the remoting scenarios - stderr output alone does not cause $? to be set to $False.

CAVEAT: The behavior changes fundamentally when stderr is redirected (becomes like the remoting-scenario behavior(!)): see #4002

In short:

  • Without using explicit redirection (2>&1 , 2>file), there is no way to detect the presence of or inspect stderr output.
  • Unlike PowerShell errors, which print in red, stderr output blends in with stdout output in the console (though it wouldn't be captured or redirected as part of the success stream, given that it prints straight to the console (in the absence of explicit redirection of the error stream)).

Non-console-host invocations, including in the ISE and in remoting contexts:

There, calls interact differently with PowerShell's error handling, with stderr output treated as if non-terminating errors had been reported; specifically:

# Run in *elevated* session, with *remoting set up*.
Invoke-Command -Computer . {
  $shell, $option = (('sh', '-c'), ('cmd', '/c'))[$env:OS -eq 'Windows_NT']; 
  $Error.Clear(); $ErrorActionPreference = 'Stop'
  try {  $null = & $shell $option nosuch } catch { 'dang!' }
  $?; $Error.Count
}

Output:

dang!  # An exception was triggered and caught - stderr output was considered a non-terminating error
True  # ($? is $True, because try/catch reset it)
1  # The stderr line *was* recorded in $Error
  • Every stderr output line is output as an error record and also recorded in the automatic $Error collection.

  • As in non-remoting consoles, automatic variable $? is set to $False for nonzero exit codes, but also when any stderr output was produced - independently of the exit code.

  • Combining $ErrorActionPreference = 'Stop' with a Try / Catch handler ONLY triggers the catch handler if there's at least 1 line of stderr output, and not with a nonzero exit code, even though the latter is the only true failure signal.

In short:

  • In remoting scenarios non-console-host invocations, stderr output behaves like non-terminating errors (as cmdlets would report).
  • However, there is no complementary mapping of nonzero exit codes to terminating errors, which means that $ErrorActionPreference = 'Stop' with a Try / Catch doesn't catch a quietly failing utility (one that signals failure by its exit code only).
    • Explicitly testing $LASTEXITCODE (or $?, if no try / catch was involved) is still needed.
    • A possible solution is to make the behavior similar to that of .NET method calls (whose exceptions are treated as non-terminating errors without try / catch, but are caught if enclosed in try / catch - although that could be considered a historical wart itself); translated to this scenario:
      • As before: outside of try / catch, merely reflect a nonzero exit code in $LASTEXITCODE, without further action.
      • New: inside of try / catch, throw an exception if the exit code is nonzero.

@lzybkr
Copy link
Member

lzybkr commented Jun 13, 2017

I found 5 random complaints about the ISE or remoting reporting errors with native executables where there was no error:

https://stackoverflow.com/questions/31449220/powershell-ise-wrongly-interprets-openssl-exe-normal-output-as-error
https://stackoverflow.com/questions/10856609/why-is-powershell-ise-showing-errors-that-powershell-console-does-not-show
https://stackoverflow.com/questions/12537254/tell-powershell-ise-to-not-send-stderr-to-write-error
https://stackoverflow.com/questions/2095088/error-when-calling-3rd-party-executable-from-powershell-when-using-an-ide
https://stackoverflow.com/questions/18380227/psexec-throws-error-messages-but-works-without-any-problems

This is why powershell.exe behaves as it does - it is common for tools to use stderr and not report errors, and automation is broken if we assume otherwise. If you follow the links, you'll see those tools are common as well - git, hg, openssl, and 2 SysInternals utilities: psexec, and du.

@SteveL-MSFT
Copy link
Member

@lzybkr I agree with you that we can't rely on stderr to be an Error, but it seems we have difference in behavior between local and remote PowerShell and that we should fix

cc @PowerShell/area-remoting

@SteveL-MSFT SteveL-MSFT added the WG-Remoting PSRP issues with any transport layer label Jun 13, 2017
@lzybkr
Copy link
Member

lzybkr commented Jun 13, 2017

This is not remoting specifc, nor ISE specific. There was a bug in the native command processor, which was worked around in the console host, but not in the "default host" which is used by remoting, nor was it worked around in the ISE host. I referenced the real fix above - 161940c

You can see the bug in an ordinary runspace:

$ps = [powershell]::Create().AddScript('cmd /c nosuch')
$ps.Invoke()
$ps.Streams.Error

In Windows PowerShell, you'll get:

cmd : 'nosuch' is not recognized as an internal or external command,
At line:1 char:1
+ cmd /c nosuch
+ ~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: ('nosuch' is not...ternal command,:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

operable program or batch file.

In PowerShell Core, you'll get:

'nosuch' is not recognized as an internal or external command,
operable program or batch file.

@BurtHarris
Copy link
Author

BurtHarris commented Jun 14, 2017

I've found an apparently undocumented workaround that captures stderr into $Error independent of host. Its actually a bit surprising, but might serve as a inspiration for developing a supported solution for native commands that fits better with PowerShell's error handling design, but doesn't present the breaking change.

If you add 2>$null to a native command, it captures stderr into $Error! I haven't dug into how this works, but it seems consistent across hosts. Further detail here.

Now I don't particularly like the syntax, but stumbled on it when I tried 2> which generated Missing file specification after redirection operator. I had hoped that something like 2>$a might act like -ErrorVariable a, but that's wishful thinking. Resorted to $null as a placeholder to get around the syntactic requirement for redirection, and got behavior (including trapping if $errorActionPreference='stop') that's pretty much what I had hoped for.

My poking around was inspired by the 2>&1 syntax helps out for cases where a command uses stderr for non-error output, and looking for a complementary notation. To extend this concept further and support full -ErrorVariable a semantics, it might be possible to support a redirection syntax like 2>&a (getting rid of the dollar sign is a good thing.) Since redirection is implemented by the shell, and not passed to the child's argv, it's transparent to the native utility.

If a further similar redirection-like syntax were able to add -ErrorAction semantics, it would put native command execution on a similar footing to cmdlet execution. I was thinking perhaps something like 2>+Stop to give just one native command terminating stderr semantics.

@BurtHarris
Copy link
Author

Also, DefaultHost is used in other contexts, e.g. Start-Job. Will the 161940c fix be a breaking change for them as well?

@mklement0
Copy link
Contributor

mklement0 commented Jun 14, 2017

@lzybkr: Thanks for finding the real cause - I've cleaned up my previous post accordingly.

it is common for tools to use stderr and not report errors, and automation is broken if we assume otherwise

You're right: external utilities have no choice other than to use stderr for anything that's not data, so stderr is frequently used even for non-error messages, and the only true indicator of an external utility's success is its exit code.
My initial thought was that it would be harmless to treat stderr lines as non-terminating PS errors, as they'd just collect in $Error without stopping the script (and how they should be formatted for display is a secondary issue).

But I can definitely see how you do NOT want your script to abort with $ErrorActionPreference = 'Stop' in effect just because an external utility happens to write to stderr, and how $? should be set solely based on the exit code.

In an ironic twist, that is precisely what happens when you explicitly want to suppress stderr output currently (or redirect it to a file) - see #4002:

 & { $ErrorActionPreference = 'stop'; cmd /c nosuch 2>$null; $? } # ABORTS

As @BurtHarris has discovered, 2>$null doesn't actually just mean "discard that stream's output" (and 2>file doesn't just mean "save that stream's output to a file"), but it currently also means "quietly record the output lines as error records in $Error", which is:

  • (a) completely unexpected
  • (b) badly interacts with $ErrorActionPreference = 'stop', as demonstrated (writing to $Error apparently is tantamount to a non-terminating error)

However, as @BurtHarris mentions, there is value in making stderr output available to subsequent commands, at least on demand, but 2>$null is clearly not the way to do it.

For instance, being able to inspect stderr input is important for ill-behaved external utilities that, instead of signaling success via their exit code, write to stderr only.

So, as @BurtHarris suggests, having something analogous to -ErrorVariable would be helpful.
While surfacing that via > is a natural candidate, it makes it difficult to separate the immediate output behavior from the record-in-a-variable behavior, unless some presumably awkward compound syntax is devised.

An -ErrorAction analog might also be nice - which raises similar syntax questions - but, based on the discussion above, its semantics should be tied to the exit code, not to stderr output.

@mklement0
Copy link
Contributor

mklement0 commented Jun 14, 2017

@BurtHarris: Some more thoughts on the syntax of >$null and >... in general:

  • There's nothing special about the syntax of > $null: $null is a reference to a variable whose value is used as the filename.

    • The semantics are special, however (and documented neither in about_Redirection nor about_Automatic_Variables), because a $null value conveniently acts as a "portable null device"; that is, you can use it in lieu of NUL on Windows, and in lieu of /dev/null on Unix; using an empty string works too. Am I missing something here?
      • On Windows, you actually must use $null or '' instead of NUL in PowerShell, because using NUL results in the following error:
        out-file : FileStream was asked to open a device that was not a file. For support for devices like 'com1:' or 'lpt1:', call CreateFile, then use the FileStream constructors that take an OS handle as an IntPtr.
      • On Unix, PowerShell's $null / '' shortcut for /dev/null is not available in POSIX-like shells: They have no $null-equivalent type, and attempting to use the empty string (null string) as a redirection target results in an error.
  • Similarly, in >a, a is simply a bareword (unquoted token) that is treated like an (expandable string; in this case, literal filename a.

  • >&a is indeed promising for introducing new functionality, because & is already established as having into-a-different-target(-stream) semantics, yet currently only a digit is supported after the & (the index of a different PS stream).

    • The absence of $ before a, if a denotes a variable, makes perfect sense and is consistent with how -ErrorVariable works, for instance: you're passing the name of a variable, not its value.
  • To elaborate on my concern about separating output behavior from collecting-in-a-variable behavior:

With cmdlets and advanced functions, you can do the following:

# Passes errors through and, *independently*, captures them in variable $err.
Get-Item /nosuch -ErrorVariable err 

# Redirects (suppresses) errors and, *independently*, captures them in variable $err.
Get-Item /nosuch 2>$null -ErrorVariable err 

How would that translate to the proposed >& syntax for native utilities?

# Expectation based on > semantics: collect error in $err *only*, do not pass errors through.
# How do you pass them trough *and* collect them?
cmd /c nosuch 2>&err

Therefore, to have the same flexibility as with -ErrorVariable, additional syntax is required, which would require a "mini-DSL" in the argument to >&, which may be problematic:

For instance:

# Double the `&` to express the desire to pass through?
cmd /c nosuch 2>&&err

That said, even the existing collect-in-output-variable common parameters already have a mini-DSL (micro-DSL?): prepending + to the variable name specifies that the existing output variable contents be appended to (e.g., -ErrorVariable +err), which, too, would have to be part of the potential new >&... syntax).

@mklement0
Copy link
Contributor

mklement0 commented Jun 14, 2017

@lzybkr:

You can still defeat the fix in 161940c via the results of remoting or jobs, in the caller's context:

  • Remoting example (Invoke-Command):
# Run on Windows, with elevation and remoting set up.
# ABORTS
> & { $ErrorActionPreference = 'Stop'; Invoke-Command -Computer . { cmd /c nosuch }; 'After' } 
'nosuch' is not recognized as an internal or external command,  # in red; 'After' never executes
  • Job example:
# Run on Unix
> & { $ErrorActionPreference = 'Stop'; Receive-Job -Wait -AutoRemove (Start-Job { sh -c nosuch }); 'After' }
/bin/sh: nosuch: command not found  # in red; 'After' never executes

While the embedded command by itself, executed locally (in PS Core) now behaves as expected (stderr passed through, uncolored, to the console), it seems [speculation alert - see next comment] that the underlying error records (which the stderr lines are apparently still being emitted as in the local case, but without getting written to the error stream) are ultimately still sent to the error stream, namely by Invoke-Command / Receive-Job - at which point $ErrorActionPreference = 'Stop' kicks in and aborts the pipeline.

@mklement0
Copy link
Contributor

I may be getting confused here (I don't have the full picture): My assumption was that the referenced fix - which is already in (at least) beta.2 - would take effect for all hosts, but both Invoke-Cmd and Receive-Job still exhibit the old behavior - see below.

Is there another fix pending?

# Run on Windows, with elevation and remoting set up.
> Invoke-Command -Computer . { $Error.Clear(); cmd /c nosuch; $Error.Count }
'nosuch' is not recognized as an internal or external command,
operable program or batch file.
2

cmd's stderr output was recorded in $Error (2 items, because 2 stderr lines were output).

# On Unix
> Receive-Job -Wait -AutoRemove (Start-Job { $Error.Clear(); sh -c nosuch; $Error.Count })
/bin/sh: nosuch: command not found
1

sh's stderr output was recorded in $Error.

@BurtHarris
Copy link
Author

BurtHarris commented Jun 14, 2017

Sleeping on this I can see the argument for not having stderr output trigger the catch block, but it would be really great if having $errorActionPreference = 'stop' did throw an exception when the program exited with a non-zero status code. Combine that with capturing the stderr in $error, and somehow reflected in the exception thrown on exit, and its a much better package. I think I've seen another issue on the status code treatment.

@felixfbecker
Copy link
Contributor

Aborting execution / throwing an exception on any stderr output would be terrible. Lots of programs use stderr as a general logging stream (especially if stdout is used used to speak an IPC protocol).

@SteveL-MSFT SteveL-MSFT added this to the 6.1.0-Consider milestone Jan 4, 2018
@quetzalcoatl
Copy link

quetzalcoatl commented Apr 17, 2018

well.. I think it happens right now (on PS 5.1.16299.251). I've just battled with a bunch of web-app building utilities (node, npm, ..) that really like to emit progress/status/summary/sidenotes/warning to stderr. I run them on remote device via PS-Remoting and, literally, any warning from stderr broke jobs.. eh..

@BrucePay
Copy link
Collaborator

(Not having read all of the rather long discussion here but here's my take on the basic console host/non-console host issue)

Historically, there was a fundamental difference between the console host and any other host. In the console host, if the output of a native command was not redirected in script, then the executable got the native console handle and PowerShell was unaware of what is being written to stdout/stderr. This is necessary because many Windows console executables use the console APIs to , for example, color their output. In fact, originally output was always redirected but when we started using the Windows build system from PowerShell, the lack of colored output was intolerable. Even more important, it allows console editors like vim.exe to work. If output was redirected, it wouldn't be possible to use a console editor from PowerShell. And so there is a complex calculation to decide if the command was redirected or not and if it isn't, it writes straight to the console. All other (non-console) hosts have to do redirection all the time.

As far as error handling behaviour goes, here's simple error output to the console:

PS[1] (78) > $ErrorActionPreference="stop"
PS[1] (79) > try { cmd /c nosuchexe   } catch { "blargh" }
'nosuchexe' is not recognized as an internal or external command, operable program or batch file.

No trap is fired because PowerShell is unaware of the error output. But as soon as you introduce redirection:

PS[1] (80) > try { cmd /c nosuchexe 2>1  } catch { "blargh" }
blargh

the trap fires.

As inconsistent as this behaviour seems, changing it on Windows would probably be very bad. But one thing to consider - we should probably be throwing if $? is false after executing a native command instead of depending on the error output.

@BurtHarris
Copy link
Author

Fascinating @BrucePay. Thanks for the history lesson.

I agree that use of the error output stream isn't a reasonable basis for throwing. I like the idea of $? testing, but it seems like that might be a breaking change for scripts.

@mklement0
Copy link
Contributor

mklement0 commented Apr 25, 2018

Interesting backstory, indeed.

As inconsistent as this behaviour seems

To illustrate that with two examples of counter-intuitive behavior:

# Explicitly ignore stderr output.
$ErrorActionPreference = 'Stop'; try { whoami -nosuchoption 2>$null } catch { "blargh" }

# Explicitly make stderr output success output
$ErrorActionPreference = 'Stop'; try { whoami -nosuchoption 2>&1 } catch { "blargh" }

Even though the user intent is clearly not to treat the external program's stderr output as an error, the catch block fires.

changing it on Windows would probably be very bad

The predominant hosts on Windows are the console and the ISE:

Note: VSCode is catching up and will presumably take over eventually; fortunately, its built-in console's behavior is consistent with the regular console's, and it can even handle colored output.

There already is a fundamental inconsistency between the two, as discussed above, which is worth addressing:

# NO redirection:
# Throws in the ISE, but not in the regular console.
$ErrorActionPreference = 'Stop'; whoami -nosuchoption; 'after'
  • The ISE behavior should be aligned with that of the regular console, given that production runs typically happen in the latter.

  • The same goes for the invisible default host used in the context of remoting, including background jobs - see above.


Separately, it's hard to imagine that anyone has relied on the obscure behavior of 2> redirection being introduced to a command causing errors with $ErrorActionPreference = 'Stop' in effect.

Therefore, I think this falls into Bucket 3: Unlikely Grey Area and should be fixed as well.


we should probably be throwing if $? is false after executing a native command instead of depending on the error output.

Even leaving backward compatibility aside, I don't think that $? being $False should throw by default, for two reasons:

  • It makes more sense to conceive of an external program's non-success as a non-terminating error (even though the PS distinction between non-terminating and (statement)-terminating doesn't apply), and $? commendably already reflects the success status (by being the equivalent of $LASTEXITCODE -eq 0).

  • Even though nonzero exit codes by convention signal failure, there are external programs that use them to convey status information [too] (e.g, Robocopy.exe on Windows).

Thus, it would make sense for $ErrorActionPreference to apply to external ("native") programs too, as @BurtHarris has previously suggested, so that users could opt into treating nonzero exit codes as fatal with value 'Stop'.

  • That said, introducing this now would be a seriously breaking change, so the best we can do is to introduce something like $ExternalErrorActionPreference to complement $ErrorActionPreference.

Additionally, it would be nice to be able to do that on a per-command basis, analogous to -ErrorAction Stop, as @BurtHarris has previously suggested, which necessitates introducing new syntax.

@joeyaiello joeyaiello added this to the 7.2-Consider milestone Jul 13, 2020
@joeyaiello joeyaiello added the Review - Committee The PR/Issue needs a review from the PowerShell Committee label Jul 13, 2020
ppekrol pushed a commit to ravendb/ravendb that referenced this issue Jul 15, 2020
jcflack added a commit to jcflack/pljava that referenced this issue Jul 16, 2020
The experiment with 2>&1 was very revealing. Without the 2>&1,
the output going to stderr was simply being lost. With 2>&1 added,
the output gets joined in with stdout ... BUT NOT BEFORE getting
munged in some way by PowerShell (or .NET framework, or whatever
layer of whatever is doing the actual munging). Something tries to
construct a structured error record object out of arbitrary strings
of stderr text, as in:

At C:\projects\pljava\.appveyor\test_script.ps1:6 char:1
+ java -jar $packageJar 2>&1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : NotSpecified: (C:/PROGRA~1/POS...OT.dll as bytes:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError

What's more, this text-stream-to-error-record conversion seems to
deal poorly with stderr data that comes in multiple writes per line
(as is common for progress reporting). It seems to end up trying to
make individual "error" records out of bits and pieces of lines,
leading to the ugly and inconsistent (probably timing-dependent)
reformatting:

C:/PROGRA~1/POSTGR~1/12/share\pljava\pljava--1.5.0-BETA2--1.6.0-SNAPSHOT.sql
as lines (
UTF8)
C:/PROGRA~1/POSTGR~1/12/share\pljava\pljava--1.5.0-BETA1--1.6.0-SNAPSHOT.sql
as lines (
UTF8
)

There seems to be a mother lode of information on this strange
behavior in these two github issues:

PowerShell/PowerShell#3996
PowerShell/PowerShell#4002

It may turn out to work better to tell Java itself to merge its
.err stream into .out, rather than depending on PowerShell's
peculiar idea of what redirection means.
@joeyaiello
Copy link
Contributor

I still believe strongly that stderr is not a reasonable indicator that a real error has taken place. If anything, the behavior should be changed such that we're consistent that stderr does not throw a PowerShell error. (Though again, I feel the opposite about non-zero exit codes, where are much more consistently thrown when erroneous behavior occurs.)

@n9
Copy link

n9 commented Jul 30, 2020

@joeyaiello

I still believe strongly that stderr is not a reasonable indicator that a real error has taken place. If anything, the behavior should be changed such that we're consistent that stderr does not throw a PowerShell error.

What about to add an option to PowerShell that will treat stderr from native commands as the information stream?

@mklement0
Copy link
Contributor

mklement0 commented Jul 30, 2020

@n9, we already have the ability to redirect the stderr stream selectively with 2>.

While this is somewhat wasteful - every line is wrapped in a System.Management.Automation.ErrorRecord instance (which, curiously, wraps a System.Management.Automation.RemoteException exception) with FullyQualifiedErrorId NativeCommandError - it works in principle.

However, there are two three current problems - which I think are solvable without resorting to a different stream:

It sounds like all that is needed to exempt NativeCommandError records from being subject to $ErrorActionPreference, from getting recorded in $Error and from setting $? to $false.

There's also the tangentially related #11134

@n9
Copy link

n9 commented Jul 30, 2020

@mklement0, I am currently using redirection.

My suggestion is to have one option (similar to $ErrorActionPreference) that can optionally solve all three problems you have mentioned:

  1. Somewhat wastefulness of explicit redirection, especially, in case of scripts that call a lot of native commands like git.
  2. Triggering the error in case of $ErrorActionPreference = Stop.
  3. Needless recording in the $Error collection.

The current implementation is good for PowerShell commands, but complicates invoking of native commands.

@mklement0
Copy link
Contributor

mklement0 commented Jul 30, 2020

@n9

Your suggestion won't solve 1., because writing to the information stream requires that stderr lines be wrapped in System.Management.Automation.InformationRecord instances.

  1. and 3. can be solved as I've suggested, as a direct fix - to me there is no need for the added complexity that comes with new features.

Generally, we want to unify treatment of PowerShell commands and external programs as much as possible.

@BurtHarris
Copy link
Author

BurtHarris commented Jul 31, 2020

Reviewing this issue for the first time in over a year. Thanks for the continued attention to it, please forgive me if I'm not fully up-to-date.

@joeyaiello said

I still believe strongly that stderr is not a reasonable indicator that a real error has taken place.

I would agree if we changed the word "reasonable" to "sufficient". There are too many different practices in command-line native commands for it to be considered "sufficient". The tricky part is that the meaning of stderr output is both context and content dependent. But a script knows (or defines) the context, and what content might indicate a real error or meaningful information stream event.

Has any thought been put into using a preference variable to let a script customize the treatment of stderr handling? Using $ErrorActionPreference is problematic, but how about something like $StderrActionPreference?

I'm not sure if a simple true/false or ignore/continue/error/abort is flexible enough, but perhaps some sort of regex matching approach, similar to how IDEs are adapted to different compile tools (like a VSCode task problemMatcher.) [This line-of-thought is perhaps informed by thought years ago about a composable text-to-object mapping facility for PS, but at the time I was thinking bout stdout text.]

@SteveL-MSFT
Copy link
Member

@BurtHarris there is an existing RFC that the @PowerShell/powershell-committee supports for handling exit codes. Stderr isn't reliable to detect errors as native tools use it as for anything that isn't intended to be stdout. Exit codes have their own problems where non-zero doesn't indicate an error, but most of the time, a non-zero exit code is an error case.

Changing stderr to be information stream is a breaking change in that redirection of stream 2 would not work as expected for native commands. Unfortunately, I think we are stuck with this design.

However, I agree with @mklement0's proposal that we special case NativeCommandError error records from triggering $ErrorActionPreference as a way to resolve this specific issue.

@SteveL-MSFT
Copy link
Member

@PowerShell/powershell-committee discussed this and agrees for 7.1 we should have NativeCommandError error records not trigger $ErrorActionPreference although a breaking change, but is likely bucket 3

@SteveL-MSFT SteveL-MSFT added Committee-Reviewed PS-Committee has reviewed this and made a decision and removed Review - Committee The PR/Issue needs a review from the PowerShell Committee labels Aug 5, 2020
@SteveL-MSFT SteveL-MSFT self-assigned this Aug 5, 2020
@mklement0
Copy link
Contributor

mklement0 commented Aug 7, 2020

Glad to see that this is getting tackled:

That is, even with the experimental feature enabled the following doesn't work as intended:

# On Unix
PS> sh -c 'ls nosuch; :' 2>$null; $?
False # !! Should be $true

Since the exit code of the sh command is 0, $? should be $true - irrespective of the presence of stderr output, whether redirected or not.

This matters primarily with respect to && and ||:

# On Unix
PS> sh -c 'ls nosuch; :' 2>$null || 'why did I get here?'
why did I get here?

@mklement0
Copy link
Contributor

Just so we don't lose track of this: see #13393

@ghost
Copy link

ghost commented Aug 17, 2020

🎉This issue was addressed in #13361, which has now been successfully released as v7.1.0-preview.6.:tada:

Handy links:

@jazzdelightsme
Copy link
Contributor

I was REALLY happy to see this was addressed. However, although the problem seemed to be fixed in 7.1.0-preview.7, it has regressed in 7.1.0-rc.1. :'( Was this intentional? I didn't see anything in release notes about it; did I miss it? I hope that it can be re-fixed... @SteveL-MSFT @mklement0

@jazzdelightsme
Copy link
Contributor

(False alarm: it's still an experimental feature ('PSNotApplyErrorActionToStderr'), and I didn't realize it was on by default in prev7, and back to off in rc1.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Committee-Reviewed PS-Committee has reviewed this and made a decision WG-Interactive-Console the console experience WG-Remoting PSRP issues with any transport layer
Projects
None yet
Development

Successfully merging a pull request may close this issue.