PowerShell / PowerShell

PowerShell for every system!
https://microsoft.com/PowerShell
MIT License
43.66k stars 7.08k forks source link

`$ErrorActionPreference = 'Stop'` ignored, when command read from stdin. #21808

Closed philcerf closed 2 weeks ago

philcerf commented 3 weeks ago

Prerequisites

Steps to reproduce

Hey.

Another issue when reading the commands from stdin, possibly related to #21784.

Consider something like:

$ pwsh -NonInteractive -NoProfile -Command '$ErrorActionPreference = "Stop"; Get-Process -foo; Write-Host BLA'
Get-Process: A parameter cannot be found that matches parameter name 'foo'.

This exits on the error and BLA is not written.

The same when the command is given as argument but with newlines in it:

$ pwsh' -NonInteractive -NoProfile -Command $'$ErrorActionPreference = "Stop"\nGet-Process -foo\nWrite-Host BLA'
Get-Process: 
Line |
   2 |  Get-Process -foo
     |              ~~~~
     | A parameter cannot be found that matches parameter name 'foo'.

Now when read from stdin:

$ pwsh -NonInteractive -NoProfile -Command '-' <<'END'                                                         
$ErrorActionPreference = 'Stop'
Get-Process -foo
Write-Host BLA

END

gives:

Get-Process: A parameter cannot be found that matches parameter name 'foo'.
BLA

However:

$ pwsh -NonInteractive -NoProfile -Command '-' <<'END'                                                        
$ErrorActionPreference = 'Stop'
Get-Process -foo; Write-Host BLA

END

works as expected and does not print BLA:

Get-Process: A parameter cannot be found that matches parameter name 'foo'.

WTF?! ... Reading commands from stdin seems basically completely broken.

Expected behavior

reading commands from stdin should work as if given as `-Command` argument.

Actual behavior

it's completely broken

Error details

No response

Environment data

N/A

Visuals

No response

mklement0 commented 3 weeks ago

See also:

rhubarb-geek-nz commented 3 weeks ago

There is a consistency rather than being completely broken.

Consider

pwsh -NonInteractive -NoProfile -Command '-' <<'END'
$ErrorActionPreference = 'Stop' ; Get-Process -foo; Write-Output foo
Get-Process -foo; Write-Output bar
Write-Host BLA
$ErrorActionPreference
END

Results in

Get-Process: A parameter cannot be found that matches parameter name 'foo'.
Get-Process: A parameter cannot be found that matches parameter name 'foo'.
BLA
Stop

My explanations often start with PowerShell is not a UNIX shell. UNIX programs are pipe/stream based, PowerShell is record based.

Now consider each line in stdin as its own record and executed one after the other, however in the same session so the state accumulates, so setting $ErrorActionPreference is persistent in the session.

So notice that the Write-Output 'foo' and Write-Output 'bar' were never executed because within that line the Get-Process caused a terminating error.

jborean93 commented 3 weeks ago

The stdin reader acts like the command line repl so things like $ErrorActionPreference = 'Stop' does apply but the next line is read in stdin so will still run. It's just like copy/pasting the stdin data to the actual console.

I find when you want to use -Command - with stdin input you are best to wrap the code to run in a scriptblock so that the standard rules apply to a file to run.

pwsh -NoProfile -NoLogo -Command - << EOF
& {
    code here
}

EOF

You still need the trailing newlines to ensure PowerShell sees the end of the } and runs it but everything inside the {} runs as one statement so things like $ErrorActionPreference = 'Stop' applies.

mklement0 commented 3 weeks ago

Indeed, as discussed in the previously mentioned #3223 and in #15331, both -Command - and -File - were seemingly designed to exhibit REPL-like behavior, were the code being provided via stdin isn't run as a whole - as it would be with a direct -Command argument or with a file argument passed to -File - but statement by statement.

-File - is especially baffling, since it prints the prompt string and the statement about to be executed before each statement, but there's seemingly code out there that relies on this - see https://github.com/PowerShell/PowerShell/issues/15331#issuecomment-830451818

Given the desire to maintain backward compatibility, these behaviors won't change, though even in the context of this behaviors there are two bugs:

In other words: the following should print 1 after the parser error message, whereas it currently reports 0

# From Bash
pwsh -NoProfile -NoLogo -Command - <<'EOF'
& {
    1 -eqFOO 1
}

EOF
echo $?

Taking a step back:

philcerf commented 3 weeks ago

@rhubarb-geek-nz

There is a consistency rather than being completely broken.

My explanations often start with PowerShell is not a UNIX shell. UNIX programs are pipe/stream based, PowerShell is record based. Now consider each line in stdin as its own record and executed one after the other, however in the same session so the state accumulates, so setting $ErrorActionPreference is persistent in the session.

Which would be fine, if PowerShell really always did it like that, but it doesn't. If I execute the whole thing as one string (with newlines) passed as single argument to -Command it works as expected. As it probably also does when executing it as .ps1 file.

IMO it simply never makes sense to have the exactly same script behave completely different depending on how it's read.

Now as @jborean93 and @mklement0 mentioned, the stdin reader apparently behaves like some semi-interactive prompt.
But again, this seems completely strange to me, especially when invoked with -NonInteractive.
Plus it kinda defeat's the whole purpose of reading from stdin.

As you might have guessed, I come from the Linux world, where a program in such a case would check whether stdin is connected to a terminal or not. No idea whether windows has such a concept, but if one says -File - or -Command - one most like does not want something like an interactive prompt, cause then one would have simply omitted the options altogether?

Even if it's decided that this is in fact the desired behaviour, there should IMO be quite some warnings about it in the help texts to -Command - and -File - cause right now those read as if they'd behave just as if instead of - a real script respectively script file would be given (which again would behave as expected).

Taking a step back:

