PowerShell 从命令行参数中去除双引号 [英] PowerShell stripping double quotes from command line arguments

查看:129
本文介绍了PowerShell 从命令行参数中去除双引号的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最近我在使用 PowerShell 中的 GnuWin32 时遇到了一些问题,只要涉及到双引号.

经过进一步调查,PowerShell 似乎正在从命令行参数中去除双引号,即使正确转义也是如此.

PS C:Documents and SettingsNick>echo '你好"'你好"PS C:Documents and SettingsNick>echo.exe '你好"'你好PS C:Documents and SettingsNick>echo.exe '你好"'你好"

请注意,当传递给 PowerShell 的 echo cmdlet 时,双引号是存在的,但是当作为参数传递给 echo.exe 时,双引号将被去除,除非使用反斜杠(即使 PowerShell 的转义字符是反斜杠,而不是反斜杠).

这对我来说似乎是一个错误.如果我将正确的转义字符串传递给 PowerShell,那么 PowerShell 应该处理它调用命令所需的任何转义.

这里发生了什么?

目前,修复方法是根据这些规则转义命令行参数(这些规则似乎被 PowerShell 用来调用 .exe 文件的 CreateProcess API 调用(间接)使用):

  • 要传递双引号,请使用反斜杠转义:" ->"
  • 要传递一个或多个反斜杠后跟双引号,请使用另一个反斜杠转义每个反斜杠并转义引号:\\" ->\"
  • 如果后面没有双引号,则反斜杠不需要转义:\ ->\

请注意,为了将 Windows API 转义字符串中的双引号转义到 PowerShell,可能需要进一步转义双引号.

以下是一些示例,使用来自 GnuWin32 的 echo.exe:

PS C:Documents and SettingsNick>echo.exe "`"""PS C:Documents and SettingsNick>echo.exe "\\`""\"PS C:Documents and SettingsNick>echo.exe\"\

我想如果你需要传递一个复杂的命令行参数,这会很快变成地狱.当然,CreateProcess() 或 PowerShell 文档中都没有记录这些内容.

另请注意,这不是将带双引号的参数传递给 .NET 函数或 PowerShell cmdlet 所必需的.为此,您只需要将双引号转义到 PowerShell.

正如 Martin 在他出色的回答中指出的那样,这在 CommandLineToArgv() 函数(CRT 用于解析命令行参数)文档中有记录.

解决方案

TL;DR

如果您只需要 Powershell 5 的解决方案,请参阅:

