2016-12-19 94 views
2

我想運行一個ssh命令,並將輸出捕獲到VBA中的變量(使用OpenSSH)。我能得到這個命令行正常工作:如何在Shell中運行SSH以將值返回到VBA變量中?

SSH用戶@ IP蟒蛇C:\ TEMP \ Remote.py

的結果是值的列表返回到命令行窗口。我想將其讀入VBA變量。

我發現thisthisthis。前兩個似乎沒有正確發送或接收命令,因爲代碼被掛在oShell.Exec(...)上。 shell看起來像正在執行,但它只是被掛起。如果我關閉掛起的命令窗口,VBA中的結果是空白的。

Dim sCmd As String 
sCmd = "ssh [email protected] python C:\Temp\Remote.py" 

Dim oShell As Object 
Set oShell = CreateObject("WScript.Shell") 
Dim oExec As Object 
Dim oOutput As Object 
Set oExec = oShell.Exec(sCmd) 
Set oOutput = oExec.StdOut 

Dim s As String 
Dim sLine As String 

While Not oOutput.AtEndOfStream 
    sLine = oOutput.ReadLine 
    If sLine <> "" Then s = s & sLine & vbCrLf 
Wend 

第三,似乎部分工作(使用'retVal = Shell("ssh [email protected] python C:\Temp\Remote.py", vbNormalFocus)'),但我不能得到的價值了。我可以看到命令窗口打開,值返回,但我得到了一些retVal中的整數。

任何幫助?

+0

OK,感謝殼牌清晰度()。應該不需要輸入,因爲沒有密碼,並且命令在命令提示符下按原樣運行。 – lukehawk

+0

它卡在oShell.Exec()上。如果我使用VBA調試器進行檢查,它將永遠不會從後續的命令窗口返回。如果我關閉了被困窗口,它會返回一個空的StdOut。 – lukehawk

+0

它是一個控制檯應用程序,它在我的PATH變量中,意思是來自命令提示符,在我的驅動器上的任何文件夾上,我可以正確運行:ssh user @ ip python C:\ Temp \ Remote.py,它可以工作精細。 – lukehawk

回答

1

WScript.Shell對象的.Exec() method是被設計爲(a)中運行控制檯命令/應用,和(b)經由WshScriptExec實例返回的屬性捕獲其輸出的方法。

在這一點上是一個謎,爲什麼你ssh基於命令不起作用 - 代碼一般確實與控制檯命令工作。

這就是說,有另外一個原因使用.Exec(),從GUI應用程序運行時,諸如VBA的宿主應用程序,控制檯窗口總是執行期間做出可見,它可以在視覺上具有破壞性。

以下備選方案(可選地)允許運行隱藏命令(沒有可見的窗口),但要注意既不直接支持捕獲輸出從被調用的命令:

  • VBA自己的Shell() function,但是,它總是異步執行,因此確定命令何時結束很麻煩。

  • WScriptShell對象的.Run() method,其任選地允許等待該命令到結束。

同時獲得無形的執行輸出捕捉,您可以:

  • 結合WScriptShell對象的.Run()方法
  • 用命令的輸出發送到臨時文件
  • 並且在命令完成時讀取臨時文件記憶。
Dim cmd as String 
Dim exitCode As Integer 
Dim tempFile As String 
Dim capturedOutput As String 

' Construct the name of a temporary file to capture the command's stdout output in. 
tempFile = Environ$("TEMP") & "\" & CreateObject("Scripting.FileSystemObject").GetTempName() 

' Define the command to invoke. 
cmd = "ssh [email protected] python C:\Temp\Remote.py" 

' Use .Run() to invoke the command. 
' - In order to use output redirection, the command must be prefixed with 'cmd /c '. 
' - Setting the last argument to `True` makes the invocation synchronous. 
' - Replace vbNormalFocus with vbHidden to run the command *invisibly*. 
exitCode = CreateObject("WScript.Shell").Run("cmd /c " & cmd & " >" & tempFile, vbNormalFocus, True) 
If exitCode = 0 Then ' Command succeeded. 

    ' Read the output file, then delete the temporary file. 
    capturedOutput = CreateObject("Scripting.FileSystemObject").OpenTextFile(tempFile).ReadAll() 
    CreateObject("Scripting.FileSystemObject").DeleteFile(tempFile) 

    ' Display the output. 
    MsgBox "Captured Output:" & vbNewLine & capturedOutput 

Else ' Command indicated failure. 

    MsgBox "An unexpected error occurred.", vbExclamation 

End If 

下面是一個替代基於Shell() - 由this implementation啓發。

正如你看到的,讓Shell()同步需要更多的努力和需要使用的Windows API:

Private Declare Function OpenProcess Lib "kernel32.dll" (ByVal dwDesiredAccessas As Long, ByVal bInheritHandle As Long, ByVal dwProcId As Long) As Long 
Private Declare Function CloseHandle Lib "kernel32.dll" (ByVal hObject As Long) As Long 
Private Declare Function WaitForSingleObject Lib "kernel32.dll" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long 
Private Declare Function GetExitCodeProcess Lib "kernel32.dll" (ByVal hProcess As Long, ByRef lpExitCodeOut As Long) As Integer 

