【问题标题】:Why does invoke operator (&) and Invoke-Expression produce different results for the same input?为什么调用运算符 (&) 和 Invoke-Expression 对相同的输入产生不同的结果?
【发布时间】:2018-04-25 09:08:54
【问题描述】:

据我了解,调用运算符 (&) 和 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"

有人可以澄清一下吗?

【问题讨论】:

  • 因为您不提供相同的输入 - IEX arg 没有包装在外部脚本块 ({}) 中,所以它更进一步,实际上 执行 i> 解析器完成扩展字符串后的“echo helloworld”
  • @MathiasR.Jessen - 你会展示如何评估 IEX 示例(每个步骤)吗?什么执行“echo helloworld”?

标签: windows powershell


【解决方案1】:

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

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

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

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

在手头的情况下:

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

[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZWNobyAnaGVsbG93b3JsZCc='))

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

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

  • & { "echo 'helloworld'" }

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

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

  • iex "echo 'helloworld'"

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

[1] 可选阅读:调用外部程序时 PowerShell 引用问题

注意:

  • 据我所知,处理对外部程序或从外部程序调用时的引用不是官方文档的一部分(在撰写本文时,about_Parsingabout_Quoting_Rulesabout_Special_Characters 提到它 - 我已经打开 this issue on GitHub 来解决这个问题。

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

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

即使从 PowerShell-internal 的角度来看,您通过将 "" 转义为 "" 将它们正确嵌入到整个 "..." 字符串中,额外的 @987654358 转义需要@ 和\ ,以便将它们传递给外部程序,即使该外部程序是通过powershell.exe 调用的另一个PowerShell 实例。

以这个简化的例子为例:

powershell.exe -command " ""hi"" "  # !! BROKEN
powershell.exe -command ' "hi" '    # !! BROKEN
  • 在 PowerShell 内部," ""hi"" "' "hi" ' 计算为包含文字内容  "hi"  的字符串,执行时会打印出 hi

  • 遗憾的是,PowerShell 将此字符串作为 " "hi" " 传递给 powershell.exe - 请注意 "" 如何变成普通的 " 并且封闭的 单引号 被替换为 double 引号 - 在被新实例解析后有效地导致  hi (因为 " "hi" " 被解析为子字符串 " "hi 和 @ 的 连接 987654374@),因此 PowerShell 最终会尝试执行名为 hi 的(可能不存在的)命令

相比之下,如果您设法将嵌入的 " 传递为 \" (原文如此) - 满足 PowerShell 自己的转义需求之后 - 该命令按预期工作。 因此,如上所述,您需要结合 PowerShell 内部转义和 for-the-CLI 转义,以便传递嵌入的 ",这样:

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

或者,使用 脚本块 代替命令 字符串,这样可以绕过引用问题:

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 不将' 识别为字符串分隔符,因此嵌入"..." 之外的任何元字符再次由cmd.exe 本身解释;例如,powershell.exe -command ' "hi" | Measure-Object '

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

【讨论】:

  • 感谢您的出色回答。我对你写的东西很感兴趣:"Also note that, due to how the command line is parsed, the ""..."" surrounding the core expression in your original commands are effectively ignored..."。你能提供一个文档链接吗?我读过"""..." 中使用时表示单个"。因此,我希望核心表达式被引用,即被视为一个字符串。
  • @user111854 我读到"""..." 中使用时表示单个" 这是真的。但是然后你通过命令行将该字符串传递给PowerShell.exe。命令行解析器将" 视为特殊字符,因此需要将其转义为命令的文字部分。
  • @user111854:PetSerAl 是正确的,但请参阅我的更新以获得更详细的说明。
  • 最后一个问题,为什么使用\"" 而不是\"\"? \ 只是转义了第一个",所以第二个还有特殊含义吗?
  • @user111854:PowerShell 需要 "" 才能识别嵌入在 "..." 中的 ",而 \ - 具有 no 内部对 PowerShell 的特殊含义 - 命令行 构造需要。在 PowerShell 的内部解析之后,powershell.exe 在其命令行上看到的是 \"one ")。相比之下,\"\" 会导致语法错误,因为 PowerShell 规则 " 字符。没有逃脱。
猜你喜欢
  • 1970-01-01
  • 2020-01-10
  • 2021-11-09
  • 2021-09-09
  • 2014-10-18
  • 1970-01-01
  • 2021-11-24
  • 2021-11-17
相关资源
最近更新 更多