-
Notifications
You must be signed in to change notification settings - Fork 7.2k
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
Comments
|
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. |
|
The parallel to The MSDN public API docs say:
|
|
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 |
|
Thanks, @lzybkr . That PR seems to further codify the undesirable separation between native command and cmdLet by adding a Boolean flag to |
|
@SteveL-MSFT: The ISE is incidental, the problem does affect PS Core:
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
, 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:
CAVEAT: The behavior changes fundamentally when stderr is redirected (becomes like the remoting-scenario behavior(!)): see #4002 In short:
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
In short:
|
|
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 This is why |
|
@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 |
|
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.ErrorIn Windows PowerShell, you'll get: In PowerShell Core, you'll get: |
|
I've found an apparently undocumented workaround that captures If you add Now I don't particularly like the syntax, but stumbled on it when I tried My poking around was inspired by the If a further similar redirection-like syntax were able to add |
|
Also, |
|
@lzybkr: Thanks for finding the real cause - I've cleaned up my previous post accordingly.
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. But I can definitely see how you do NOT want your script to abort with 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; $? } # ABORTSAs @BurtHarris has discovered,
However, as @BurtHarris mentions, there is value in making stderr output available to subsequent commands, at least on demand, but 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 An |
|
@BurtHarris: Some more thoughts on the syntax of
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 # 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>&errTherefore, to have the same flexibility as with For instance: # Double the `&` to express the desire to pass through?
cmd /c nosuch 2>&&errThat said, even the existing collect-in-output-variable common parameters already have a mini-DSL (micro-DSL?): prepending |
|
You can still defeat the fix in 161940c via the results of remoting or jobs, in the caller's context:
# 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
# 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 executesWhile 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 |
|
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 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
# On Unix
> Receive-Job -Wait -AutoRemove (Start-Job { $Error.Clear(); sh -c nosuch; $Error.Count })
/bin/sh: nosuch: command not found
1
|
|
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. |
|
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). |
|
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.. |
|
(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: No trap is fired because PowerShell is unaware of the error output. But as soon as you introduce redirection: 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. |
|
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. |
|
Interesting backstory, indeed.
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
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'
Separately, it's hard to imagine that anyone has relied on the obscure behavior of Therefore, I think this falls into Bucket 3: Unlikely Grey Area and should be fixed as well.
Even leaving backward compatibility aside, I don't think that
Thus, it would make sense for
Additionally, it would be nice to be able to do that on a per-command basis, analogous to |
powershell error handling quirks on CI related to PowerShell/PowerShell#3996
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.
|
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.) |
What about to add an option to PowerShell that will treat |
|
@n9, we already have the ability to redirect the stderr stream selectively with While this is somewhat wasteful - every line is wrapped in a However, there are
It sounds like all that is needed to exempt There's also the tangentially related #11134 |
|
@mklement0, I am currently using redirection. My suggestion is to have one option (similar to
The current implementation is good for PowerShell commands, but complicates invoking of native commands. |
|
Your suggestion won't solve 1., because writing to the information stream requires that stderr lines be wrapped in
Generally, we want to unify treatment of PowerShell commands and external programs as much as possible. |
|
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 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.] |
|
@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 |
|
@PowerShell/powershell-committee discussed this and agrees for 7.1 we should have |
|
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 $trueSince the exit code of the This matters primarily with respect to # On Unix
PS> sh -c 'ls nosuch; :' 2>$null || 'why did I get here?'
why did I get here? |
|
Just so we don't lose track of this: see #13393 |
|
🎉This issue was addressed in #13361, which has now been successfully released as Handy links: |
|
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 |
|
(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.) |
I discovered recently that the handling of
stderrfrom native commands isn't being dealt with consistently between Windows PowerShell and Windows PowerShell ISE.Steps to reproduce
Actual ISE behavior (expected)
Native commands writing to
stderrwhile$errorActionPreference="stop"generate a catchableNativeCommandErrorwhich encapsulates the original error message in aErrorRecord. It is easy to capture, analyze, and reword error messages in context.Actual CLI behavior
No
NativeCommandErroris generated, no matter how$errorActionPreferenceis set. Thestderrfrom the native command is piped straight through to the default processstderr.This behavior is a serious loss of functionality, leading some to believe that PowerShell can't distinguish between
stderrandstdout. 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:
Environment data
Both powershell.exe and powershell_ise.exe report the same version information:
The text was updated successfully, but these errors were encountered: