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



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

"%winscp%" /ini=nul ^
/log=C:TEMPwinscplog.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"

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

& $winscp "/ini=nul" `
"/log=C:TEMPwinscplog.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"

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

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

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

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

WinSCP 是一个 SFTP 实用程序。

注意:

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

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


cmd.exe(批处理文件)命令行转换为PowerShell很棘手 - 非常棘手,以至于在PSv3伪参数中--%停止解析令牌,被引入

其目的是允许您将--%之后的所有内容传递到目标程序,以便控件精确引用-除了PowerShellcmd.exe样式%...%样式的环境变量引用由 PowerShell扩展[1]。

但是,--%具有许多严重的限制(下面将讨论),并且其有用性仅限于Windows,因此应将其视为最后的手段;

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

  • 函数ie,你附加到对外部程序的任何调用(包括cmd.exe)之前,允许你只使用PowerShell的syntax_,而不必担心参数传递问题;例如:

    # Without `ie`, this command would malfunction.
    'a"b' | ie findstr 'a"b'
    
  • 函数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`""
    

--%的局限性和陷阱

注意:如果将--%拼接相结合,则可以克服其中的许多限制,但是这需要更多的工作,并且需要将参数表述为带引号的字符串- 请参阅此答案。

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

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

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

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

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

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

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

    • 对于标准输出,可以通过附加| Set-Content file.txt来解决此问题,但没有直接的 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:TEMPwinscplog.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"

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

[2] 即使是 PowerShell 自己的行延续字符`也被视为直通文字。 当您使用--%时,甚至不涉及cmd.exe(除非您显式使用cmd --% /c ...),因此它的行继续符^也不能使用。

Martin Prikryl(WinSCP的作者)也提供了一个.Net程序集,如果你想切换到PowerShell,这可能是一个更好的选择。

使用命令行中的参数更新的文档示例中的示例:

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
}

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

& SomeUtility.exe @('param1','param2',"with$variable")

当参数中有空格时,这会变得有点棘手,因为您可能必须提供引号,以便实用程序可以正确解析命令。

您的示例更加棘手,因为 WinScp 希望/command参数的参数用引号引起来,而里面的任何字符串都用引号引起来。 所有这些都需要保留,因为WinScp。 我相信以下方法会起作用。 为了便于阅读,我将参数分成多行。 我还假设您已成功填充$winscp$outfile$basename变量。

$params = @(
'/ini=nul',
'/log=C:TEMPwinscplog.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来确认这一点。 另请记住,如果必须将日志文件路径更改为带空格的内容,则需要在日志文件路径两边使用引号。

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

. 2017-03-04 15:32:24.387 Command-line: "C:Program Files (x86)WinSCPWinSCP.exe" /ini=nul /log=C:TEMPwinscplog.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参数周围的内部双引号加倍后,我能够成功连接,如下所示:

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

最新更新