我在.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""' `