通过 sFTP & FTP 使用 VBA 上传,记录输出以检测错误

Upload with VBA via sFTP & FTP, log outputs to detect errors

我写了下面的代码来尝试上传到两个不同的服务器,一个通过 ftp,一个通过 sftp。

我想知道是否有更好的方法通过 SFTP 上传,因为我目前的方法如果任何部分失败都不会触发 FTP 错误.

我想有一个变通办法,我想做的是让他们都将输出记录到一个文本文件中,然后我可以手动看到错误是什么,如果我想设置一个简单的阅读记录,检查错误,如果 x 做 y...

        On Error GoTo Err_FTPFile

        ' UPLOAD FIRST FILE VIA FTP

        'Build up the necessary parameters
        sHost = "ftp.server.com"
        sUser = "user@server.com"
        sPass = "password"
        sSrc = """" + Environ("TEMP") + "\" + file + ".txt" + """"
        sDest = "/remote/folder/"

        'Write the FTP commands to a file
        iFNum = FreeFile
        sFTPCmds1 = Environ("TEMP") & "\" & "FTPCmd1.tmp"
        Open sFTPCmds1 For Output As #iFNum
            Print #iFNum, "ftp"
            Print #iFNum, "open " & sHost
            Print #iFNum, sUser
            Print #iFNum, sPass
            Print #iFNum, "cd " & sDest
            Print #iFNum, "put " & sSrc
            Print #iFNum, "disconnect"
            Print #iFNum, "bye"
        Close #iFNum

        'Upload the file
        Shell Environ("WINDIR") & "\System32\ftp.exe -s:" & sFTPCmds1
        Application.Wait (Now + TimeValue("0:00:10"))


        ' UPLOAD SECOND FILE VIA SFTP

        'Build up the necessary parameters
        sFTPDetails = "C:\psftp.exe -b C:\commands.tmp user@ex.server.com -pw password"
        sSrc = """" + Environ("TEMP") + "\" + file + ".txt" + """"
        sDest = "/remote/folder/"

        'Write the FTP commands to a file
        iFNum = FreeFile
        sFTPCmds2 = sFolder & "\" & "commands.tmp"
        Open sFTPCmds2 For Output As #iFNum
            Print #iFNum, "cd " & sDest
            Print #iFNum, "put " & sSrc
            Print #iFNum, "quit"
            Print #iFNum, "bye"
        Close #iFNum

        'Upload the file
        Call Shell(sFTPDetails, vbNormalFocus)
        Application.Wait (Now + TimeValue("0:00:10"))

Exit_FTPFile:
        On Error Resume Next
        Close #iFNum

        'Delete the temp FTP command file
        Kill sFTPCmds1
        Kill sFTPCmds2
        Kill Environ("TEMP") + file + ".txt"

        GoTo ContinuePoint

Err_FTPFile:
        Shell "C:\FailPushBullet.exe"
        MsgBox Err.Number & " - " & Err.Description & " Failed.", vbOKOnly, "Error"
        GoTo ContinuePoint

ContinuePoint:
' Do stuff

理想情况下,我希望底部的 SFTP 能够像上面的 FTP 一样工作和运行。

我尝试了以下 运行s:

    sClient = "C:\psftp.exe"
    sArgs = "user@website.com -pw passexample -b C:\commands.tmp"
    sFull = sClient & " " & sArgs

    sSrc = """" + Environ("TEMP") + "\" + "test" + ".txt" + """"
    sDest = "folder"

    'Write the FTP commands to a text file
    iFNum = FreeFile
    sFTPCmds = "C:\" & "commands.tmp"
    Open sFTPCmds For Output As #iFNum
        Print #iFNum, "cd " & sDest
        Print #iFNum, "put " & sSrc
        Print #iFNum, "quit"
        Print #iFNum, "bye"
    Close #iFNum

    'Upload the file
    Call Shell(sFull, vbNormalFocus)

但是如果我将 sArgs 更改为 sArgs = "user@website.com -pw passexample -b C:\commands.tmp 1> log.txt" 它不会 运行,它只是关闭而不做任何事情。我认为 1> log.txt 应该将输出放入文件