Well, in the end it's your decision, and if there'd be some warning in the description of these options which tells about the behaviour I'd also consider the issue as "solved", but...

From a perspective of what's common sense and assuming that PowerShell will be a long time around, I would suggest to rather do the compatibility break[0] now than live one with something that is in itself inconsistent[1].

[0] And do you really think that a lot people actually depend on that behaviour? I mean it's even not documented as it actually behaves, so anyone depending on this would already use it by accident. Plus what sense would it make to use -Commnd - and want the script to be semi-interactively read, when one can just omit the option for the same?

[1] Cause you'd then have -Command someReadCommandsHere which behaves non-interactively, -File someRealFile which behaves non-interactively, -CommandFromStdin which behaves non-interactively, but -Command - and -File - which suddenly behave semi-interactively. And at the same time, as you've said, for -CommandWithArgs (which I guess is modelled after POSIX behaviour - i.e. a script file + args) it would probably make quite some sense to really change the behaviour - so yet another unexpected deviation from homogenous behaviour.

mklement0 commented 3 weeks ago

I hear you - #3223 pretty much argued the same points.

I have no say in what technically breaking changes are considered acceptable; my previous comment links to at least one instance of existing code that would break.

As for amending the docs: I encourage you to file an issue at https://github.com/MicrosoftDocs/PowerShell-Docs/issues/new?template=04-customer-feedback.yml&pageUrl=https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fpowershell%2Fmodule%2Fmicrosoft.powershell.core%2Fabout%2Fabout_Pwsh%3Fview%3Dpowershell-7.4&pageQueryParams=%3Fview%3Dpowershell-7.4&contentSourceUrl=https%3A%2F%2Fgithub.com%2FMicrosoftDocs%2FPowerShell-Docs%2Fblob%2Fmain%2Freference%2F7.4%2FMicrosoft.PowerShell.Core%2FAbout%2Fabout_Pwsh.md&documentVersionIndependentId=4ccd2aa8-1a49-5b9d-04ee-5d2c61fb3c38&feedback=%0A%0A%5BEnter+feedback+here%5D%0A&author=%40sdwheeler&metadata=*+ID%3A+583c1d4c-486c-9338-2cd2-9ea3bc30cefb+%0A*+Service%3A+**powershell**&labels=needs-triage

🤞that at least -CommandWithArgs will be modified to work sensibly.

SteveL-MSFT commented 3 weeks ago

I think it's more than a bucket 3 breaking change to change the - behavior. However, if the desire is to be able to pipe a script to pwsh via STDIN, then wouldn't it be better to introduce a new parameter strictly for that purpose?

philcerf commented 3 weeks ago

As for amending the docs

I mostly meant what comes out of -help and friends.

As for -, one further way of handling that would be, to completely remove it for -Command and -File… and instead provide completely new options for both use cases (non-interactive vs semi-interactive).
Perhaps should there ever be a new major version.

That way, any current user would fail fast and could quickly adapt their code, without any silent issues.
The benefit would be, that you'd get rid of the ambiguously different behaviour within a single option.

One could further argue, that at least for -File - it should have been even more clear to anyone that the behaviour should have been from the beginning like as if it would be a (script) file, i.e. non-interactive... and that the current behaviour i simply a bug and not something where one could rely upon for backwards compatibility.

Anyway... I'm now simply using multi-line strings (instead of here-documents) to pass a script to PowerShell via -Command and no longer via stdin... so for me it doesn’t really matter much.

mklement0 commented 3 weeks ago

I mostly meant what comes out of -help and friends.

Command-specific help topics as well as conceptual help topics including the one for the CLI, about_Pwsh, are maintained both online and - assuming you've run Update-Help - locally.

However, a duplicate of the content of about_Pwsh is also baked into the pwsh binary (for invocation via pwsh -help, pwsh --help, pwsh -?, pwsh /?), which therefore requires a source-code change - but I'd still start with creating an issue in the docs repo - they'll know what to do.


@SteveL-MSFT, personally I think that fixing the problem in the context of -CommandWithArgs is sufficient and preferable, but I wouldn't complain about a separate, new parameter either.


As for what could be done if backward compatibility were NOT a concern (a collection of proposed breaking changes can be found in #6745, but there are no plans for a version allowed to make such fundamentally breaking changes):

mklement0 commented 3 weeks ago

I've created a docs issue:

kilasuit commented 2 weeks ago

I think it's more than a bucket 3 breaking change to change the - behavior. However, if the desire is to be able to pipe a script to pwsh via STDIN, then wouldn't it be better to introduce a new parameter strictly for that purpose?

I agree that adding a new parameter makes more sense than changing behaviour as can then be better documented than this current mix of semi-broken expectations vs how PowerShell currently works which gives this mixed outputs.

Interactive-UX WG (which I can't make) will be discussing this later tonight so expect a response from one of the rest of the WG on this

theJasonHelmick commented 2 weeks ago

@philcerf - Thank you for this issue. The existing -command and -file were not intended for this scenario. The WG is not certain that the new scenario is needed since other ways are available to achieve this behavior. We would need to see more compelling use cases to consider adding a feature for this use case. We will update the documentation (Thank you @mklement0 ) to explain the current behavior.

microsoft-github-policy-service[bot] commented 2 weeks ago

This issue has been marked as declined and has not had any activity for 1 day. It has been closed for housekeeping purposes.

microsoft-github-policy-service[bot] commented 2 weeks ago

This issue has been marked as by-design and has not had any activity for 1 day. It has been closed for housekeeping purposes.

microsoft-github-policy-service[bot] commented 2 weeks ago

📣 Hey @philcerf, how did we do? We would love to hear your feedback with the link below! 🗣️

🔗 https://aka.ms/PSRepoFeedback

Microsoft Forms