2

Here's my args.bat file:

@echo off
python -c "import sys; print(sys.argv)" %*

I'm invoking python here because I know exactly how to interpret output, rather than guessing which quotes are in the string

Here's what happens if I invoke it from cmd:

>.\args.bat ^^^^^
More?
More?
['-c', '^']
>.\args.bat "^^^^^"
['-c', '^^^^^']

But here's what powershell does:

PS> .\args.bat ^^^^^
['-c', '^']
PS> .\args.bat "^^^^^"
['-c', '^']
PS> .\args.bat '^^^^^'
['-c', '^']
PS> .\args.bat "'^^^^^'"
['-c', '^']
PS> .\args.bat '"^^^^^"'
['-c', '^^^^^']

Why do I have to doubly-quote the argument when calling a batch file from powershell?


The same problem doesn't exist calling python from powershell:

PS> python -c "import sys; print(sys.argv)" "^^^^^"
['-c', '^^^^^']
PS> python -c "import sys; print(sys.argv)" '^^^^^'
['-c', '^^^^^']
PS> python -c "import sys; print(sys.argv)" ^^^^^
['-c', '^^^^^']

But persists when calling batch from python from powershell!

PS> python -c "import subprocess; subprocess.call(['args.bat', '^^^^^'])"
['-c', '^']

And batch from python from cmd:

> python -c "import subprocess; subprocess.call(['args.bat', '^^^^^'])"
['-c', '^']
Eric
  • 95,302
  • 53
  • 242
  • 374

1 Answers1

6

For the larger question of whether batch files can robustly relay arbitrary arguments (spoiler alert: no), see Eric's follow-up question.


tl;dr:

PS> .\args.bat --% "^^^^^"
['-c', '^^^^^']

Using --%, the stop-parsing symbol, makes PowerShell pass the remaining arguments through as-is, except for expanding cmd.exe-style environment variable references such as %OS%.

Conversely, you won't able to reference PowerShell variables or expressions.


PowerShell, as a shell in its own right, performs its own parsing first and then - an unfortunate necessity on Windows - reapplies quoting when composing a command line for an external program.

The way it does that has changed over time and has been the source of many problems in the past. These problems led to the introduction of --%, as described above, which has its own challenges, however.

The re-quoting is generally a necessity, however, given that PowerShell's syntax isn't necessarily understood by external programs.
Most notably, '...' quoting cannot be assumed to be understood by all external programs and therefore requires transforming to "...".

However, PowerShell re-quotes conditionally, based on whether it thinks it is necessary.

In the case at hand, because the content of the PowerShell "^^^^^" string contains no whitespace, PowerShell decides not to quote it when passing it through to the batch file; i.e.:

.\args.bat "^^^^^"

effectively becomes

.\args.bat ^^^^^

This is fine for most external programs, where ^ has no syntactic meaning, but in the case of a batch file (interpreted by cmd.exe) it matters, because ^ function as the escape char. in unquoted strings there.

To still force the use double-quoting in this scenario, as an alternative to the --% approach above, you can use embedded double-quoting, as you've already demonstrated in your question:

PS> .\args.bat '"^^^^^"' #  '...' quoting with embedded "..." quoting
['-c', '^^^^^']

The same problem doesn't exist calling python from powershell:
PS> python -c "import sys; print(sys.argv)" "^^^^^"

That's because cmd.exe isn't involved in this scenario, and even though the enclosing double quotes are lost when calling from PowerShell, Python treats the ^ instances are as literals.

But persists when calling batch from python from powershell
PS> python -c "import subprocess; subprocess.call(['args.bat', '^^^^^'])"

And batch from python from cmd: > python -c "import subprocess; subprocess.call(['args.bat', '^^^^^'])"

That's because you're again calling a batch file - with unquoted ^^^^^ in this case even from cmd.exe, because the enclosing '....' only have syntactical meaning to Python and aren't passed through - which reintroduces the special interpretation of ^.

Note that even "shell-less" methods of invoking a batch file (such as subprocess.call() from Python) still invariably invoke cmd.exe, because cmd.exe is the interpreter required to execute batch files.

When passing an unquoted string ^^^^^ to a batch file, there seem to be 2 rounds of interpretation: first, as part of argument parsing, each ^^ pair becomes a single ^, with any non-paired ^ getting discarded. Inside the batch file, you therefore see ^^, which, when used unquoted, becomes a single ^ - if you double-quote %* or %1 inside the batch file you'll see that ^^ was passed.

Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775