好的..经过反复试验,我终于找到了问题所在,假设给定参数中的所有值都有效,问题是:

  1. username 之前缺少 -l 选项 (line 34)
  2. 缺少 hostname (line 34)
  3. sFolder 未设置或空字符串 (line 40) - 可能会导致问题 - 找不到文件

line 34 上的代码:

 sFTPDetails = "C:\psftp.exe -b C:\commands.tmp user@ex.server.com -pw password"

正确的代码应该是:

 sFTPDetails = "C:\psftp.exe -b C:\commands.tmp -l user@ex.server.com -pw password ftp.server.com"

作为预防措施,您可以使用代码前面所述的 parameter/variable 生成命令。还有一点提示可以通过 直接将 写入 Cells 值来调试您的代码,以便稍后可以在命令提示符

中进行测试
   ' UPLOAD SECOND FILE VIA SFTP

    'Build up the necessary parameters
    sHost = "ftp.server.com"
    sUser = "user@server.com"
    sPass = "password"
    sSrc = """" & Environ("TEMP") & "\" + file & ".txt" & """"
    sDest = "/remote/folder/"
    sFolder = "C:"
    sFTP = "C:\psftp.exe"

    sFTPCmds2 = sFolder & "\" & "commands.tmp"
    sFTPDetails = sFTP & " -b " & sFTPCmds2 & " -1 " & sUser & " -pw " & sPass & " " & sHost

    'FOR DEBUG
    Sheets(1).Cells(1,1) = sFTPDetails

    'Write the FTP commands to a file
    iFNum = FreeFile
    Open sFTPCmds2 For Output As #iFNum
        Print #iFNum, "cd " & sDest
        Print #iFNum, "put " & sSrc
        Print #iFNum, "quit"
        Print #iFNum, "bye"
    Close #iFNum

    'Upload the file
    Call Shell(sFTPDetails, vbNormalFocus)
    Application.Wait (Now + TimeValue("0:00:10"))

如果此代码不是 运行ning 那么参数值可能有问题,您可以从命令提示符手动复制 Sheet1!A1 和 运行 中的粘贴值..不要忘记在调试前注释掉 line 58,这样文件就不需要删除了

是否必须使用 Putty?对于 VBA 内的 FTP 操作,我建议 WinSCP。实际上有一个 .NET assembly/COM 库可用于使用 VBA 轻松实现自动化(甚至比我下面的示例更容易)。也就是说,我的公司环境禁止用户安装 .NET/COM(有充分的理由),所以我编写了自己的代码,简化如下。

要使用下面的内容,请从上面的 link 下载可移植的可执行文件,因为您需要 WinSCP.com 来编写脚本。

本例具有以下特点:

  • FTP 和 SFTP 传输使用相同的协议 (WinSCP)
  • 写一个精简的、机器可读的 XML 日志和全文 记录到文件
  • 使用批处理文件而不是直接 Shell() 执行;这允许 您暂停代码(或注释掉最终的 Kill 语句)以 查看原始命令和批处理文件以便于调试。
  • 显示来自尝试解析 XML 的用户友好的错误消息 日志;保留 XML 和 txt 日志(没有密码数据)供以后使用 评论。

子上传 FTP 和 SFTP 数据:

Public Sub FTPUpload()
'Execute the upload commands

'Create the commands file
Dim ObjFSO As Object
Dim ObjFile As Object
Dim ObjShell As Object
Dim ErrorCode As Integer
Dim sTempDir As String
Dim sType As String
Dim sUser As String
Dim sPass As String
Dim sServer As String
Dim sHostKey As String
Dim file As String 'Using your variable name here.
Dim sLocal As String
Dim sRemote As String
Dim sWinSCP As String

