Start-Process with PowerShell.exe exhibits different behavior with embedded single quotes and double quotes

耗尽温柔 提交于 2021-02-18 18:59:38

问题


First, in case anyone wonders why we're invoking PowerShell in this way, I ran into this behavior with a more complex command we were building, but the behavior can be exhibited using a more simple example as shown below. In practice, we are running a command under 32-bit PowerShell as admin with additional variables rendered in the string (hence why I don't simply use single-quotes for the outer portion), but that doesn't seem to factor into the behavior below.


When I invoke PowerShell through Start-Process, I get some odd behaviors if I use single quotes surrounding the -Command parameter to the PowerShell executable. For example:

Start-Process -FilePath Powershell.exe -ArgumentList "-Command 'ping google.com'"

just renders ping google.com as the output and exits. However, if I use nested double-quotes instead of the single-quotes as follows:

Start-Process -FilePath Powershell.exe -ArgumentList "-Command `"ping google.com`""

ping runs and produces the expected output:

Pinging google.com [173.194.78.113] with 32 bytes of data:

Reply from 173.194.78.113: bytes=32 time=34ms TTL=45

Reply from 173.194.78.113: bytes=32 time=33ms TTL=45

Reply from 173.194.78.113: bytes=32 time=35ms TTL=45

Reply from 173.194.78.113: bytes=32 time=32ms TTL=45

Ping statistics for 173.194.78.113:

Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),

Approximate round trip times in milli-seconds:

Minimum = 32ms, Maximum = 35ms, Average = 33ms

Why does the command string simply render as-is instead of execute if I use the single-quotes for the -Command parameter instead of double-quotes?


回答1:


js2010's helpful answer is correct in that the use of Start-Process is incidental to your question and that the behavior is specific to PowerShell's CLI (powershell.exe for Windows PowerShell, and pwsh for PowerShell [Core] 6+):

On Windows[1], there are two layers of evaluation to consider:

  • (a) The initial parsing of the command line into arguments.

  • (b) The evaluation of the (space-concatenated) resulting arguments as PowerShell code, due to use of the -Command (-c) CLI parameter.

Re (a):

As most console programs on Windows do, PowerShell recognizes only " chars. (double quotes) - not also ' (single quotes) - as having syntactic function.[2]

  • That is, unless " chars. are escaped, they are string delimiters that themselves are removed during parsing.

  • As implied by the above, ' chars. are not removed.

Whatever arguments are the result - an array of possibly "-stripped tokens - are concatenated with a single space between them, which becomes the input for (b).


To explain this in the context of your example, with Start-Process taken out of the picture:

Note: The following applies to calling from cmd.exe or any context where no shell is involved (including Start-Process and the Windows Run (WinKey-R) dialog). By contrast, PowerShell re-quotes the command line behind the scenes to always use ", if needed.
To put it differently: the following applies to command lines as seen by PowerShell.

Single-quoted command:

# Note: This *would* work for calling ping if run from 
#       (a) PowerShell itself or (b) from a POSIX-like shell such as Bash.
#       However, via cmd.exe or any context where *no* shell is involved,
#       notably Start-Process and the Windows Run dialog, it does not.
powershell -Command 'ping google.com'
  • (a) results in PowerShell finding the following two verbatim arguments: 'ping and google.com'

  • (b) concatenates these verbatim arguments to form 'ping google.com'[2] and executes that as PowerShell code, therefore outputs the content of this string literal, ping google.com

Double-quoted command:

powershell -Command "ping google.com"
  • (a) results in PowerShell stripping the syntactic " characters, finding the following, single verbatim argument: ping google.com

  • (b) then results in this verbatim argument - ping google.com - being executed as PowerShell code, which therefore results in a command invocation, namely of the ping executable with argument google.com.


[1] On Unix-like platforms, the first layer doesn't apply, because programs being invoked only ever see an array of verbatim arguments, not a command line that they themselves must parse into arguments. Not that if you call the PowerShell CLI from a POSIX-like shell such as bash on Unix-like platforms, it is that shell that recognizes single-quotes as string delimiters, and strips them before PowerShell sees them.

[2] Surprisingly, on Windows it is ultimately up to each individual program to interpret the command line, and some do choose to also recognize ' as string delimiters (e.g., Ruby). However, many programs on Windows - including PowerShell itself - are based on the C runtime, which only recognizes ".

[3] As an aside, note that this implies that whitespace normalization is taking place: that is,
powershell -Command 'ping google.com' would equally result in 'ping google.com'.




回答2:


We can drop start-process from this. It seems true that embedded double and single quotes are treated differently by the powershell executable. Start-process is not relevant to this behavior. This is the way powershell behaves on the command line. I don't see a way to change it. You can also do "start-job -runas32", but it wouldn't be elevated. Another option is powershell's "-file" option instead of "-command". These examples are run from within Osx (unix) powershell core:

pwsh -c "'ping -c 1 google.com'"

ping -c 1 google.com


pwsh -c "`"ping -c 1 google.com`""

PING google.com (172.217.10.110): 56 data bytes
64 bytes from 172.217.10.110: icmp_seq=0 ttl=52 time=20.020 ms

--- google.com ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 20.020/20.020/20.020/0.000 ms


pwsh -c '"ping -c 1 google.com"'        
PING google.com (172.217.6.206): 56 data bytes
64 bytes from 172.217.6.206: icmp_seq=0 ttl=52 time=22.786 ms

--- google.com ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 22.786/22.786/22.786/0.000 ms



回答3:


The help for powershell.exe mentions nothing about supporting single-quotes... why would you be using them?

-Command
    Executes the specified commands (and any parameters) as though they were
    typed at the Windows PowerShell command prompt, and then exits, unless
    NoExit is specified. The value of Command can be "-", a string. or a
    script block.

    If the value of Command is "-", the command text is read from standard
    input.

    If the value of Command is a script block, the script block must be enclosed
    in braces ({}). You can specify a script block only when running PowerShell.exe
    in Windows PowerShell. The results of the script block are returned to the
    parent shell as deserialized XML objects, not live objects.

    If the value of Command is a string, Command must be the last parameter
    in the command , because any characters typed after the command are
    interpreted as the command arguments.

    To write a string that runs a Windows PowerShell command, use the format:
        "& {<command>}"
    where the quotation marks indicate a string and the invoke operator (&)
    causes the command to be executed.


来源:https://stackoverflow.com/questions/61109118/start-process-with-powershell-exe-exhibits-different-behavior-with-embedded-sing

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!