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

将具有复杂参数的批处理文件命令转换为PowerShell,powershell,syntax,winscp,Powershell,Syntax,Winscp,我在.bat中有以下内容,即(这是有效的): 我尝试将其复制到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:6

我在.bat中有以下内容,即(这是有效的):

我尝试将其复制到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"
"/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""' `
当我运行.bat脚本时,文件将上载

运行.ps1脚本时,我得到的主机密钥与配置的密钥ssh rsa不匹配

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

我检查了日志,显示的只是主机的主机密钥。它没有显示我正在使用的键。我改变了我的主机,并注意到它没有出现在日志中,从而证实了这一点。我比较了.bat和.ps1之间的日志,差别是ps1终止时出现了上面提到的错误

winscp是一个sftp实用程序。

我运行了您的代码(当然是为了使用我自己的服务器而修改的),并获得了以下日志:

. 2017-03-04 15:32:24.387 Command-line: "C:\Program Files (x86)\WinSCP\WinSCP.exe" /ini=nul /log=C:\TEMP\winscplog.txt /command "open sftp://goofy:***@10.61.10.225/ -hostkey=ssh-rsa" 2048 d4:1c:1a:4c:c3:60:d5:05:12:02:d2:d8:d6:ae:6c:5d"" exit
. 2017-03-04 15:32:24.388 Time zone: Current: GMT-6, Standard: GMT-6 (Central Standard Time), DST: GMT-5 (Central Daylight Time), DST Start: 3/12/2017, DST End: 11/5/2017
. 2017-03-04 15:32:24.388 Login time: Saturday, March 4, 2017 3:32:24 PM
. 2017-03-04 15:32:24.388 --------------------------------------------------------------------------
. 2017-03-04 15:32:24.388 Script: Retrospectively logging previous script records:
> 2017-03-04 15:32:24.388 Script: open sftp://goofy:***@10.61.10.225/ -hostkey=ssh-rsa
. 2017-03-04 15:32:24.388 --------------------------------------------------------------------------
请注意,在第一行中,它是如何在
-hostkey=ssh rsa
之后立即插入双引号的,在最后一行中,
-hostkey
参数是如何从我们预期的参数截断的

在将
-hostkey
参数周围的内部双引号加倍后,我能够成功连接,如下所示:

& $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"
"/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""' `

每当我必须从PowerShell调用可执行文件时,我总是将参数作为列表传递,如下所示。我也使用单引号,除非我实际上在替换变量,在这种情况下,我必须使用双引号

 & SomeUtility.exe @('param1','param2',"with$variable")
当参数中有空格时,这会变得有点棘手,因为您可能必须提供引号,以便实用程序能够正确地解析命令

您的示例甚至更复杂,因为WinScp希望
/command
参数的参数用引号括起来,而其中的任何字符串都用双引号括起来。由于WinScp,所有这些都需要保留。我相信以下方法会奏效。为了便于阅读,我将参数分成了多行。我还假设您已经成功地填充了
$winscp
$outfile
$basename
变量

$params = @(
  '/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"'
)

& $winscp $params
注意第五个参数周围的括号;这是由于那里的字符串连接操作造成的。(如果没有括号,每个操作数都会被单独添加到列表中——您可以通过查看
$params.Count
)来确认这一点。此外,请记住,如果必须将日志文件路径更改为带空格的路径,则需要在其周围加上引号。

注意:

  • 是这种情况下的最佳解决方案

  • 此答案通常讨论如何将为
    cmd.exe
    编写的命令行转换为PowerShell


cmd.exe
(批处理文件)命令行转换为PowerShell非常棘手-非常棘手,以至于PSv3伪参数中的 已介绍

它的目的是允许您将
--%
之后的所有内容按原样传递给目标程序,以便控制精确的引用-除了
cmd.exe
-style
%…%
-style环境变量引用仍然由PowerShell扩展

但是,
-->%
有许多严重的限制,下面将讨论这些限制,并且它的用途仅限于Windows,因此应将其视为最后的手段

替代方法是使用PSv3+(使用PSv5+中的
install Module Native
安装,它提供了两个命令,可在内部补偿PowerShell的所有参数传递和
cmd.exe
的参数解析异常

  • 函数
    ie
    ,您可以在调用外部程序(包括
    cmd.exe
    )之前使用它,只允许您使用PowerShell的语法,而不必担心参数传递问题;e、 g:

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


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

  • -%
    只支持一个命令,同一行上未加引号的
    |
    &
    如果存在,则隐式结束
    ;这允许您将此类命令与其他命令进行管道/链接

    • 但是,使用
      不支持无条件地在同一行上放置另一个命令;
      是逐字传递的

    • -->%
      读取(最多)到行尾,因此使用行连续字符将命令分散到多行。不受支持。[2]

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

    • 不支持将
      %%
      字符转义为
      %%
      (在批处理文件中可以使用的方法)<如果
      引用已定义的环境变量,则代码>%%
      标记将始终展开(如果不是,则标记按原样传递)
  • 除了环境变量引用以外,不能嵌入
    # 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"
    
    try {
        # Load WinSCP .NET assembly
        Add-Type -Path 'WinSCPnet.dll'
    
        # Setup session options
        $sessionOptions = New-Object WinSCP.SessionOptions -Property @{
            Protocol = [WinSCP.Protocol]::Sftp
            HostName = '10.61.10.225'
            UserName = 'goofy'
            Password = 'changeme'
            SshHostKeyFingerprint = 'ssh-rsa 2048 d4:1c:1a:4c:c3:60:d5:05:12:02:d2:d8:d6:ae:6c:5d'
        }
    
        $session = New-Object WinSCP.Session
    
        try {
            # Connect
            $session.Open($sessionOptions)
    
            # Upload files
            $transferOptions = New-Object WinSCP.TransferOptions
            $transferOptions.TransferMode = [WinSCP.TransferMode]::Binary
    
            $transferResult = $session.PutFiles($outfile, "/home/public/somedir/somesubdir/$basename", $false, $transferOptions)
    
            # Throw on any error
            $transferResult.Check()
    
            # Print results
            foreach ($transfer in $transferResult.Transfers) {
                Write-Host ("Upload of {0} succeeded" -f $transfer.FileName)
            }
        } finally {
            # Disconnect, clean up
            $session.Dispose()
        }
    
        exit 0
    } catch [Exception] {
        Write-Host ("Error: {0}" -f $_.Exception.Message)
        exit 1
    }