Why does PowerShell not recognize quoted parameters?

梦想的初衷 提交于 2019-12-08 02:27:35

问题


Why does PowerShell treat quoted parameters differently when you invoke the script directly (in a PowerShell console or ISE) or when you invoke it via another PowerShell instance?

Here is the script (TestQuotes.ps1):

param
(
    [string]
    $Config = $null
)

"Config = $Config"

Here are the results:

PS D:\Scripts> .\TestQuotes.ps1 -Config "A B C"
Config = A B C
PS D:\Scripts> PowerShell .\TestQuotes.ps1 -Config "A B C"
Config = A
PS D:\Scripts> .\TestQuotes.ps1 -Config 'A B C'
Config = A B C
PS D:\Scripts> PowerShell .\TestQuotes.ps1 -Config 'A B C'
Config = A

Any ideas?


回答1:


According to the PowerShell.exe Command-Line Help, the first argument for the powershell executable is -Command:

PowerShell[.exe]
       [-Command { - | <script-block> [-args <arg-array>]
                     | <string> [<CommandParameters>] } ]
       [-EncodedCommand <Base64EncodedCommand>]
       [-ExecutionPolicy <ExecutionPolicy>]
       [-File <FilePath> [<Args>]]
       [-InputFormat {Text | XML}]
       [-Mta]
       [-NoExit]
       [-NoLogo]
       [-NonInteractive]
       [-NoProfile]
       [-OutputFormat {Text | XML}]
       [-PSConsoleFile <FilePath> | -Version <PowerShell version>]
       [-Sta]
       [-WindowStyle <style>]

PowerShell[.exe] -Help | -? | /?

Any text after -Command is sent as a single command line to PowerShell.

...

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

What child PowerShell instance actually receives is easy to check with echoargs:

PS > echoargs .\TestQuotes.ps1 -Config "A B C"
Arg 0 is <.\TestQuotes.ps1>
Arg 1 is <-Config>
Arg 2 is <A B C>

Which is further parsed by the child instance to:

'.\TestQuotes.ps1' '-Config' 'A' 'B' 'C'

And this is where you get your "wrong" result: Config = A

If you specify -File argument, you'll get the desired result:

PS >  PowerShell -File .\TestQuotes.ps1 -Config 'A B C'
Config = A B C

PS >  PowerShell -Command .\TestQuotes.ps1 -Config 'A B C'
Config = A



回答2:


tl;dr

If you're calling another PowerShell instance from PowerShell, use a script block ({ ... }) to get predictable behavior:

Windows PowerShell:

powershell.exe { .\TestQuotes.ps1 -Config "A B C" }

PowerShell Core:

pwsh { .\TestQuotes.ps1 -Config "A B C" }

This will make the quoting of arguments work as expected - and it will even return objects with near-type fidelity from the invocation, because serialization similar to that used for PowerShell remoting is employed.

Note, however, that this is not an option when calling from outside of PowerShell, such as from cmd.exe or bash.

Read on for an explanation of the behavior you saw in the absence of a script block.


The PowerShell CLI (calling powershell.exe (Windows PowerShell) / pwsh.exe (PowerShell Core) supports only one parameter that accepts a positional argument (i.e., a value not preceded by a parameter name such as -Command).

  • In Windows PowerShell, that (implied) parameter is -Command.

  • In PowerShell Core, it is -File.

    • The default had to be changed to support use of the CLI in Unix shebang lines.

Any arguments after the first positional argument, if any, are considered:

  • in Windows PowerShell: part of the snippet of PowerShell source code passed to the (implied)
    -Command parameter.

  • in PowerShell Core: individual arguments to pass as literals to the script file specified in the first positional argument (the implied -File argument).


Arguments passed to -Command - whether implicitly or explicitly - undergo two rounds of parsing by PowerShell, which can be tricky:

  • In the first round, the "..." (double quotes) enclosing individual arguments are stripped.

    • If you're calling from PowerShell, this even applies to arguments that were originally '...'-enclosed (single-quoted), because, behind the scenes, PowerShell re-quotes such arguments to use "..." when calling external programs (which includes the PowerShell CLI itself).
  • In the second round, the stripped arguments are joined with spaces to form a single string that is then interpreted as PowerShell source code.


Applied to your invocation, this means that both PowerShell .\TestQuotes.ps1 -Config "A B C" and PowerShell .\TestQuotes.ps1 -Config 'A B C' resulted in PowerShell ultimately parsing and executing the following code:

.\TestQuotes.ps1 -Config A B C

That is, due to the 2 rounds of parsing, the original quoting was lost, resulting in three distinct arguments getting passed, which explains your symptom.


If you had to make your command work without a script block, you have two options:

  • Use -File, which only applies one round of parsing:

    powershell.exe -File .\TestQuotes.ps1 -Config "A B C"
    
    • That is, aside from stripping the enclosing "...", the resulting arguments are treated as literals - which, however, is typically what you want.
  • With (implied) -Command, apply an extra layer of quoting:

    powershell.exe -Command .\TestQuotes.ps1 -Config '\"A B C\"'
    

Note how PowerShell requires " chars. to be escaped as \" in arguments passed to its CLI, whereas inside PowerShell, `" (or "") must be used.



来源:https://stackoverflow.com/questions/55201798/why-does-powershell-not-recognize-quoted-parameters

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