Sub Main() 

    Dim cmd As String 
    Dim taskId As Integer 
    Dim exitCode As Integer 
    Dim tempFile As String 
    Dim capturedOutput As String 

    ' Construct the name of a temporary file to capture the command's stdout output in. 
    tempFile = Environ$("TEMP") & "\" & CreateObject("Scripting.FileSystemObject").GetTempName() 

    ' Define the command to run. 
    cmd = "ssh [email protected] python C:\Temp\Remote.py" 

    ' Use the SyncShell() helper function defined below to invoke the command 
    ' synchronously and to obtain its exit code. 
    ' - In order to use output redirection, the command must be prefixed with 'cmd /c '. 
    ' - Add a 3rd argument with a timeout value in seconds if you don't want to wait 
    ' indefinitely for the process to complete. 
    ' - Replace vbNormalFocus with vbHidden to run the command *invisibly*. 
    exitCode = SyncShell("cmd /c " & cmd & " >" & tempFile, vbNormalFocus) 
    If exitCode = 0 Then ' Command succeeded. 

     ' Read the output file and delete the temporary file. 
     capturedOutput = CreateObject("Scripting.FileSystemObject").OpenTextFile(tempFile).ReadAll() 
     CreateObject("Scripting.FileSystemObject").DeleteFile (tempFile) 

     ' Display the output. 
     MsgBox "Captured Output:" & vbNewLine & capturedOutput 

    Else ' Command indicated failure. 

     MsgBox "An unexpected error occurred.", vbExclamation 

    End If 

End Sub 

' Helper function 
Private Function SyncShell(ByVal cmd As String, Optional ByVal windowStyle As VbAppWinStyle = vbMinimizedFocus, Optional ByVal timeoutInSecs As Double = -1) As Long 

    Dim pid As Long ' PID (Process ID) as returned by Shell(). 
    Dim h As Long ' Process handle 
    Dim sts As Long ' WinAPI return value 
    Dim timeoutMs As Long ' WINAPI timeout value 
    Dim exitCode As Long 

    ' Invoke the command (invariably asynchronously) and store the PID returned. 
    ' Note that the invocation may fail. 
    pid = Shell(cmd, windowStyle) 

    ' Translate the PIP into a process *handle* with the SYNCHRONIZE and PROCESS_QUERY_LIMITED_INFORMATION access rights, 
    ' so we can wait for the process to terminate and query its exit code. 
    h = OpenProcess(&H100000 Or &H1000, 0, pid) ' &H100000 == SYNCHRONIZE, &H1000 == PROCESS_QUERY_LIMITED_INFORMATION 
    If h = 0 Then Err.Raise vbObjectError + 1024, , "Failed to obtain process handle for process with ID " & pid & "." 

    ' Now wait for the process to terminate. 
    If timeoutInSecs = -1 Then 
     timeoutMs = &HFFFF ' INFINITE 
    Else 
     timeoutMs = timeoutInSecs * 1000 
    End If 
    sts = WaitForSingleObject(h, timeoutMs) 
    If sts <> 0 Then Err.Raise vbObjectError + 1025, , "Waiting for process with ID " & pid & " to terminate timed out, or an unexpected error occurred." 

    ' Obtain the process's exit code. 
    sts = GetExitCodeProcess(h, exitCode) ' Return value is a BOOL: 1 for true, 0 for false 
    If sts <> 1 Then Err.Raise vbObjectError + 1026, , "Failed to obtain exit code for process ID " & pid & "." 

    CloseHandle h 

    ' Return the exit code. 
    SyncShell = exitCode 

End Function 
+0

該死的,兒子。好答案。很快就會嘗試。真誠的,謝謝你的努力。這很棒。 – lukehawk

+0

@lukehawk:我的榮幸;我在回答過程中總是自己學習一些東西。 – mklement0

0

我想你應該嘗試在命令開始時使用「命令規範」。你的機器這可以在VBA獲得具有VBA.Environ$("comspec")這我在我的機器上返回C:\WINDOWS\system32\cmd.exe

所以儘量

sCmd = VBA.Environ$("comspec") & " /C " & " ssh [email protected] python C:\Temp\Remote.py" 

cmd switches

我沒有安裝Python的命令規範需要在某些情況下,交換機所以不能測試。

+0

謝謝!我會試試這個。 (但是,cmd開關的鏈接已損壞,你可以再次提供它嗎?)但我不認爲我需要命令的cmd部分。如果ssh在我的%path%中,它應該將ssh識別爲一個命令,對嗎? – lukehawk

+0

我剛試過這個,並沒有改變行爲。 – lukehawk

+0

好的,谷歌搜索在這裏產生一些代碼https://www.example-code.com/vb/ssh_exec.asp。 vb6的語法與vba相同。或者這個http://www.mrexcel.com/forum/excel-questions/816494-visual-basic-applications-excel-2010-how-initiate-ssh-connection-list-ip-address-send-credential-write-結果連接憑證2 cell.html或這個http://stackoverflow.com/questions/18135551/how-to-call-python-script-on-excel-vba –

相關問題