I wrote the following code to try and upload to two different servers one via ftp and one via sftp.
I would like to know if there is a better way to upload via SFTP because the current method as I have it doesn't trigger the FTP error if it fails on any part.
I guess a work around and something I would like to have is for both of them to log the output to a text file and then from that I can see what the error was manually and if I want setup a simple read log, check error, if x do 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
I ideally would like the SFTP one at the bottom to work and function exactly like the FTP one from above.
I tried the following and this runs:
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)
But if I change the sArgs to sArgs = "user#website.com -pw passexample -b C:\commands.tmp 1> log.txt" it doesn't run, it just closes without doing anything. I thought 1> log.txt is supposed to put the output into a file
Is it a requirement to use Putty? I recommend WinSCP for FTP operations within VBA. There is actually a .NET assembly/COM library available for easy automation with VBA (even easier than my below example). That said, my corporate environment prohibits users from installing .NET/COM (for good reason), so I wrote my own code, simplified below.
To use the below, download the Portable executables from the above link as you will need WinSCP.com for the scripting.
This example has the following features:
Uses the same protocol (WinSCP) for both FTP and SFTP transfers
Writes a condensed, machine-readable XML log as well as a full text
log to files
Uses batch files rather than direct Shell() executions; this allows
you to pause the code (or comment out the final Kill statements) to
view original command and batch files for easy debugging.
Displays a user-friendly error message from attempts to parse the XML
log; retains the XML and txt log (with no password data) for later
review.
Sub to upload the FTP and SFTP data:
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
Function to check the output log and return a message for the user:
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
Note: the actual code that I use is significantly more detailed, as it allows for more (S)FTP operations than uploading, uses an FTP class to utilize objects instead, and more. I think that goes a bit beyond a SO answer, but I am happy to post if it would be helpful.
OK.. after some trial and error finally I found the problem, with assumption that all value in given parameters is valid the problem are:
missing the -l option before username (line 34)
missing the hostname (line 34)
sFolder not set or empty string (line 40) - may cause a problem - file not found
Code on line 34:
sFTPDetails = "C:\psftp.exe -b C:\commands.tmp user#ex.server.com -pw password"
The right code should be:
sFTPDetails = "C:\psftp.exe -b C:\commands.tmp -l user#ex.server.com -pw password ftp.server.com"
As prevention may be you can generate your command using parameter/variable that described earlier in the code. Also there is a little hint to debug your code by write it directly to Cells value so later can be tested in command prompt
' 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"))
If this code not running then may be something wrong with parameter values, to see that you can just copy paste value in Sheet1!A1 and run it manually from command prompt..and don't forget to comment out line 58 before debugging so the file needed not deleted
Related
If I copy the contents of the bat file and enter directly onto the command line in cmd.exe, the WinSCP SFTP script transfers the file from local directory to SFTP site. When I run it from this VBA code, I do not get any log files created. The winscp.bat and winscp.txt files are created. The ErrorCode that is returned is a "1".
Any help would be appreciated.
Public Sub SFTPUpload()
'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 sBat 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 SFTP Options
''''''''''''''''''''''''''''''''''''''''''''
sTempDir = DataPath & "Log\" 'Log/batch files will be stored here.
sType = "sftp://"
sUser = "User"
sPass = "Name"
file = DataPath & FileName
sServer = "sftp.dfsco.int"
sHostKey = "ssh-rsa 1024 9d:d9:e9:69:db:cf:9c:71:8d:cb:da:a5:cf:a7:41:a7"
sLocal = file
sWinSCP = "C:\Program Files (x86)\WinSCP\WinSCP.com"
If SFTP_USE_TEST_SITE Then
sRemote = "/Allianz/DFS/CSR/Test/OneToMany/"
Else
sRemote = "/Allianz/DFS/CSR/Prod/OneToMany/"
End If
'''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''
'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 /log=" &
sTempDir & "winscplog.txt /xmllog=" & sTempDir & "winscplog.xml /ini=nul
/loglevel=2"
ObjFile.Close
Set ObjFile = Nothing
Set ObjFSO = Nothing
'''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''
'Execute batch file and process output log
'''''''''''''''''''''''''''''''''''''''''''''
Set ObjShell = VBA.CreateObject("WScript.Shell")
sBat = sTempDir & "winscp.bat"
ErrorCode = ObjShell.Run(sBat, 1, True)
Set ObjShell = Nothing
If CheckOutput(sTempDir) <> "All FTP operations completed successfully."
Then 'MsgBox CheckOutput(sTempDir)
If ErrorCode > 0 Then
MsgBox "Excel encountered an error when attempting to run the FTP
program. Error code: " & ErrorCode
Else
MsgBox "One2Many file has been sent to ADMS."
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
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
Sub UpdateStatus(ByVal StepNum As String, ByVal Desc As String)
Dim MyStr As String
MyStr = Now & ": " & StepNum & " - " & Desc
frmEDLBilling.txtStatus = frmEDLBilling.txtStatus & MyStr & vbCrLf
oWS_Log.Cells(Log_Row, 1) = MyStr
Log_Row = Log_Row + 1
DoEvents
End Sub
Output for Winscp.bat & winscp.txt is as follows:
Winscp.bat contains the following:
"C:\Program Files (x86)\WinSCP\WinSCP.com" /script="D:Data\Test\Log\winscp.txt" /log="D:\Data\Test\Log\winscplog.txt" /xmllog="D:\Data\Test\Log\winscplog.xml" /ini=nul
Winscp.txt contains the following:
open sftp://userID:pwd#sftp.dfsco.int/ - hostkey="actual hostkey"
put D:\Data\Test\AZL_ONE2MANY_PDF_MASTER.txt /Allianz/Test/OneToMany/
close
exit
You are missing double quotes around the path to winscp.com (as it contains a space in the Program Files (x86)).
I cannot imagine that the .bat file works, if you run it manually, despite your claim that it does.
The code should be:
ObjFile.writeline Chr(34) & sWinSCP & Chr(34) & " /script=" ...
AND the answer is: the double quotes around Program files (x86), added them before and after the file paths for /script and /log AND, the last issue I had was that somehow I changed "Program Files (x86)" to "Program Files (x86}". The code is finally working. Thanks for the tips on how to ask a question and also for pointing me in the right direction on double quotes.
I am having trouble getting the code below to work (from an answer here Using FTP in VBA). I'm in an FTP client from WinSCP and I'm not 100% sure how to use the code. When I run the macro, I get the successful MsgBox "Sent" popup, and I see that a file named whatever I put as FTP_BATCH_FILE_NAME gets created holding the following info:
"open " & FTP_ADDRESS
FTP_USERID
FTP_PASSWORD
"mput " & sWorkingDirectory & sFileToSend
quit
The person who posted the code noted that -
"While stepping through code, you could type the following command in the immediate window and then copy/paste the results into a Command Window:"
? Shell "ftp -i -w:20480 -s:" & sWorkingDirectory & FTP_BATCH_FILE_NAME
But when I do step through the code, and after the yellow highlight passes this line in Excel, I enter this line in and I get an error that '?', sWorkingDirectory, and FTP_BATCH_FILE_NAME is not recognized as an internal or external command.
I thought that after typing it at Cmd line I'd be able to get the contents of FTP_BATCH_FILE_NAME into sFileToSend, but nothing is put into sFileToSend.
Option Explicit
Const FTP_ADDRESS = myHostAddr
Const FTP_USERID = myUsername
Const FTP_PASSWORD = myPassword
Sub Macro1()
If Not SendFtpFile_F() Then
MsgBox "Could not (send/get?) ftp file"
Else
MsgBox "Sent"
End If
End Sub
Function SendFtpFile_F() As Boolean
Dim rc As Integer
Dim iFreeFile As Integer
Dim sFTPUserID As String
Dim sFTPPassWord As String '
Dim sWorkingDirectory As String
Dim sFileToSend As String
Const FTP_BATCH_FILE_NAME = "a file in FTP I want to get.ftp"
Const INCREASED_BUFFER_SIZE = 20480
SendFtpFile_F = False
sWorkingDirectory = "C:\Users\...\Documents\"
sFileToSend = "test.txt" 'is this the file I want to put it into?
On Error GoTo FtpNECAFile_EH
'Kill FTP process file if it exists
If Dir(sWorkingDirectory & FTP_BATCH_FILE_NAME) <> "" Then
Kill sWorkingDirectory & FTP_BATCH_FILE_NAME
End If
'Create FTP process file
iFreeFile = FreeFile
Open sWorkingDirectory & FTP_BATCH_FILE_NAME For Output As #iFreeFile
Print #iFreeFile, "open " & FTP_ADDRESS
Print #iFreeFile, FTP_USERID
Print #iFreeFile, FTP_PASSWORD
Print #iFreeFile, "mput " & sWorkingDirectory & sFileToSend
Print #iFreeFile, "quit"
Close #iFreeFile
'Shell command the FTP file to the server
Shell "ftp -i -w:20480 -s:" & sWorkingDirectory & FTP_BATCH_FILE_NAME
SendFtpFile_F = True
GoTo FtpNECAFile_EX
FtpNECAFile_EH:
MsgBox "Err", Err.Name
FtpNECAFile_EX:
Exit Function
End Function
I would like to use Excel VBA to run a CMD command.
The basic cmd command is:
"C:\Program Files\example program.exe" -w C:\Program files\outputfile.file "dir1" "dir2" "dir n+1"
The first part is the location of the program that will merge the files together.
The second part is the file location of the outputted merged files.
And the "dir1".... is the files that will be merged together.
I have code that lists the files to be merged but struggling to get the CMD code to get it so it does what I want as mentioned above. I have tried the following:
Sub RunCMD()
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
Dim waitOnReturn As Boolean: waitOnReturn = True
Dim windowStyle As Integer: windowStyle = 1
Dim locationofprogram as string
'dir of program that will do the merge
Dim outputfolder as string
'dir of where the merged file will go
Dim listoffiles as string
'list of files to be merged
wsh.Run "cmd.exe /S /C locationofprogram & " " & "-w" & " " & outputfolder & " " & listoffiles, windowStyle, waitOnReturn
End Sub
Thanks for the help!
You can always create a .bat file, run the file, then delete it.
MyFile = "C:\Bat-File\Bat-File.bat"
fnum = FreeFile()
Open MyFile For Output As #fnum
Print #fnum, "C:\Program Files\example program.exe -w C:\Program files\outputfile.file dir1 dir2 dir n+1"
Close #fnum
Shell MyFile, vbNormalFocus
' wait 10 seconds to let bat file finnish
newHour = Hour(Now())
newMinute = Minute(Now())
newSecond = Second(Now()) + 10
waitTime = TimeSerial(newHour, newMinute, newSecond)
Application.Wait waitTime
' delete bat-file
kill MyFile
I can't test that example program runs without the " around the dir´s so you have to see what happens.
But if it does not work you need to double enclose the " or use & CHR(34) &
If in doubt, send the text to the Immediate Window to see if it still matches the syntax using:
Debug.Print "cmd.exe /S /C " & locationofprogram & " " & "-w" & " " & outputfolder & " " & listoffiles
If that works, use:
wsh.Run "cmd.exe /S /C " & locationofprogram & " " & "-w" & " " & outputfolder & " " & listoffiles, windowStyle, waitOnReturn
In reference to the below code, what I am looking to do is rather than process an entire folder I would like only to process the emails that I selected.
Otherwise it works perfectly.
Jeff
Requires the following references:
Visual Basic for Applications
Microsoft Outlook 14.0 Object Library
OLE Automation
Microsoft Office 14.0 Object Library
Microsoft Shell Controls and Automation
Public Sub SaveOLFolderAttachments()
' Ask the user to select a file system folder for saving the attachments
Dim oShell As Object
Set oShell = CreateObject("Shell.Application")
Dim fsSaveFolder As Object
Set fsSaveFolder = oShell.BrowseForFolder(0, "Please Select a Save Folder:", 1)
If fsSaveFolder Is Nothing Then Exit Sub
' Note: BrowseForFolder doesn't add a trailing slash
' Ask the user to select an Outlook folder to process
Dim olPurgeFolder As Outlook.MAPIFolder
Set olPurgeFolder = Outlook.GetNamespace("MAPI").PickFolder
If olPurgeFolder Is Nothing Then Exit Sub
' Iteration variables
Dim msg As Outlook.MailItem
Dim att As Outlook.Attachment
Dim sSavePathFS As String
Dim sDelAtts As String
For Each msg In olPurgeFolder.Items
sDelAtts = ""
' We check each msg for attachments as opposed to using .Restrict("[Attachment] > 0")
' on our olPurgeFolder.Items collection. The collection returned by the Restrict method
' will be dynamically updated each time we remove an attachment. Each update will
' reindex the collection. As a result, it does not provide a reliable means for iteration.
' This is why the For Each style loops will not work. ~~
If msg.Attachments.Count > 0 Then
' This While loop is controlled via the .Delete method which
' will decrement msg.Attachments.Count by one each time. ~~
While msg.Attachments.Count > 0
' Save the attachment to the file system
sSavePathFS = fsSaveFolder.Self.path & "\" & msg.Attachments(1).filename
msg.Attachments(1).SaveAsFile sSavePathFS
' Build up a string to denote the file system save path(s)
' Format the string according to the msg.BodyFormat.
If msg.BodyFormat <> olFormatHTML Then
sDelAtts = sDelAtts & vbCrLf & "<file://" & sSavePathFS & ">"
Else
sDelAtts = sDelAtts & "<br>" & "<a href='file://" & sSavePathFS & "'>" & sSavePathFS & "</a>"
End If
' Delete the current attachment. We use a "1" here instead of an "i"
' because the .Delete method will shrink the size of the msg.Attachments
' collection for us. Use some well placed Debug.Print statements to see
' the behavior. ~~
msg.Attachments(1).Delete
Wend
' Modify the body of the msg to show the file system location of
' the deleted attachments.
If msg.BodyFormat <> olFormatHTML Then
msg.Body = msg.Body & vbCrLf & vbCrLf & "Attachments Deleted: " & Date & " " & Time & vbCrLf & vbCrLf & "Saved To: " & vbCrLf & sDelAtts
Else
msg.HTMLBody = msg.HTMLBody & "<p></p><p>" & "Attachments Deleted: " & Date & " " & Time & vbCrLf & vbCrLf & "Saved To: " & vbCrLf & sDelAtts & "</p>"
End If
' Save the edits to the msg. If you forget this line, the attachments will not be deleted. ~~
msg.Save
End If
Next
End Sub
Drop the pickfolder code and select the items first.
' http://msdn.microsoft.com/en-us/library/office/aa171941(v=office.11).aspx
Untested code
Sub SaveOLSelectedItemsAttachments()
Dim myOlExp As Outlook.Explorer
Dim myOlSel As Outlook.Selection
Dim x As Integer
Set myOlExp = Application.ActiveExplorer
Set myOlSel = myOlExp.Selection
' Ask the user to select a file system folder for saving the attachments
Dim oShell As Object
Set oShell = CreateObject("Shell.Application")
Dim fsSaveFolder As Object
Set fsSaveFolder = oShell.BrowseForFolder(0, "Please Select a Save Folder:", 1)
If fsSaveFolder Is Nothing Then Exit Sub
' Note: BrowseForFolder doesn't add a trailing slash
For x = 1 To myOlSel.Count
' Iteration variables
Dim att As Outlook.Attachment
Dim sSavePathFS As String
Dim sDelAtts As String
Dim msg as mailitem
Set msg = myOlSel.Item(x)
sDelAtts = ""
' We check the item for attachments.
' The collection returned by the Restrict method
' will be dynamically updated each time we remove an attachment. Each update will
' reindex the collection. As a result, it does not provide a reliable means for iteration.
' This is why the For Each style loops will not work. ~~
If msg.Attachments.Count > 0 Then
' This While loop is controlled via the .Delete method which
' will decrement msg.Attachments.Count by one each time. ~~
While msg.Attachments.Count > 0
' Save the attachment to the file system
sSavePathFS = fsSaveFolder.Self.path & "\" & msg.Attachments(1).filename
msg.Attachments(1).SaveAsFile sSavePathFS
' Build up a string to denote the file system save path(s)
' Format the string according to the msg.BodyFormat.
If msg.BodyFormat <> olFormatHTML Then
sDelAtts = sDelAtts & vbCrLf & "<file://" & sSavePathFS & ">"
Else
sDelAtts = sDelAtts & "<br>" & "<a href='file://" & sSavePathFS & "'>" & sSavePathFS & "</a>"
End If
' Delete the current attachment. We use a "1" here instead of an "i"
' because the .Delete method will shrink the size of the msg.Attachments
' collection for us. Use some well placed Debug.Print statements to see
' the behavior. ~~
msg.Attachments(1).Delete
Wend
' Modify the body of the msg to show the file system location of
' the deleted attachments.
If msg.BodyFormat <> olFormatHTML Then
msg.Body = msg.Body & vbCrLf & vbCrLf & "Attachments Deleted: " & Date & " " & Time & vbCrLf & vbCrLf & "Saved To: " & vbCrLf & sDelAtts
Else
msg.HTMLBody = msg.HTMLBody & "<p></p><p>" & "Attachments Deleted: " & Date & " " & Time & vbCrLf & vbCrLf & "Saved To: " & vbCrLf & sDelAtts & "</p>"
End If
' Save the edits to the msg. If you forget this line, the attachments will not be deleted. ~~
msg.Save
End If
Next
Next x
End Sub
I want to monitor a drive for file changes, using VBScript. I have the below code. It works fine for InstanceCreationEvent and InstanceDeletionEvent. But InstanceModificationEvent is not happening. From googling I got to know we need to use CIM_DataFile instead of CIM_DirectoryContainsFile to monitor InstanceModificationEvent. I am not sure how to modify the code. Can anyone help.
FYI: One script should monitor all the folders and subfolders in a drive.
PS: Any suggestion to improve the code and performance or other ideas also welcome.
My Code:
Dim arrFolders
Dim strComputer
Dim objWMIService
Dim strFolder
Dim strCommand
Dim i
Dim strQuery
strChangeFile = "MonitorFolder_Log.txt"
strMailIDFile = "MonitorFolder_MailIDs.txt"
'Check if the log file exists, if not ceate a new file and exit the script. Restart the script again.
Set oFSO = CreateObject("Scripting.FileSystemObject")
If not oFSO.FileExists(strChangeFile) then
'WScript.Echo "Change Log File Not Found. Creating new file..."
Set oTxtFile = oFSO.CreateTextFile(strChangeFile)
WScript.Echo strChangeFile & " File Created." & vbCrLf & "Please restart the script." & vbCrLf
WScript.Quit
End If
'Prompt for which drive should be monitored. If not a valid drive, then exit the script.
strDrive = InputBox("Enter the drive to monitor: " & vbCrLf & "E.g.: Input C to monitor C:\ drive.", "Monitor Folder - Oracle", "E")
If strDrive = "" then
WScript.Echo "Not a valid drive. Terminating the script."
WScript.Quit
End If
'Append ":" with the drive name.
strDrive = strDrive & ":"
'Read the mail IDs.
Set objFSOMailID = CreateObject("Scripting.FileSystemObject")
Set oTSMailID = objFSOMailID.OpenTextFile(strMailIDFile)
strMailIDsList = oTSMailID.ReadAll
oTSMailID.close
'WScript.Echo strMailIDsList
'Array to store the existing folder paths that should be monitored.
arrFolders = Array()
i = 0
Set FSO = CreateObject("Scripting.FileSystemObject")
ShowSubfolders FSO.GetFolder(strDrive)
Sub ShowSubFolders(Folder)
For Each Subfolder in Folder.SubFolders
i = i + 1
folderPath = "" & Subfolder.Path & ""
folderPath = Replace(folderPath ,"\","\\\\")
ReDim Preserve arrFolders(i)
arrFolders(i) = folderPath
'Wscript.Echo i & " " & arrFolders(i)
ShowSubFolders Subfolder
Next
End Sub
'Set the first path to be the drive.
arrFolders(0) = strDrive & "\\\\"
'Use WMI query to get the file changes.
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")
'Loop throught the array of folders setting up the monitor for Each
i = 0
For Each strFolder In arrFolders
'Create the event sink
'WScript.Echo "setup for folder: " & strFolder & vbLf
strCommand = "Set EventSink" & i & " = WScript.CreateObject" & "(""WbemScripting.SWbemSink"", ""SINK" & i & "_"")"
ExecuteGlobal strCommand
'Setup Notification
strQuery = "SELECT * " _
& "FROM __InstanceOperationEvent " _
& "WITHIN 1 " _
& "WHERE Targetinstance ISA 'CIM_DirectoryContainsFile'" _
& " AND TargetInstance.GroupComponent = " & "'Win32_Directory.Name=""" & strFolder & """'"
strCommand = "objWMIservice.ExecNotificationQueryAsync EventSink" & i & ", strQuery"
ExecuteGlobal strCommand
'Create the OnObjectReady Sub
strCommand = "Sub SINK" & i & "_OnObjectReady(objObject, " & "objAsyncContext)" & vbLf _
& " 'Wscript.Echo objObject.TargetInstance.PartComponent" & vbLf _
& " SendNotification(objObject)" & vbLf _
& "End Sub"
'WScript.Echo strCommand
ExecuteGlobal strCommand
i = i + 1
Next
'Wait for events.
WScript.Echo "Waiting for events..."
i = 0
While (True)
Wscript.Sleep(1000)
Wend
Function SendNotification(objObject)
strEventType = objObject.Path_.Class
strPartComp = Split(objObject.TargetInstance.PartComponent, "=")
strFileName = Replace(strPartComp(1), "\\", "\")
WScript.Echo strEventType
WScript.Echo strFileName
'Some more code to send mail and logs...
End Function
Monitoring the entire filesystem for file creation is not feasible. It will eat up system resources and might severly affect system operation. Only ever monitor selected folders. The following should work:
Const Interval = 1
Set monitor = CreateMonitor("C:\foo")
Do
Set evt = monitor.NextEvent()
Select Case evt.Path_.Class
Case "__InstanceCreationEvent" : SendNotification evt.TargetInstance
Case "__InstanceModificationEvent" : ...
Case "__InstanceDeletionEvent" : ...
End Select
Loop
Function CreateMonitor(path)
Set wmi = GetObject("winmgmts://./root/cimv2")
Set fso = CreateObject("Scripting.FileSystemObject")
path = Split(fso.GetAbsolutePathName(path), ":")
drv = path(0) & ":"
dir = Replace(path(1), "\", "\\")
If Right(dir, 2) <> "\\" Then dir = dir & "\\"
query = "SELECT * FROM __InstanceOperationEvent" & _
" WITHIN " & Interval & _
" WHERE Targetinstance ISA 'CIM_DataFile'" & _
" AND TargetInstance.Drive='" & drv & "'" & _
" AND TargetInstance.Path='" & dir & "'"
Set CreateMonitor = wmi.ExecNotificationQuery(query)
End Function
Sub SendNotification(tgtInst)
'send notification
End Sub
You should run monitors for different folders as separate processes, because NextEvent() is a blocking operation.