2

Very simple setup, please do try this at home.

test.ps1

$args

test.rb (replace with language of choice, if necessary)

p ARGV

test.bat

@echo off
ruby test.rb "foo bar moo"
powershell .\test.ps1 "foo bar moo"
pause

Run test.bat. Guess the output.

If you think like me, you're wrong.

Output

["foo bar moo"]
foo
bar
moo

Ruby is my witness that the three words are passed as single argument. What in Bill Gates' name is PowerShell doing? And how can I make it work logically?

Or what am I missing?

Neonit
  • 680
  • 8
  • 27
  • Okay, I found a workaround at least: `powershell .\test.ps1 """foo bar moo"""`. So it requires escaped quotes? But why? – Neonit May 09 '21 at 12:42

1 Answers1

4

In order to invoke a script file (.ps1) with arguments, use the PowerShell CLI's -File parameter, not the (implied) -Command parameter:

powershell -File .\test.ps1 "foo bar moo"

Note that PowerShell (Core) (pwsh, PowerShell versions starting with v6) now defaults to -File.[1]


As for what you tried:

powershell .\test.ps1 "foo bar moo"

is implicitly the same as (you can also use -c instead of -Command):

powershell -Command .\test.ps1 "foo bar moo"

and that causes PowerShell to evaluate the arguments in two stages:

  • First, syntactic " around individual arguments are stripped and the resulting arguments are then joined with spaces to form a single string.

  • Second, the resulting string - .\test.ps1 foo bar moo in this case - is then interpreted as PowerShell source code, and, as you can see, the original " have been stripped by then, resulting in the script receiving 3 arguments.

Note:

  • -Command's behavior therefore also involves subjecting the arguments to additional interpretation according to PowerShell's rules, unlike with -File. E.g.,
    powershell -File .\test.ps1 $HOME would pass $HOME verbatim to the script, whereas
    powershell -Command .\test.ps1 $HOME would pass the value of PowerShell's automatic $HOME variable.

  • With -Command, passing " characters that are to become part of the resulting PowerShell code being evaluated indeed requires escaping:

    • From PowerShell's perspective, the most robust form is \" (sic), though that can result in broken command lines when calling from batch files / cmd.exe
    • In cases where that's a problem, pass a single argument enclosed in "..." to -Command, and escape pass-through " as "" in PowerShell (Core) / as "^"" (sic) in Windows PowerShell.
  • See this answer for more guidance on when to use -File vs. -Command.


[1] It is understandable to expect -File, not -Command to be the default parameter, given that most shell and script-engine CLIs require an explicit parameter (typically, -c or -e) to pass a command string rather than a script-file path. Fortunately, in PowerShell (Core) 7+ -File now is the default, a change that was necessary to support shebang lines on Unix.
Additionally, most shell and script-engine CLIs expect the command string to be passed as a single argument, with any additional arguments then becoming arguments passed to the code in the command string, whereas PowerShell simply stitches multiple arguments together and then interprets the result as the command string; see GitHub issue #4024

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    TL;DR: RTFM. Thanks for explaining this. I somehow was convinced that just passing the file and arguments were the way to go. Probably because it coincidentally worked in the past. – Neonit May 09 '21 at 15:42
  • 1
    @Neonit, yes, situationally it can work. And it's understandable to expect `-File` to be the default, not `-Command`, given that most CLIs require an explicit parameter to pass a command string, such as `-c` or `-e`. Fortunately, in PowerShell (Core) 7+ `-File` now _is_ the default (this was necessary to support shebang lines on Unix). I've also included this information as a footnote to the answer, and I've added a link to an answer that provides further guidance on `-File` cs. `-Command`. – mklement0 May 09 '21 at 16:40