为什么对于同一输入,调用运算符(&)和Invoke-Expression会产生不同的结果?

Shu*_*eng 4 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
Run Code Online (Sandbox Code Playgroud)

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

有人可以澄清吗?

mkl*_*nt0 9

Invoke-Expression(其内置别名为iex)和&(调用运算符)具有不同的用途:

  • Invoke-Expression将给定的字符串评估为PowerShell源代码,就像您已直接将命令的字符串内容执行一样。

    • 因此,它与evalin 类似bash,因此仅与完全在调用者控制下的输入或调用者信任的输入一起使用

    • 通常有更好的解决方案可用,因此通常应避免使用 Invoke-Expression

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

    • 两种情况都没有涉及将字符串评估为源代码。

在当前情况下:

命令的核心是下面的表达式,该表达式返回字符串
"echo 'helloworld'"(其内容不包括括号"-这只是将结果字符串表示为PowerShell字符串文字):

[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZWNobyAnaGVsbG93b3JsZCc='))
Run Code Online (Sandbox Code Playgroud)

另请注意,由于命令行的解析方式,""...""原始命令中核心表达式周围的内容将被有效忽略,这说明了为什么执行该表达式而不是将其视为字符串的内容。[1]

因此,您的两个命令总计为:

  • & { "echo 'helloworld'" }

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

    • 请注意,这echoWrite-Outputcmdlet 的内置别名,很少需要显式使用:如果未以某种形式捕获命令或表达式的返回值,则它们将隐式输出,例如,在这种情况下,将自己执行一个字符串作为一条语句仅输出字符串。(例如,您可以通过仅'hi'在提示时提交来尝试此操作)。

  • iex "echo 'helloworld'"

    • 这使iexInvoke-Expression)将该字符串的内容作为源代码进行评估,然后执行echo 'helloworld'该代码,并将其输出helloworld到控制台。

[1]可选阅读:调用外部程序时,PowerShell引用错误

注意:

  • 据我所知,处理外部程序或从外部程序进行调用的报价都不是官方文档的一部分(截至撰写本文时,about_Parsingabout_Quoting_Rulesabout_Special_Characters都未提及它-我已经解决了此问题在GitHub上解决此问题)。

  • 现有处理中存在缺陷,但是如果不破坏向后兼容性就无法修复它们。

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

即使从PowerShell 内部透视图将"它们转义到""整个"..."字符串中也可以正确嵌入,但是仍需要with进行附加转义,以将它们传递给外部程序,即使该外部程序是PowerShell的另一个实例,也称为via 。"\powershell.exe

举这个简化的例子:

powershell.exe -command " ""hi"" "  # !! BROKEN
powershell.exe -command ' "hi" '    # !! BROKEN
Run Code Online (Sandbox Code Playgroud)
  • 在PowerShell中进行内部处理," ""hi"" "' "hi" '求值为包含文字内容的字符串 "hi" ,该字符串在执行时将显示print hi

  • 令人遗憾的是,PowerShell的传递这个字符串powershell.exe作为" "hi" "-记怎么""变成平原"和封闭的单引号被替换为引号-这有效地导致 hi 由新实例解析后(因为" "hi" "被解析为串联的子串" "hi" ")因此,PowerShell最终尝试执行名为的命令(可能不存在)hi

相反,如果你能设法通过嵌入作为"\"(原文如此) - 指挥的作品如预期-会议PowerShell的自己逃脱的需求。因此,如前所述,您需要 PowerShell内部的转义与for-the-CLI的转义结合起来,以便传递一个Embedded",以便:

  • 在总体内部"...",每个嵌入"必须以\""(sic)或\`"(sic)进行转义
  • 在整体内部'...'\"可以原样使用。
powershell.exe -command " \""hi\"" " # OK
powershell.exe -command " \`"hi\`" " # OK
powershell.exe -command ' \"hi\" '   # OK
Run Code Online (Sandbox Code Playgroud)

或者,使用脚本块而不是命令字符串,从而绕过引人头痛的问题:

powershell.exe -command { "hi" } # OK, but only works when calling from PS
Run Code Online (Sandbox Code Playgroud)

请注意,脚本块技术仅在从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它不能识别'为字符串定界符,因此嵌入之外的任何元字符"..."都将再次由其cmd.exe自身解释;例如,powershell.exe -command ' "hi" | Measure-Object '

最后,仅使用""from cmd.exe进行嵌入" 有时可行,但并不可靠;例如,powershell.exe -command " 'Nat ""King"" Cole' "打印Nat "King Cole"缺少结束符)。
这似乎已在PowerShell Core中修复。