将带有复杂参数的批处理文件命令转换为 PowerShell

Mic*_*ter 4 syntax powershell winscp

我在 .bat 中有以下内容(这有效):

"%winscp%" /ini=nul ^
           /log=C:\TEMP\winscplog.txt ^
           /command "open scp://goofy:changeme@10.61.10.225/ -hostkey=""ssh-rsa 2048 d4:1c:1a:4c:c3:60:d5:05:12:02:d2:d8:d6:ae:6c:5d""" ^
           "put ""%outfile%"" /home/public/somedir/somesubdir/%basename%" ^
           "exit"
Run Code Online (Sandbox Code Playgroud)

我试图将其复制到这样的 powershell 脚本中:

& $winscp "/ini=nul" `
           "/log=C:\TEMP\winscplog.txt" `
           "/command" 'open sftp://goofy:changeme@10.61.10.225/ -hostkey="ssh-rsa 2048 d4:1c:1a:4c:c3:60:d5:05:12:02:d2:d8:d6:ae:6c:5d"' `
           "put `"" + $outfile + "`" /home/public/somedir/somesubdir/" + $basename `
           "exit"
Run Code Online (Sandbox Code Playgroud)

当我运行 .bat 脚本时,文件将上传。

当我运行 .ps1 脚本时,我得到 Host key does not match configured key ssh-rsa

我怀疑我没有在 powershell 中正确格式化命令,并且在 winscp 看到它时主机密钥已经损坏。

我检查了日志,显示的只是来自主机的主机密钥。它没有显示我正在使用的密钥。我通过更改我的主机并注意到它没有出现在日志中来确认这一点。我比较了 .bat 和 .ps1 之间的日志,不同之处在于 ps1 以上述错误终止。

winscp 是一个 sftp 实用程序。

mkl*_*nt0 5

笔记:

  • 在这种情况下,JamesQMurphy 的有用答案是最佳解决方案。

  • 这个答案通常讨论翻译为cmd.exePowerShell编写的命令行


平移cmd.exe(批处理文件)的命令行到PowerShell是棘手-如此棘手,即在PSv3伪参数 --%,该停止解析符号被介绍

它的目的是让你通过后到来的一切--% 作为,就是通过对目标程序,从而控制确切的报价-除非cmd.exe风格%...%样式的环境变量的引用还在扩大通过PowerShell的[1]

然而,--%它有许多严重的限制——下面讨论——它的用处仅限于 Windows,所以它应该被认为是最后的手段

另一种方法是使用PSv3+Native模块Install-Module Native从PSv5+ 中的PowerShell Gallery安装),它提供了两个命令,可以在内部补偿 PowerShell 的所有参数传递和cmd.exe参数解析怪癖

  • Function ie,您可以将其添加到对外部程序的任何调用之前,包括cmd.exe,允许您仅使用PowerShell的 syntax_,而不必担心参数传递问题;例如:

    # Without `ie`, this command would malfunction.
    'a"b' | ie findstr 'a"b'
    
    Run Code Online (Sandbox Code Playgroud)
  • Function ins( Invoke-NativeShell) 函数允许您重用按原样编写的cmd.exe命令行,作为单个字符串传递;与 不同--%,这还允许您通过 PowerShell可扩展字符串( )在命令行中嵌入PowerShell变量和表达式:"..."

    # Simple, verbatim cmd.exe command line.
    ins 'ver & whoami'
    
    # Multi-line, via a here-string.
    ins @'
      dir /x ^
        c:\
     '@
    
    # With up-front string interpolation to embed a PowerShell var.
    $var='c:\windows'; ins "dir /x `"$var`""
    
    Run Code Online (Sandbox Code Playgroud)

限制和陷阱 --%

  • --%必须遵循要调用的外部实用程序的名称/路径(它不能是命令行上的第一个标记),以便实用程序可执行文件(路径)本身,如果通过 [environment] 变量传递,则必须定义和引用使用PowerShell语法。

  • --%仅支持一个命令,该命令不加引号|||&&在同一行(如果存在)隐式结束;这允许您将这样的命令通过管道/链接到/与其他命令。

    • 但是,支持使用;in order 无条件地将另一个命令放在同一行上;将通过逐字传递。;

    • --%读取(最多)到行尾,因此使用行继续符将命令扩展到多行。不支持。[2]

  • 除了%...% 环境变量引用,您不能在命令中嵌入任何其他动态元素;也就是说,您不能使用常规 PowerShell 变量引用或表达式

    • 支持转义%字符%%(您可以在批处理文件中执行的方式);如果引用已定义的环境变量,则标记总是会扩展(如果不是,则标记按原样传递)。%<name>%<name>
  • 除了%...% 环境变量引用,您不能在命令中嵌入任何其他动态元素;也就是说,您不能嵌入常规 PowerShell 变量引用或表达式

  • 不能使用流重定向(例如,>file.txt),因为它们是逐字传递的,作为目标命令的参数

    • 对于stdout输出,您可以通过附加| Set-Content file.txt来解决该问题,但对于stderr输出没有直接的 PowerShell 解决方法。
    • 但是,如果您通过cmd调用命令,则可以让cmd处理 (stderr) 重定向(例如,cmd --% /c nosuch 2>file.txt

应用于您的案例,这意味着:

  • %winscp%必须转换为其 PowerShell 等效项 ,$env:winscp并且后者必须以&, PowerShell 的调用运算符为前缀,这在调用由变量或带引号的字符串指定的外部命令时是必需的。
  • & $env:winscp必须跟在后面--%以确保所有剩余的参数都未经修改地传递(%...%变量引用的扩展除外)。
  • 原始命令的参数列表可以按原样粘贴之后--%,但必须在一行上

因此,在您的情况下,最简单的方法 - 尽管必须使用单行为代价 - 是:

# Invoke the command line with --%
# All arguments after --% are used as-is from the original command.
& $env:winscp --% /ini=nul /log=C:\TEMP\winscplog.txt /command "open scp://goofy:changeme@10.61.10.225/ -hostkey=""ssh-rsa 2048 d4:1c:1a:4c:c3:60:d5:05:12:02:d2:d8:d6:ae:6c:5d""" "put ""%outfile%"" /home/public/somedir/somesubdir/%basename%" "exit"
Run Code Online (Sandbox Code Playgroud)

[1] 请注意,尽管cmd.exe-like 语法--%也适用于 PowerShell Core(macOS、Linux)中的类 Unix平台,但在那里的用途非常有限:与诸如bash那里的本机 shell 不同,--% 仅适用于引号字符串( "..."); 例如,bash --% -c "hello world"工作,但bash --% -c 'hello world'支持-并且不支持通常的 shell 扩展,特别是 globbing - 请参阅此 GitHub 问题

[2] Even `,PowerShell 自己的行继续符,被视为传递文字cmd.exe当您使用时甚至不涉及--%(除非您明确使用cmd --% /c ...),因此它的行继续符^, 也不能使用。