Neonit
Neonit

Reputation: 782

Why does PowerShell split arguments at spaces when invoked by a batch script and how to fix it?

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?

Upvotes: 3

Views: 2595

Answers (1)

mklement0
mklement0

Reputation: 439487

In order to invoke a script file (.ps1) with arguments, use the PowerShell CLI's -File (-f) parameter, not the (implied) -Command (-c) 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 that the same applies in cases where the script path has embedded spaces and must therefore be quoted:

      :: !! BROKEN, because PowerShell *removes the double quotes and 
      :: !! ultimately executes: & C:\path with spaces\script.ps1         
      powershell -Command ^& "C:\path with spaces\script.ps1"
      
  • -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

Upvotes: 5

Related Questions