Powershell: CLI: `pwsh -Command -` and `pwsh -File -` unexpectedly exhibit pseudo-interactive behavior, lack support for argument passing

Created on 27 Feb 2017  路  11Comments  路  Source: PowerShell/PowerShell

Related: #9497

While pwsh -Command - and pwsh -File - both accept command input from the pipeline / from stdin:

  • they exhibit undesirable pseudo-interactive behavior:

    • they execute each line _one by one_, in _separate pipelines_.

      • additionally, -File - prints the prompt string and each input line before the output
    • they require pipelines that are spread across multiple lines for readability to be terminated with _two_ newlines (see below) - as would be necessary _interactively_, if PSReadLine weren't loaded.

  • they do not support combining - input with passing _arguments_:

    • While that limitation is documented for -Command - (-File - is undocumented altogether), there's no good reason for this limitation, and it limits the usefulness of the construct.

    • While you could (but in my mind shouldn't) argue that -Command - should be limited to providing a self-contained source-code snippet that doesn't expect arguments, at the least
      -File - should support passing arguments by placing them after the - - after all, you want to be able to pass arguments to a _script_ - whether that script is specified as a a _file_ or as a script's _content_ via stdin.

Thus, the following command, which spreads pipeline "hi" | % { "$_ there" } across _two_ lines - does _not_ work:

@'
"in"
"hi" |
  % { "$_ there" }
"out"
'@ | powershell -noprofile -command -

The above yields _only_ in, because the end of the multi-line command is never detected, causing it to be _quietly ignored_.

As evidence that this happens in the real world, see this Stack Overflow question, whose accepted answer provides more detail.

Inserting an empty line after the multi-line command fixes the problem:

@'
"in"
"hi" |
  % { "$_ there" }

"out"
'@ | powershell -noprofile -command -

The above now yields in, hi there and out - that is, the multi-line pipeline was correctly submitted due to the extra newline, as was the single-line statement afterwards.


Summary:

  • Both File - and -Command - should be fixed; and, on Unix /dev/stdin should be the same as passing -.

  • If -File - worked properly, there'd really be no need for -Command -.

  • -File - should optionally also accepted _arguments_.

Issue-Discussion WG-Interactive-Console

Most helpful comment

AddScript() adds a _script block_ that just _happens to be_ constructed from a _string argument_.

It's better to name methods for _what they do_, not the - somewhat incidental, possibly varying by overload - type of their _arguments_.

And, of course, the far more typical use of the term _script_ is to refer to a _script file_ - and that's where the confusion comes from.

All 11 comments

At first look I'd expect that "-Command -" accept only one line command but how input multi-line one? And it is not clear that is semantic of "-File script.ps1 -"

Also I'd expect that we read console input if there is not explicit redirection but currenly the dash accepted as argument that can confuse.

At first look I'd expect that -Command - accept only one line command but how input multi-line one?

As argued in https://github.com/PowerShell/PowerShell/issues/9494#issuecomment-487621948, there's really no point in supporting -Command - altogether, if -File - worked properly.

Given that we cannot remove it: Just like you can already pass a multi-line argument to -Command, the entirety of stdin input should be executed as a snippet of source code.
(Line-by-line _interactive_ execution doesn't really make sense - just start a PowerShell session normally to do that.)

it is not clear that is semantic of -File script.ps1 -

As stated in the linked comment, the - is not special at all in this case: since it _follows_ a -File argument - script.ps1 - it is just a (positional) _argument_ to pass to script.ps1; - is only special _in lieu of_ a script-file argument.

Given that we cannot remove it: Just like you can already pass a multi-line argument to -Command, the entirety of stdin input should be executed as a snippet of source code.

We have API Powershell.AddCommand() and Powershell.AddScript() - I believe "-Command" behavior must fit first and "-File" - second.

It is the other way around:

.AddScript() is the one that behaves like -Command - you can pass it an arbitrary snippet of PowerShell code - the method is poorly named, .AddScriptBlock() would have made more sense.

.AddCommand() is for adding a command _name or path_, with the need to specify arguments separately; as such it is akin to -File.

Aside from that, there's no requirement of one-to-one mapping to SDK methods here.

Aside from that, there's no requirement of one-to-one mapping to SDK methods here.

My comment was about expected behavior. Both methods is often used in our tests and I'd expect such behavior in CLI too.

As shown, the names of the SDK methods not only do not align with the CLI parameters, but suggest the _opposite_ mapping.

It is the CLI parameters that are sensibly named, and their _fundamental_ semantics must not and should not change (-File is self-describing (the fact that the argument must be a _script_ file is implied), and -Command / -c aligns with the long-established -c command in POSIX-like shells (and is akin to /c in cmd.exe)).

What _should_ change, however, is how they deal with _stdin input_, as (now) suggested in the OP and argued in https://github.com/PowerShell/PowerShell/issues/9494#issuecomment-487648892

P.S., @iSazonov: You could open a separate issue suggesting more sensible alias names for the SDK methods (obviously, the old names cannot be removed); especially .AddScript() keeps causing confusion.

@mklement0 I think this is a dead proposal (add aliases) because it is a _public_ API.

-File - is not documented so we could change its behavior as we need.

-File - is not documented so we could change its behavior as we need.

That's a good point.

We can then deprecate -Command - (and leave it as is) to support the following idioms, mostly based on -File, in conjunction with fixing #9497:

# Execute *code* piped from stdin.
... | pwsh   # same as: ... | pwsh -File -

# Execute *code* piped from stdin *with arguments* 
# (`File -` is then required for disambiguation)
... | pwsh -File - args...

# Provide *input data* via stdin - to a script file
... | pwsh -File someScript.ps1 [args...] 

# Provide *input data* via stdin - to a string containing commands
... | pwsh -Command $snippetOfPsCode

The unfortunate thing - which we cannot change - is that -Command doesn't accept _arguments_ to pass to $snippetOfPsCode (which the latter would see as $Args or bind to its parameters) - it simply considers the remaining arguments to be _part of the command string_.

I think this is a dead proposal (add aliases) because it is a _public API_.

I don't think it's a pressing issue, and the downside is that it bloats the SDK, but _why_ couldn't we add new, more descriptive method names that effectively call their less descriptively named predecessors? Of course, you'd then need to know what version of the SDK you're targeting.

I'd expect that AddScriptBlock() accept a parameter of ScriptBlock type, AddScript() - string parameter.

AddScript() adds a _script block_ that just _happens to be_ constructed from a _string argument_.

It's better to name methods for _what they do_, not the - somewhat incidental, possibly varying by overload - type of their _arguments_.

And, of course, the far more typical use of the term _script_ is to refer to a _script file_ - and that's where the confusion comes from.

Was this page helpful?
0 / 5 - 0 ratings