''''''''''''''''''''''''''''''''''''''''''''
'Set FTP Options
''''''''''''''''''''''''''''''''''''''''''''
sTempDir = Environ("TEMP") & "\" 'Log/batch files will be stored here.
sType = "ftp://" 'Or use "sftp://"
sUser = "user"
sPass = "password"
file = "example.txt" 'Assuming you will set this earlier in your code
sServer = "ftp.server.com"
sLocal = Chr(34) & Environ("TEMP") & "\" & file & Chr(34) 'Note that I included the full filename in the file variable; change this as necessary.
sRemote = "/remote/folder"
sWinSCP = "C:\Path\To\WinSCP\WinSCP.com"
'''''''''''''''''''''''''''''''''''''''''''''

'''''''''''''''''''''''''''''''''''''''''''''
'Create batch file and command script
'''''''''''''''''''''''''''''''''''''''''''''
On Error Resume Next
'Delete existing files
Kill sTempDir & "winscp.txt"
Kill sTempDir & "winscp.bat"
Kill sTempDir & "winscplog.xml"
Kill sTempDir & "winscplog.txt"
On Error GoTo 0

Set ObjFSO = CreateObject("Scripting.FileSystemObject")
Set ObjFile = ObjFSO.CreateTextFile(sTempDir & "winscp.txt", True)
ObjFile.writeline "open " & sType & sUser & ":" & sPass & "@" & sServer & "/" & IIf(sHostKey <> vbNullString, " -hostkey=" & Chr(34) & sHostKey & Chr(34), vbNullString)
ObjFile.writeline "put " & sLocal & " " & sRemote
ObjFile.writeline "close"
ObjFile.writeline "exit"
ObjFile.Close
Set ObjFile = Nothing
Set ObjFile = ObjFSO.CreateTextFile(sTempDir & "\winscp.bat", True)
ObjFile.writeline sWinSCP & " /script=" & sTempDir & "winscp.txt /xmllog=" & sTempDir & "winscplog.xml /log=" & sTempDir & "winscplog.txt"
ObjFile.Close
Set ObjFile = Nothing
Set ObjFSO = Nothing
'''''''''''''''''''''''''''''''''''''''''''''

'''''''''''''''''''''''''''''''''''''''''''''
'Execute batch file and process output log
'''''''''''''''''''''''''''''''''''''''''''''
Set ObjShell = VBA.CreateObject("WScript.Shell")
ErrorCode = ObjShell.Run(sTempDir & "\winscp.bat", 0, True)
Set ObjShell = Nothing
If CheckOutput(sTempDir) <> "All FTP operations completed successfully." Then
    MsgBox CheckOutput(sTempDir)
ElseIf ErrorCode > 0 Then
    MsgBox "Excel encountered an error when attempting to run the FTP program. Error code: " & ErrorCode
Else
    MsgBox "All FTP operations completed successfully."
End If
'''''''''''''''''''''''''''''''''''''''''''''

'Done with the FTP transfer. If you want to SFTP transfer immediately thereafter, use the below code
''''''''''''''''''''''''''''''''''''''''''''
'Re-set FTP Options
''''''''''''''''''''''''''''''''''''''''''''
sType = "sftp://"
'sHostKey = "ssh-rsa 1024 9d:d9:e9:69:db:cf:9c:71:8d:cb:da:a5:cf:a7:41:a7" 'Set this if you have a hostkey that should be auto-accepted
'I assume all other options are the same, but you can change user, password, server, etc. here as well. 
'Note that all code from here down is exactly the same as above; only the options have changed.
''''''''''''''''''''''''''''''''''''''''''''

'''''''''''''''''''''''''''''''''''''''''''''
'Create batch file and command script
'''''''''''''''''''''''''''''''''''''''''''''
On Error Resume Next
'Delete existing files
Kill sTempDir & "winscp.txt"
Kill sTempDir & "winscp.bat"
Kill sTempDir & "winscplog.xml"
Kill sTempDir & "winscplog.txt"
On Error GoTo 0

Set ObjFSO = CreateObject("Scripting.FileSystemObject")
Set ObjFile = ObjFSO.CreateTextFile(sTempDir & "winscp.txt", True)
ObjFile.writeline "open " & sType & sUser & ":" & sPass & "@" & sServer & "/" & IIf(sHostKey <> vbNullString, " -hostkey=" & Chr(34) & sHostKey & Chr(34), vbNullString)
ObjFile.writeline "put " & sLocal & " " & sRemote
ObjFile.writeline "close"
ObjFile.writeline "exit"
ObjFile.Close
Set ObjFile = Nothing
Set ObjFile = ObjFSO.CreateTextFile(sTempDir & "\winscp.bat", True)
ObjFile.writeline sWinSCP & " /script=" & sTempDir & "winscp.txt /xmllog=" & sTempDir & "winscplog.xml /log=" & sTempDir & "winscplog.txt"
ObjFile.Close
Set ObjFile = Nothing
Set ObjFSO = Nothing
'''''''''''''''''''''''''''''''''''''''''''''

