【问题标题】:PowerShell 7.3.0 breaking command invocationPowerShell 7.3.0 中断命令调用
【发布时间】:2022-12-10 04:46:58
【问题描述】:

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

减少代码:

& winscp `
    /log `
    /command `
        'echo Connecting...' `
        "open sftp://kjhgk:jkgh@lkjhlk.com/ -hostkey=`"`"ssh-ed25519 includes spaces`"`"" 

使用 v7.2.7 的错误信息

主机 "lkjhlk.com" 不存在。

使用 v7.3.0 的错误信息

命令“打开”的参数太多。

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

PowerShell 的哪些更改导致了此问题,我该如何解决? (我该如何调试此类问题?我尝试了一些转义,但无论版本如何,字符串看起来都一样,没有明显的破坏性变化可能是负责任的)

【问题讨论】:

  • 重新排除外部程序参数传递问题:如果您使用Chocolatey,则可以从提升的会话中使用choco install echoargs -y来安装echoargs.exe,它显示了 PowerShell 在幕后构建的原始命令行,以及如何(大多数)外部程序将其解析为参数。或者,您可以临时编译一个实用程序:请参阅this answer。在 PowerShell 7.3+ 中,只有先明确(暂时)设置 $PSNativeCommandArgumentPassing = 'Legacy',您才会看到损坏的旧行为。

标签: powershell escaping winscp quoting powershell-7.3


【解决方案1】:

PowerShell (Core) 的 7.3.0 版本引入了一个重大改变关于如何带有嵌入 " 字符的参数被传递给外部程序,比如winscp

虽然这种变化是大多有益的,因为它修复了自 v1 以来根本被破坏的行为this answer 讨论旧的、损坏的行为),它总是打破现有的解决方法建立在破碎的行为之上,除了那些打电话给批处理文件以及 WSH CLI(wscript.execscript.exe)及其关联的脚本文件(文件扩展名为 .vbs.js)。

警告:

  • GitHub issue #18694 暗示7.3.1及以上版本 将做出这个突破性的改变选择参加, 但仅在 Windows 上, 为了向后兼容;那是:
    • 在 Windows 上:
      • 旧的解决方法将继续有效默认.
      • 获得新的、正确的行为需要(暂时)设置
        $PSNativeCommandArgumentPassing = 'Standard'
    • 开启Unix- 类似平台:
      • 新的正确行为 ($PSNativeCommandArgumentPassing = 'Standard') 将保持默认
      • 旧的解决方法需要(暂时)设置$PSNativeCommandArgumentPassing = 'Legacy'才能继续工作,7.3.0 中已经如此

要使现有的解决方法继续有效,请将 $PSNativeCommandArgumentPassing preference variable(暂时)设置为 '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@lkjhlk.com/ -hostkey=`"`"ssh-ed25519 includes spaces`"`"" 
}

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

如果你想靠必须修改$PSNativeCommandArgumentPassing对于解决方法,这里是在其中起作用的解决方法两个都v7.2- 和 v7.3+:

  • 利用--%stop-parsing token,然而,它伴随着陷阱和严重的限制,特别是无法(直接)使用 PowerShell变量或它后面的参数中的子表达式 - 有关详细信息,请参阅this answer

    # 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@lkjhlk.com/ -hostkey=""ssh-ed25519 includes spaces""" 
    
  • 最好通过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@lkjhlk.com/ -hostkey=""ssh-ed25519 includes spaces"""
    "@
    
    • 注意:您并非严格需要使用 here-string@"<newline>...<newline>"@@'<newline>...<newline>'@),但它有助于提高可读性并简化使用嵌入式引号的过程。

两种解决方法都允许您传递参数直接引用,但不幸的是还需要制定整个(传递)命令在一条线上.


背景资料:

Windows 上的 v7.3 默认值 $PSNativeCommandArgumentPassing'Windows'

  • 遗憾地保留旧的、破碎的行为打电话给批处理文件以及 WSH CLI(wscript.execscript.exe)及其关联的脚本文件(文件扩展名为 .vbs.js.

    • 虽然仅对于这些程序,这允许现有的解决方法继续运行,未来只需要在 v7.3+ 中运行的代码将继续因需要这些晦涩的解决方法而负担重重,这建立在破碎的行为之上.

      • 替代方案,这是不是实施,本来是为这些项目建造住宿以及一些与项目无关的住宿进入PowerShell,因此在绝大多数情况下,将来甚至不需要变通方法:请参阅GitHub issue #15143
    • 还有 troublesome signs that this list of exceptions will be appended to, piecemeal,对于给定的 PowerShell 版本,这几乎可以保证混淆哪些程序需要解决方法,哪些不需要。

  • 值得称道的是对于所有其他程序,使 PowerShell 在必要时对参数进行编码 -重建关于"的幕后命令行如下:

    • 对遵循C++ command-line parsing rules(由 C/C++/.NET 应用程序使用)/CommandLineToArgv WinAPI 函数的解析规则的程序的参数进行编码, 这是最广泛遵守的约定用于解析进程的命令行。

    • 简而言之,这意味着嵌入式"字符 嵌入式在争论中,被视为逐字部分它由目标程序,是转义为", 本身需要转义<em>只要</em>(如\)如果它在"之前但要被解释逐字.

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

为一个突破性 v7.3 变化的影响总结,请参阅this comment on GitHub

如果你有/需要编写跨版本、跨版本的PowerShell代码Native moduleInstall-Module Native;由我撰写),有一个iefunction(简称:Invoke Executable),是一个 polyfill,它在绝大多数案例 - 只需将 ie 添加到您的外部程序调用中。
警告: 在手头的具体情况下,它会不是工作,因为它不知道 winscp.exe 需要 ""-转义。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-24
    • 2016-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-17
    相关资源
    最近更新 更多