为什么调用operator(&)和Invoke-Expression会为同一输入产生不同的结果?

时间:2018-04-25 09:08:54

标签: windows powershell

根据我的理解,invoke运算符(&)和Invoke-Expression cmdlet的行为应该相似。但是,如下所示,情况并非如此:

PS C:\Users\admin> powershell -Command "& {""([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZWNobyAnaGVsb
G93b3JsZCc=')))""}"
echo 'helloworld'

PS C:\Users\admin> powershell -Command "IEX ""([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZWNobyAnaGVs
bG93b3JsZCc=')))"""
helloworld

此处,'ZWNobyAnaGVsbG93b3JsZCc='是Base64编码的字符串"echo helloworld"

有人可以澄清吗?

1 个答案:

答案 0 :(得分:6)

Invoke-Expression(其内置别名为iex)和&, the call operator用于不同目的:

  • Invoke-Expression将给定字符串评估为PowerShell源代码 ,就好像您已执行字符串内容< / em>直接作为命令。

  • &用于调用命令& <nameOrPath> [...])或脚本块({{1} })

    • 两种情况都不涉及将字符串作为源代码进行评估。

在手头的情况下:

您的命令的核心是以下表达式,它返回字符串
& { ... } [...](其内容不包含封闭的"echo 'helloworld'" - 这只是结果字符串作为PowerShell字符串文字的表示形式):

"

另请注意,由于解析命令行的方式,原始命令中核心表达式周围的[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZWNobyAnaGVsbG93b3JsZCc=')) 实际上是忽略,这解释了为什么表达式为执行而不是被视为字符串的内容。 [1]

因此,您的两个命令相当于:

  • ""...""

    • & { "echo 'helloworld'" }执行脚本块中的语句,该语句恰好是字符串,并且字符串本身 - 如果它没有分配给变量或重定向到其他地方 - 按原样只是输出 在这种情况下,该命令实际上与仅执行&一样(包括封闭的"echo 'helloworld'",您可以将其视为
      "),因此echo "echo 'helloworld'"会打印到控制台。

    • 请注意,echo 'helloworld'echo cmdlet的内置别名,很少需要使用显式:命令或表达式的返回值为< em>隐式输出,如果它们没有以某种形式捕获,就像在这种情况下,将字符串本身作为语句执行只是输出字符串。 (例如,您可以在提示时提交Write-Output来尝试此操作。

  • 'hi'

    • 这使得iex "echo 'helloworld'"iex)将字符串的内容评估为源代码,因此执行Invoke-Expression,将echo 'helloworld'打印到控制台。< / LI>

[1]可选阅读:PowerShell在调用外部程序时引用困境

注意:

  • 据我所知,处理关于外部程序或从外部程序调用的引用不是官方文档的一部分(截至撰写本文时,about_Parsing和{{ 3}}或about_Quoting_Rules提到它 - 我已经打开about_Special_Characters来解决这个问题。

  • 现有处理存在缺陷,但在不破坏向后兼容性的情况下无法修复。

  • 从PowerShell调用时,最好的方法是使用脚本块,绕过引用问题 - 见下文。

即使您通过从PowerShell- 内部角度将整个helloworld字符串中的"转义为""来正确嵌入"..." 需要" \的额外转义才能将其传递到外部程序 ,即使该外部程序是另一个通过powershell.exe调用PowerShell的实例。

采用这个简化的例子:

powershell.exe -command " ""hi"" "  # !! BROKEN
powershell.exe -command ' "hi" '    # !! BROKEN
  • PowerShell内部," ""hi"" "' "hi" '评估为包含文字内容 "hi" 的字符串,执行后会打印hi

  • 令人遗憾的是,PowerShell将此字符串作为powershell.exe传递给" "hi" " - 请注意""如何变为普通"以及封闭的引号被替换为 double 引号 - 在新实例解析后有效地导致 hi (因为" "hi" "被解析为连接子串" "hi" "),因此PowerShell最终会尝试执行一个名为hi命令命令。< / p>

相比之下,如果您设法将嵌入式"作为\"(原文如此) - 满足PowerShell自身的转义需求之后传递 - 该命令可以正常工作如预期。 因此,如上所述,您需要将组合 PowerShell内部转义与for-for-CLI转义相结合,以便传递嵌入式" ,以便:< / p>

  • 在整体"..."内,每个嵌入式"必须转发为\""(原文如此)或\`"(原文如此)
  • 整体'...'内,\"可以按原样使用。
powershell.exe -command " \""hi\"" " # OK
powershell.exe -command " \`"hi\`" " # OK
powershell.exe -command ' \"hi\" '   # OK

或者,使用脚本块而不是命令 string ,这会绕过引用的麻烦:

powershell.exe -command { "hi" } # OK, but only works when calling from PS

请注意,脚本块技术仅在从PowerShell 调用时才有效,而不是从cmd.exe调用。

cmd.exe有自己的引用要求: 值得注意的是,cmd.exe仅支持""嵌入双引号(不是`");因此,在上述解决方案中,只有 powershell.exe -command " \""hi\"" "来自cmd.exe(批处理文件),无需额外转义。

然而,\""的缺点是\""...\""之间的内部空白运行会折叠到每个单独的空间。为避免这种情况,请使用\"...\",但cmd.exe会将\"个实例之间的子字符串视为未加引号,如果该子字符串包含元字符,则会导致命令中断例如|&;例如,powershell.exe -command " \"a|b\" ";修复您必须单独^ - 逃避以下字符:& | < > ^

powershell.exe -command ' "hi" '同样很脆弱,因为cmd.exe不会将'识别为字符串分隔符,因此嵌入式"..."之外的所有元字符都会被{{ 1}}本身;例如,cmd.exe

最后,仅使用powershell.exe -command ' "hi" | Measure-Object '中的""来嵌入cmd.exe ,有时有效,但不能可靠;例如,"打印powershell.exe -command " 'Nat ""King"" Cole' "(缺少结束Nat "King Cole) 这似乎已在PowerShell Core 中修复。