TL;DR
如果您只想要 Powershell 5 的解决方案,请参阅:
ConvertTo-ArgvQuoteForPoSh.ps: Powershell V5 (and C# Code) to allow escaping native command arguments
我将尝试回答的问题
...,看来 PowerShell 正在从命令中删除双引号
行参数,即使正确转义。
PS C:\Documents and Settings\Nick> echo.exe '"hello"'
hello
PS C:\Documents and Settings\Nick> echo.exe '\"hello\"'
"hello"
请注意,双引号在传递给 PowerShell 时
echo cmdlet,但是当作为参数传递给 echo.exe 时,double
除非用反斜杠转义,否则引号将被删除(即使
PowerShell 的转义字符是反引号,而不是反斜杠)。
这对我来说似乎是一个错误。如果我通过正确的转义
字符串到 PowerShell,然后 PowerShell 应该处理任何事情
转义可能是必要的,因为它会调用命令。
这是怎么回事?
非 Powershell 背景
您需要使用反斜杠 \ 转义引号这一事实与 powershell 相比 没有,但使用所有 msvcrt 和 C# 程序用来构建argv 来自 Windows 进程传递的单字符串命令行的数组。
详细信息在Everyone quotes command line arguments the wrong way 进行了解释,它基本上归结为这个函数在历史上具有非常不直观的转义规则:
- 2n 个反斜杠后跟一个引号产生 n 个反斜杠,后跟开始/结束引号。这不会成为解析的一部分
参数,但切换“引号”模式。
- (2n) + 1 个反斜杠后跟一个引号再次产生 n 个反斜杠后跟一个引号文字 (")。这不会
切换“引号”模式。
- n 个不带引号的反斜杠只会产生 n 个反斜杠。
导致所描述的通用转义函数(这里的逻辑短引号):
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'"');
Powershell 细节
现在,直到 Powershell 5(包括 Win10/1909 上的 PoSh 5.1.18362.145)PoSh 基本上对这些规则一无所知,也不应该争论,因为这些规则并不是真正通用的,因为任何理论上,您调用的可执行文件可以使用其他方式来解释传递的命令行。
这导致我们 -
Powershell 引用规则
然而,PoSh 所做 所做的是尝试确定您将其作为参数传递给本机命令的字符串s 是否需要被引用,因为它们包含空格。
PoSh - in contrast to cmd.exe - 对您提交的命令进行更多解析,因为它必须解析变量并了解多个参数。
所以,给定一个类似的命令
$firs = 'whaddyaknow'
$secnd = 'it may have spaces'
$third = 'it may also have "quotes" and other \" weird \\ stuff'
EchoArgs.exe $firs $secnd $third
Powershell 必须就如何为 Win32 CreateProcess(或者更确切地说 C# Process.Start)调用创建 single 字符串命令行采取立场,它最终将不得不这样做。
Powershell 采用的方法是 weird 并得到了 more complicated in PoSh V7 ,据我所知,它必须做 powershell 如何处理不带引号的字符串中的不平衡引号。长话短说是这样的:
Powershell 将自动引用(包含在 "> 中)单个参数
字符串,如果它包含空格 and 空格不与
奇数个(未转义的)双引号。
PoSh V5 的特定引用规则使得不可能将某个类别的字符串作为单个参数传递给子进程。
PoSh V7 修复了这个问题,因此只要所有引号都被\" 转义——无论如何它们都需要通过CommandLineToArgvW——我们可以将任何来自 PoSh 的任意字符串传递给一个子可执行文件使用CommandLineToArgvW。
以下是从 PoSh github 存储库中提取的 C# 代码规则,用于我们的工具类:
PoSh 引用规则 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 引用规则 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;
}
哦,是的,they also added 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('"');
Powershell 情况
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> |
------------------------------|-----------------------------|---------------------------
...
由于时间原因,我在这里截取更多示例。无论如何,他们不应该在答案中添加太多内容。
Powershell 解决方案
要使用 CommandLineToArgvW 将任意字符串从 Powershell 传递到本机命令,我们必须:
- 正确转义源参数中的所有引号和反斜杠
- 这意味着识别 V7 对反斜杠的特殊字符串结束处理。 (这部分在下面的代码中没有实现。)
-
和确定powershell是否会自动引用我们的转义字符串,如果它不会自动引用它,请自己引用它。
-
并且确保我们自己引用的字符串不会被 powershell 自动引用:这是破坏 V5 的原因。
Powershell V5 源代码,用于正确地将所有参数转义到任何本机命令
I've put the full code on Gist,因为太长了,无法在此处包含:ConvertTo-ArgvQuoteForPoSh.ps: Powershell V5 (and C# Code) to allow escaping native command arguments
- 请注意,此代码尽力而为,但对于有效负载和 V5 中带有引号的某些字符串,您只需在传递的参数中添加前导空格即可。 (有关逻辑详细信息,请参见代码)。