'''''''''''''''''''''''''''''''''''''''''''''
'Execute batch file and process output log
'''''''''''''''''''''''''''''''''''''''''''''
Set ObjShell = VBA.CreateObject("WScript.Shell")
ErrorCode = ObjShell.Run(sTempDir & "\winscp.bat", 0, True)
Set ObjShell = Nothing
If CheckOutput(sTempDir) <> "All FTP operations completed successfully." Then
    MsgBox CheckOutput(sTempDir)
ElseIf ErrorCode > 0 Then
    MsgBox "Excel encountered an error when attempting to run the FTP program. Error code: " & ErrorCode
Else
    MsgBox "All FTP operations completed successfully."
End If
'''''''''''''''''''''''''''''''''''''''''''''

Exit_Upload:
    On Error Resume Next
    'Clean up (leave log files)
    Kill sTempDir & "winscp.txt" 'Remove scripting commands (note: this file will contain the password)
    Kill sTempDir & "winscp.bat" 'Remove batch file
    'Clear all objects
    Set ObjFSO = Nothing
    Set ObjFile = Nothing
    Set ObjShell = Nothing
    Exit Sub

End Sub

检查输出日志和return用户消息的功能:

Private Function CheckOutput(sLogDir As String) As String

Dim ObjFSO As Object
Dim ObjFile As Object
Dim StrLog As String

'Open log file
Set ObjFSO = CreateObject("Scripting.FileSystemObject")
Set ObjFile = ObjFSO.OpenTextFile(sLogDir & "winscplog.xml")
StrLog = ObjFile.readall
ObjFile.Close
Set ObjFile = Nothing
Set ObjFSO = Nothing

'Check log file for issues
If InStr(1, StrLog, "<message>Authentication failed.</message>") > 0 Then
    CheckOutput = "The supplied password was rejected by the server. Please try again."
ElseIf InStr(1, StrLog, "<failure>") Then
    If InStr(1, StrLog, "<message>Can't get attributes of file") > 0 Then
        CheckOutput = "The requested file does not exist on the FTP server or local folder."
    Else
        CheckOutput = "One or more attempted FTP operations has failed."
    End If
ElseIf InStr(1, StrLog, " <result success=" & Chr(34) & "false" & Chr(34)) > 0 Then
    CheckOutput = "One or more attempted FTP operations has failed."
ElseIf InStr(1, StrLog, " <result success=" & Chr(34) & "true" & Chr(34)) = 0 Then
    CheckOutput = "No FTP operations were performed. This may indicate that no files matching the file mask were found."
End If

'Enter success message or append log file details.
If CheckOutput = vbNullString Then
    CheckOutput = "All FTP operations completed successfully."
Else
    CheckOutput = CheckOutput & vbLf & vbLf & "Please see the below files for additional information. Note that passwords are not logged for security reasons." & _
    vbLf & "Condensed log: " & sLogDir & "winscplog.xml" & vbLf & "Complete log: " & sLogDir & "winscplog.txt"
End If

Exit_CheckOutput:
On Error Resume Next
Set ObjFile = Nothing
Set ObjFSO = Nothing
Exit Function

End Function

注意:我使用的实际代码要详细得多,因为它允许比上传更多的 (S)FTP 操作,使用 FTP class 来利用对象相反,还有更多。我认为这超出了 SO 答案的范围,但如果有帮助,我很高兴 post。