ConvertTo-ArgvQuoteForPoSh.ps:Powershell V5(和 C# 代码)) 允许转义本机命令参数

我将尝试回答的问题

<块引用>

...,看来 PowerShell 正在从命令中去除双引号行参数,即使正确转义.

PS C:Documents and SettingsNick>echo.exe '"你好"'你好PS C:Documents and SettingsNick>echo.exe '"你好"'你好"

请注意,当传递给 PowerShell 时,双引号在那里echo cmdlet,但是当作为参数传递给 echo.exe 时,double除非用反斜杠转义(即使PowerShell 的转义字符是反引号,而不是反斜杠).

这对我来说似乎是一个错误.如果我通过正确的转义字符串到 PowerShell,然后 PowerShell 应该处理任何事情转义可能是必要的,因为它调用了命令.

这里发生了什么?

非 Powershell 背景

事实上,您需要使用反斜杠对引号进行转义 没有 与 powershell 相关,但与 CommandLineToArgvW 被所有人使用的函数msvcrt 和 C# 程序从 Windows 进程通过的单字符串命令行构建 argv 数组.

详细信息在 每个人都以错误的方式引用命令行参数,这基本上归结为这个函数在历史上具有非常单调的转义规则:

<块引用>
  • 2n 个反斜杠后跟一个引号产生 n 个反斜杠后跟开始/结束引号.这不会成为解析的一部分参数,但会切换在引号中"模式.
  • (2n) + 1 个反斜杠后跟一个引号再次产生 n 个反斜杠后跟一个引号文字 (").这不会切换引号内"模式.
  • n 个反斜杠后面没有引号只会产生 n 个反斜杠.

导致描述的通用转义函数(这里逻辑的简短引用):

<块引用>

CommandLine.push_back (L'"');for (auto It = Argument.begin () ; ; ++It) {无符号数字反斜杠 = 0;while (It != Argument.end () && *It == L'\') {++它;++NumberBackslashes;}if (It == Argument.end()) {//转义所有反斜杠,但让终止//我们在下面添加的双引号被解释//作为元字符.CommandLine.append (NumberBackslashes * 2, L'\');休息;} else if (*It == L'"') {//转义所有反斜杠和以下内容//双引号.CommandLine.append (NumberBackslashes * 2 + 1, L'\');CommandLine.push_back (*It);} 别的 {//反斜杠在这里并不特殊.CommandLine.append (NumberBackslashes, L'\');CommandLine.push_back (*It);}}CommandLine.push_back (L'"');

Powershell 细节

现在,直到 Powershell 5(包括 Win10/1909 上的 PoSh 5.1.18362.145)PoSh 基本上对这些规则了如指掌,也不应该争论,因为这些规则并不是真正通用的,因为任何理论上,您调用的可执行文件可以使用其他方式来解释传递的命令行.

这导致我们 -

Powershell 引用规则

PoSh 所做的不过是尝试弄清楚您作为参数传递给本机命令的字符串s是否需要被引用,因为它们包含空格.

PoSh - - 对您提供的命令进行更多解析,因为它必须解析变量并且知道关于多个参数.

所以,给定一个命令

$firs = 'whaddyaknow'$secnd = '它可能有空格'$third = '它也可能有引号"和其他"奇怪的 \ 东西'EchoArgs.exe $first $secnd $third

Powershell 必须就如何为 Win32 CreateProcess(或者更确切地说是 C# Process.Start)创建 单个 字符串 CommandLine 采取立场) 调用它最终将不得不做.

Powershell 采用的方法是 奇怪并得到了 在 PoSh V7 中更复杂 ,据我所知,它必须处理 powershell 如何处理未加引号的字符串中的不平衡引号.长话短说是这样的:

Powershell 将自动引用(包含在 <"> 中)单个参数字符串,如果它包含空格 空格不与奇数个(未转义的)双引号.

PoSh V5 的特定引用规则使得不可能将特定类别的字符串作为单个参数传递给子进程.

PoSh V7 修复了这个问题,因此只要所有引号都被 " 转义——无论如何它们都需要通过 CommandLineToArgvW 来获得它们——我们可以将任意字符串从 PoSh 传递给使用 CommandLineToArgvW 的子可执行文件.

以下是从 PoSh github 存储库中提取的 C# 代码规则,用于我们的工具类:

PoSh 引用规则 V5

 public static bool NeedQuotesPoshV5(string arg){//bool needQuotes = false;intquoteCount = 0;for (int i = 0; i < arg.Length; i++){如果 (arg[i] == '"'){报价计数 += 1;}否则 if (char.IsWhiteSpace(arg[i]) && (quoteCount % 2 == 0)){//needQuotes = true;返回真;}}返回假;}

PoSh 引用规则 V7

 内部静态 bool NeedQuotesPoshV7(string arg){bool 以下反斜杠 = false;//bool needQuotes = false;intquoteCount = 0;for (int i = 0; i < arg.Length; i++){if (arg[i] == '"' && !followingBackslash){报价计数 += 1;}否则 if (char.IsWhiteSpace(arg[i]) && (quoteCount % 2 == 0)){//needQuotes = true;返回真;}以下反斜杠 = arg[i] == '\';}//返回需要报价;返回假;}

哦,是的,还有 #L2referr="noreferr"他们还添加了一个半成品的尝试,以正确转义 V7 中引用字符串的 和 :

<块引用>

if (NeedQuotes(arg)){_arguments.Append('"');//需要转义所有尾随反斜杠,以便本机命令正确接收它//根据 http://www.daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESDOC_arguments.Append(arg);for (int i = arg.Length - 1; i >= 0 && arg[i] == '\'; i--){_arguments.Append('\');}_arguments.Append('"');

Powershell 情况

EchoArgs 的输入 |输出 V5 (powershell.exe) |输出 V7 (pwsh.exe)====================================================================================EchoArgs.exe 'abc def' |Arg 0 是<abc def>|Arg 0 是<abc def>------------------------------|-----------------------------|---------------------------EchoArgs.exe '"nospace"' |Arg 0 是<无空间">|Arg 0 是<无空间">------------------------------|-----------------------------|---------------------------EchoArgs.exe '""nospace""' |Arg 0 是<无空间">|Arg 0 是<无空间">------------------------------|-----------------------------|---------------------------EchoArgs.exe 'a"bc def' | Arg 0 是 <a"bc>|Arg 0 是 <a"bc def>|Arg 1 是 <def>|------------------------------|-----------------------------|---------------------------...

出于时间原因,我将在这里截取更多示例.无论如何,他们不应该在答案中添加过多内容.

Powershell 解决方案

要将任意字符串从 Powershell 传递给使用 CommandLineToArgvW 的本机命令,我们必须:

  • 正确转义源参数中的所有引号和反斜杠
    • 这意味着识别 V7 对反斜杠的特殊字符串结尾处理.(这部分在下面的代码中没有实现.)
  • and 确定 powershell 是否会自动引用我们的转义字符串,如果不会自动引用它,请自己引用它.
    • 确保我们自己引用的字符串不会被powershell自动引用:这就是破坏V5的原因.

Powershell V5 源代码,用于正确转义任何本机命令的所有参数

我已经把完整的代码放在 Gist 上,因为它太长了,无法包含在这里:ConvertTo-ArgvQuoteForPoSh.ps:Powershell V5(和 C# 代码)到允许转义本机命令参数

  • 请注意,此代码已尽力而为,但对于某些在有效负载和 V5 中带有引号的字符串,您只需在传递的参数中添加前导空格即可.(逻辑细节见代码).

Recently I have been having some trouble using GnuWin32 from PowerShell whenever double quotes are involved.

Upon further investigation, it appears PowerShell is stripping double quotes from command line arguments, even when properly escaped.

PS C:Documents and SettingsNick> echo '"hello"'
"hello"
PS C:Documents and SettingsNick> echo.exe '"hello"'
hello
PS C:Documents and SettingsNick> echo.exe '"hello"'
"hello"

Notice that the double quotes are there when passed to PowerShell's echo cmdlet, but when passed as an argument to echo.exe, the double quotes are stripped unless escaped with a backslash (even though PowerShell's escape character is a backtick, not a backslash).

This seems like a bug to me. If I am passing the correct escaped strings to PowerShell, then PowerShell should take care of whatever escaping may be necessary for however it invokes the command.

What is going on here?

For now, the fix is to escape command line arguments in accordance with these rules (which seem to be used (indirectly) by the CreateProcess API call which PowerShell uses to invoke .exe files):

  • To pass a double quote, escape with a backslash: " -> "
  • To pass a one or more backslashes followed by a double quote, escape each backslash with another backslash and escape the quote: \\" -> \"
  • If not followed by a double quote, no escaping is necessary for backslashes: \ -> \

Note that further escaping of double quotes may be necessary to escape the double quotes in the Windows API escaped string to PowerShell.

Here are some examples, with echo.exe from GnuWin32:

PS C:Documents and SettingsNick> echo.exe "`""
"
PS C:Documents and SettingsNick> echo.exe "\\`""
\"
PS C:Documents and SettingsNick> echo.exe "\"
\

I imagine that this can quickly become hell if you need to pass a complicated command line parameter. Of course, none of this documented in the CreateProcess() or PowerShell documentation.

Also note that this is not necessary to pass arguments with double quotes to .NET functions or PowerShell cmdlets. For that, you need only escape your double quotes to PowerShell.

Edit: As Martin pointed out in his excellent answer, this is documented in the CommandLineToArgv() function (which the CRT uses to parse the command line arguments) documentation.

解决方案

TL;DR

If you just want a solution for Powershell 5, see:

ConvertTo-ArgvQuoteForPoSh.ps: Powershell V5 (and C# Code) to allow escaping native command arguments

The Question I will try to answer

..., it appears PowerShell is stripping double quotes from command line arguments, even when properly escaped.

PS C:Documents and SettingsNick> echo.exe '"hello"'
hello 
PS C:Documents and SettingsNick> echo.exe '"hello"' 
"hello"

Notice that the double quotes are there when passed to PowerShell's echo cmdlet, but when passed as an argument to echo.exe, the double quotes are stripped unless escaped with a backslash (even though PowerShell's escape character is a backtick, not a backslash).

This seems like a bug to me. If I am passing the correct escaped strings to PowerShell, then PowerShell should take care of whatever escaping may be necessary for however it invokes the command.

What is going on here?

The Non-Powershell Background

The fact that you need to escape the quotes with backslashes has nothing to to with powershell, but with the CommandLineToArgvW function that is used by all msvcrt and C# programs to build the argv array from the single-string command line that the Windows process gets passed.

The details are explained at Everyone quotes command line arguments the wrong way and it basically boils down to the fact that this function historically has very uninutitive escaping rules:

  • 2n backslashes followed by a quotation mark produce n backslashes followed by begin/end quote. This does not become part of the parsed argument, but toggles the "in quotes" mode.
  • (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark literal ("). This does not toggle the "in quotes" mode.
  • n backslashes not followed by a quotation mark simply produce n backslashes.

leading to the described generic escaping function (shortquote of the logic here):

CommandLine.push_back (L'"');

for (auto It = Argument.begin () ; ; ++It) {
      unsigned NumberBackslashes = 0;

      while (It != Argument.end () && *It == L'\') {
              ++It;
              ++NumberBackslashes;
      }

      if (It == Argument.end ()) {
              // Escape all backslashes, but let the terminating
              // double quotation mark we add below be interpreted
              // as a metacharacter.
              CommandLine.append (NumberBackslashes * 2, L'\');
              break;
      } else if (*It == L'"') {
              // Escape all backslashes and the following
              // double quotation mark.
              CommandLine.append (NumberBackslashes * 2 + 1, L'\');
              CommandLine.push_back (*It);
      } else {
              // Backslashes aren't special here.
              CommandLine.append (NumberBackslashes, L'\');
              CommandLine.push_back (*It);
      }
}

CommandLine.push_back (L'"');

The Powershell specifics

Now, up to Powershell 5 (including PoSh 5.1.18362.145 on Win10/1909) PoSh knows basically diddly about these rules, nor should it arguably, because these rules are not really general, because any executable you call could, in theory, use some other means to interpret the passed command line.

Which leads us to -

The Powershell Quoting Rules

What PoSh does do however is try to figure out whether the strings you pass it as arguments to the native commands need to be quoted because they contain whitespace.

PoSh - in contrast to cmd.exe - does a lot more parsing on the command you hand it, since it has to resolve variables and knows about multiple arguments.

So, given a command like

$firs  = 'whaddyaknow'
$secnd = 'it may have spaces'
$third = 'it may also have "quotes" and other " weird \ stuff'
EchoArgs.exe $firs $secnd $third

Powershell has to take a stance on how to create the single string CommandLine for the Win32 CreateProcess (or rather the C# Process.Start) call it will evetually have to do.

The approach Powershell takes is weird and got more complicated in PoSh V7 , and as far as I can follow, it's got to do how powershell treats unbalanced quotes in unquoted string. The long stories short is this:

Powershell will auto-quote (enclose in <">) a single argument string, if it contains spaces and the spaces don't mix with an uneven number of (unsescaped) double quotes.

The specific quoting rules of PoSh V5 make it impossible to pass a certain category of string as single argument to a child process.

PoSh V7 fixed this, so that as long as all quotes are " escaped -- which they need to be anyway to get them through CommandLineToArgvW -- we can pass any aribtrary string from PoSh to a child executable that uses CommandLineToArgvW.

Here's the rules as C# code as extracted from the PoSh github repo for a tool class of ours:

PoSh Quoting Rules V5

    public static bool NeedQuotesPoshV5(string arg)
    {
        // bool needQuotes = false;
        int quoteCount = 0;
        for (int i = 0; i < arg.Length; i++)
        {
            if (arg[i] == '"')
            {
                quoteCount += 1;
            }
            else if (char.IsWhiteSpace(arg[i]) && (quoteCount % 2 == 0))
            {
                // needQuotes = true;
                return true;
            }
        }
        return false;
    }

PoSh Quoting Rules V7

    internal static bool NeedQuotesPoshV7(string arg)
    {
        bool followingBackslash = false;
        // bool needQuotes = false;
        int quoteCount = 0;
        for (int i = 0; i < arg.Length; i++)
        {
            if (arg[i] == '"' && !followingBackslash)
            {
                quoteCount += 1;
            }
            else if (char.IsWhiteSpace(arg[i]) && (quoteCount % 2 == 0))
            {
                // needQuotes = true;
                return true;
            }

            followingBackslash = arg[i] == '\';
        }
        // return needQuotes;
        return false;
    }

Oh yeah, and they also added in a half baked attempt to correctly escape the and of the quoted string in V7:

if (NeedQuotes(arg))
{
      _arguments.Append('"');
      // need to escape all trailing backslashes so the native command receives it correctly
      // according to http://www.daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESDOC
      _arguments.Append(arg);
      for (int i = arg.Length - 1; i >= 0 && arg[i] == '\'; i--)
      {
              _arguments.Append('\');
      }

      _arguments.Append('"');

The Powershell Situation

Input to EchoArgs             | Output V5 (powershell.exe)  | Output V7 (pwsh.exe)
===================================================================================
EchoArgs.exe 'abc def'        | Arg 0 is <abc def>          | Arg 0 is <abc def>
------------------------------|-----------------------------|---------------------------
EchoArgs.exe '"nospace"'    | Arg 0 is <"nospace">        | Arg 0 is <"nospace">
------------------------------|-----------------------------|---------------------------
EchoArgs.exe '""nospace""'  | Arg 0 is <"nospace">        | Arg 0 is <"nospace">
------------------------------|-----------------------------|---------------------------
EchoArgs.exe 'a"bc def'      | Arg 0 is <a"bc>             | Arg 0 is <a"bc def>
                              | Arg 1 is <def>              |
------------------------------|-----------------------------|---------------------------
   ...

I'm snipping further examples here for time reasons. They shouldn't add overmuch to the answer anyways.

The Powershell Solution

To pass arbitrary Strings from Powershell to a native command using CommandLineToArgvW, we have to:

  • properly escape all quotes and Backslashes in the source argument
    • This means recognizing the special string-end handling for backslashes that V7 has. (This part is not implemented in the code below.)
  • and determine whether powershell will auto-quote our escaped string and if it won't auto-quote it, quote it ourselves.
    • and make sure that the string we quoted ourselves then doesn't get auto-quoted by powershell: This is what breaks V5.

Powershell V5 Source code for correctly escaping all arguments to any native command

I've put the full code on Gist, as it got too long to include here: ConvertTo-ArgvQuoteForPoSh.ps: Powershell V5 (and C# Code) to allow escaping native command arguments

  • Note that this code tries it's best, but for some strings with quotes in the payload and V5 you simply must add in leading space to the arguments you pass. (See code for logic details).

这篇关于PowerShell 从命令行参数中去除双引号的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