PowerShell 7.3.0 破坏命令调用

JFF*_*IGK 12 powershell escaping winscp quoting powershell-7.3

我在 Powershell 脚本中使用 WinSCP。它突然停止工作了。一段时间后,我发现问题是由更新版本的 PowerShell 出现的:

减少代码:

& winscp `
    /log `
    /command `
        'echo Connecting...' `
        "open sftp://kjhgk:jkgh@example.com/ -hostkey=`"`"ssh-ed25519 includes spaces`"`"" 
Run Code Online (Sandbox Code Playgroud)

使用 v7.2.7 时出现错误消息

主机“example.com”不存在。

使用 v7.3.0 时出现错误消息

命令“open”的参数太多。

正如您所看到的,在 v7.3.0 中,WinSCP 根据 PS 版本接收不同的输入。我发现差异与主机密钥中的空格有关。如果省略它们,v7.3.0 会输出相同的错误。

PowerShell 的哪些更改导致了此问题?如何修复它?(我如何调试此类问题?我尝试了一些转义,但无论版本如何,字符串看起来都一样,没有明显的重大更改可以负责)

mkl*_*nt0 23

PowerShell(核心)版本 7.3.0在如何将带有嵌入字符(和空字符串参数)的参数[1]传递给外部程序方面引入了重大更改",例如winscp[2]

虽然此更改主要是有益的,因为它修复了自 v1 以来从根本上被破坏的行为此答案讨论了旧的、破坏的行为),但它总是破坏了基于破坏行为构建的现有解决方法,除了调用批处理文件和WSH CLI (wscript.execscript.exe) 及其关联的脚本文件(具有文件扩展名,例如.vbs.js)。

要使现有解决方法继续有效,请将$PSNativeCommandArgumentPassing首选项变量(临时)设置为'Legacy'

# Note: Enclosing the call in & { ... } makes it execute in a *child scope*
#       limiting the change to $PSNativeCommandArgumentPassing to that scope.
& {
  $PSNativeCommandArgumentPassing = 'Legacy'
  & winscp `
    /log `
    /command `
        'echo Connecting...' `
        "open sftp://kjhgk:jkgh@example.com/ -hostkey=`"`"ssh-ed25519 includes spaces`"`"" 
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,因为仅在其进程命令行上winscp.exe接受(即,嵌入转义为),而不是固定行为现在采用的最广泛使用的形式(嵌入转义为),具体来说,将继续需要解决方法
"open sftp://kjhgk:jkgh@example.com/ -hostkey=""ssh-ed25519 includes spaces""""""
"open sftp://kjhgk:jkgh@example.com/ -hostkey=\"ssh-ed25519 includes spaces\"""\"winscp.exe

如果您不想依靠必须修改的$PSNativeCommandArgumentPassing解决方法,以下是在v7.2- 和 v7.3+中有效的解决方法

  • 使用--%停止解析标记,然而,它带有陷阱和严重的限制,特别是无法(直接)在其后面的参数中使用 PowerShell变量或子表达式 -有关详细信息,请参阅此答案;但是,如果您将其用作构建的数组--%的一部分并首先分配给变量,然后通过splatting传递,则可以绕过这些限制:

    # Note: Must be single-line; note the --% and the 
    #       unescaped use of "" in the argument that follows it.
    #       Only "..." quoting must be used after --% 
    #       and the only variables that can be used are cmd-style 
    #       *environment variables* such as %OS%.
    winscp /log /command 'echo Connecting...' --% "open sftp://kjhgk:jkgh@example.com/ -hostkey=""ssh-ed25519 includes spaces""" 
    
    # Superior alternative, using splatting:
    $argList = '/log', '/command', 'echo Connecting...', 
               '--%', "open sftp://kjhgk:jkgh@example.com/ -hostkey=""ssh-ed25519 includes spaces"""
    winscp @argList
    
    Run Code Online (Sandbox Code Playgroud)
  • 或者,通过以下方式致电cmd /c

    # Note: Pass-through command must be single-line,
    #       Only "..." quoting supported, 
    #       and the embedded command must obey cmd.exe's syntax rules.
    cmd /c @"
      winscp /log /command "echo Connecting..." "open sftp://kjhgk:jkgh@example.com/ -hostkey=""ssh-ed25519 includes spaces"""
    "@
    
    Run Code Online (Sandbox Code Playgroud)
    • 注意:您并不严格需要使用此处字符串@"<newline>...<newline>"@@'<newline>...<newline>'@),但它有助于提高可读性并简化嵌入式引用的使用。

这两种解决方法都允许您直接按引用传递参数,但不幸的是还需要在一行上制定整个(传递)命令- 除非 if--%与 splatting 结合使用。


背景资料:

$PSNativeCommandArgumentPassingWindows 上的v7.3+ 默认值'Windows'

  • 遗憾的是,它保留了调用批处理文件和 WSH CLI(wscript.exe和)及其关联脚本文件(带有诸如和 之cscript.exe类的文件扩展名)的.vbs.js旧的、损坏的行为

    • 虽然仅对于这些程序,这允许现有的解决方法继续发挥作用,但未来仅需要在 v7.3+ 中运行的代码将继续受到这些基于破坏行为的模糊解决方法的需求的负担。

      • 另一种方案(未实现)是为这些程序构建适应性,并在 PowerShell 中构建一些与程序无关的适应性,因此在绝大多数情况下,将来甚至不需要解决方法:请参阅GitHub 问题 #15143
    • 还有一些麻烦的迹象表明,这个异常列表将被逐个追加,这几乎肯定会导致给定的 PowerShell 版本混淆哪些程序需要解决方法,哪些不需要。

  • 值得称赞的是,对于所有其他程序, PowerShell 对参数进行编码 - 当需要在幕后重建命令行时 - 如下所示"

    • 它对遵循C++ 命令行解析规则(由 C/C++/.NET 应用程序使用)/WinAPI 函数的解析规则的程序的参数 CommandLineToArgv进行编码,这是解析进程命令行的最广泛观察的约定

    • 简而言之,这意味着嵌入在参数中的嵌入 "字符 (被目标程序视为其逐字部分)被转义为,其本身需要转义(as ),如果它在 a 之前但意味着被解释逐字逐句\"\\\"

    • 请注意,如果您将$PSNativeCommandArgumentPassing值设置为'Standard'(这是类 Unix 平台上的默认值,此模式修复了所有问题并使 v7.3+ 代码永远不需要解决方法),则此行为适用于所有外部程序,即上述异常不再存在申请)。

有关v7.3 重大更改的影响的摘要,请参阅GitHub 上的此评论

如果您有/需要编写跨版本、跨版本的 PowerShell 代码:该Native模块Install-Module Native;由我编写)有一个ie函数(缩写:Invoke Executable),它是一个提供无解决方法的跨版本( v3+),在绝大多数情况下跨平台和跨版本行为- 只需预先添加ie 到外部程序调用即可。
警告:在当前的具体情况下,它不起作用因为它不知道winscp.exe需要""- 转义。


[1]有关详细信息和解决方法,请参阅此答案。

[2]曾短暂考虑过在更高版本中恢复该更改并选择加入新行为,但最终决定反对 - 请参阅GitHub 问题 #18694