Call .com file with parameters using VBA WScript.Shell - excel

I'm using Excel to upload some files onto an server with WinSCP.
This example works:
Sub FTP_upload()
Dim logfile, ftp_login, file_to_upload, upload_to_folder As String
logfile = "D:\temp\ftp.log"
ftp_login = "ftp://ftp_mydomain:mypassword#mydomain.com/"
file_to_upload = "D:\tmep\myfile.txt"
upload_to_folder = "/myfolder/"
'upload the file
Call Shell("C:\Program Files (x86)\WinSCP\WinSCP.com /log=" & logfile & " /command " & """open """ & ftp_login & " " & """put " & file_to_upload & " " & upload_to_folder & """ " & """exit""")
End Sub
I now want Excel to wait until the shell has closed.
Using the information from Wait for shell command to complete, I put it together this code:
Sub FTP_upload_with_wait()
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
Dim waitOnReturn As Boolean: waitOnReturn = True
Dim windowStyle As Integer: windowStyle = 1
Dim errorCode As Integer
Dim logfile, ftp_login, file_to_upload, upload_to_folder As String
logfile = "D:\temp\ftp.log"
ftp_login = "ftp://ftp_mydomain:mypassword#mydomain.com/"
file_to_upload = "D:\tmep\myfile.txt"
upload_to_folder = "/myfolder/"
execute_string = "C:\Program Files (x86)\WinSCP\WinSCP.com /log=" & logfile & " /command " & """open """ & ftp_login & " " & """put " & file_to_upload & " " & upload_to_folder & """ " & """exit"""
errorCode = wsh.Run(execute_string, windowStyle, waitOnReturn)
End Sub
Unfortunately, this doesn't work. Excel reports:
run-time error '-2147024894 (80070002)'
Automation error
The system cannot find the file specified
When I replace the string this way, it works:
execute_string = "notepad.exe"
It seems that wsh.Run doesn't like the quotation marks.
How can I make this work?

The path to WinSCP contains spaces, so you need to wrap it to double-quotes (which need to be doubled to escape them in VBA string):
execute_string = """C:\Program Files (x86)\WinSCP\WinSCP.com"" ..."
But that's only the first set of quotes that is wrong in your command.
The correct command would be like:
execute_string = """C:\Program Files (x86)\WinSCP\WinSCP.com"" " & _
"/log=" & logfile & " /command " & _
"""open " & ftp_login & """ " & _
"""put " & file_to_upload & " " & upload_to_folder & """ " & _
"""exit"""
Assuming that none of logfile, ftp_login, file_to_upload and upload_to_folder contains spaces, in which case would would need a way more double-quotes.
Read ore about WinSCP command-line syntax
The Call Shell must have some heuristics that adds the quotes around C:\Program Files (x86)\WinSCP\WinSCP.com. Though it's just a pure luck that the rest of the command-line works, the quotes are wrong there too. So even your first code is wrong. It runs the following command:
"C:\Program Files (x86)\WinSCP\WinSCP.com" /log=D:\temp\ftp.log /command "open "ftp://ftp_mydomain:mypassword#mydomain.com/ "put D:\tmep\myfile.txt /myfolder/" "exit"
(Note the misplaced quotes around open)

Related

Compile error: expected list separator or ) excel VBA

I have got this command line in VBA:
Call Shell("cmd.exe /S /k " & ""C:\Program Files\Java\jre1.8.0_171\bin\javaw.exe" -jar " & DPath & " """ & inp1 & """ """ & inp2 & """ """ & sPath & """ """ & FilePath & """", vbNormalFocus)
When I remove the quotations the error appears in VBA:
'C:\Program' is not recognized as an internal or external command,
operable program or batch file.
When I add the quotations the error appears in VBA:
Compile error: expected list separator or )
How can I solve the error?
The quotation is wrong, you must put another pair of quotes around the javaw.exe path, because in VBA, a literal " must be specified as "":
Call Shell("cmd.exe /S /K " & """C:\Program Files\Java\jre1.8.0_171\bin\javaw.exe"" -jar " & DPath & " """ & inp1 & """ """ & inp2 & """ """ & sPath & """ """ & FilePath & """", vbNormalFocus)
But this is still not going to work, because cmd removes the outer-most quotation marks and leaves behind an invalid command line. Therefore, you must provide another pair of (literal) quotation marks around the whole expression behind /K, which may be removed by cmd:
Call Shell("cmd.exe /S /K " & """""C:\Program Files\Java\jre1.8.0_171\bin\javaw.exe"" -jar " & DPath & " """ & inp1 & """ """ & inp2 & """ """ & sPath & """ """ & FilePath & """""", vbNormalFocus)
Finally, let me recommend to enclose the DPath value within (literal) quotation marks as well:
Call Shell("cmd.exe /S /K " & """""C:\Program Files\Java\jre1.8.0_171\bin\javaw.exe"" -jar """ & DPath & """ """ & inp1 & """ """ & inp2 & """ """ & sPath & """ """ & FilePath & """""", vbNormalFocus)
Please learn how to make your life easy. Just because you can represent a " in a VBA string using """ doesn't mean you should except in the most trivial instance.
Here is a native VBA method for making life simpler.
Dim qDPath As String
Dim qinp1 As String
Dim qinp2 As String
Dim qsPath As String
Dim qFilePath As String
Dim qCommand As String
qDPath = MakeQuotedString(Path)
qinp1 = MakeQuotedString(inp1)
qinp2 = MakeQuotedString(inp2)
qsPath = MakeQuotedString(sPath)
qFilePath = MakeQuotedString(FilePath)
qCommand = MakeQuotedString("C:\Program Files\Java\jre1.8.0_171\bin\javaw.exe")
qCommand = MakeQuotedString("cmd.exe /S /k " & qCommand & "-Jar " & qDPath & qinp1 & qinp2 & qsPath & qFilePath)
Call Shell(qCommand, vbNormalFocus)
Whilst the above is much more verbose it at least allows you to use single stepping via F8 to check at each stage that you are constructing the final command string correctly.
As you can see the above is a bit verbose which is why many other programming languages have the ability to embed variable and formatting markers in a string. This can also be done in VBA using a Module to hide the relevant code. Please have a look at the Fmt method in my Layout Module for VBA. Its available in GitHub from here. Its a very simple library module to allow the embedding of variable fields and formatting markers in strings
This will allow you to write
Call Shell("cmd.exe /S /k " & ""C:\Program Files\Java\jre1.8.0_171\bin\javaw.exe" -jar " & DPath & " """ & inp1 & """ """ & inp2 & """ """ & sPath & """ """ & FilePath & """", vbNormalFocus)
In a much more concise way, which is also friendly to the human eye.
Dim ShellCmd as String
ShellCmd = Fmt("cmd.exe /S /k {dq}{dq}C:\Program Files\Java\jre1.8.0_171\bin\javaw.exe{dq} -jar {dq}{0}{dq} {dq}{1}{dq} {dq}{2}{dq} {dq}{3}{dq} {dq}{4}{dq}{dq}", dpath, inp1, inp2, sPath, FilePath)
Call Shell(ShellCmd, vbNormalFocus)
Apologies in advance if I've still managed to get the {dq} mismatched.
Call Shell("cmd.exe /S /k ""C:\Program Files\Java\jre1.8.0_171\bin\javaw.exe"" -jar " & _
DPath & " """ & inp1 & """ """ & inp2 & """ """ & sPath & """ """ & _
FilePath & """ ", vbNormalFocus)
...depending on the various values of your concatenated variables

PowerShell command doesn't work when called from VBA, but otherwise works

Currently I can't use xlwings because I can't access Windows' cmd. But I can access PowerShell, so I tried to change xlwings specific VBA code that calls the cmd to call the PowerShell instead.
When calling PowerShell with commands from VBA, it does not work. If I paste the exact same commands in the PowerShell terminal, it works as expected.
I've tried to compare the (not working) command VBA passes to PowerShell with the (working) command I manually paste into PowerShells terminal. But they look exactly the same.
The original xlwings code that calls the cmd
RunCommand = PythonInterpreter & " -B -c ""import sys, os; sys.path[0:0]=os.path.normcase(os.path.expandvars(r'" & PYTHONPATH & "')).split(';'); " & PythonCommand & """ "
ExitCode = Wsh.Run("cmd.exe /C " & _
DriveCommand & RunCommand & _
"""" & WORKBOOK_FULLNAME & """ ""from_xl""" & " " & _
Chr(34) & Application.Path & "\" & Application.Name & Chr(34) & " " & _
Chr(34) & Application.Hwnd & Chr(34) & _
" 2> """ & LOG_FILE & """ ", _
WindowStyle, WaitOnReturn)
And my slightly modified version
RunCommand = "C:\ProgramData\Anaconda3\pythonw.exe -B -c ""import sys, os; sys.path[0:0]=os.path.normcase(os.path.expandvars(r'" & PYTHONPATH & "')).split(';'); " & PythonCommand & """ "
ExitCode = Wsh.Run("Powershell.exe -ExecutionPolicy ByPass -NoExit " & _
DriveCommand & RunCommand & _
"""" & WORKBOOK_FULLNAME & """ ""from_xl""" & " " & _
Chr(34) & Application.Path & "\" & Application.Name & Chr(34) & " " & _
Chr(34) & Application.Hwnd & Chr(34) & _
" 2> """ & LOG_FILE & """ ", _
WindowStyle, WaitOnReturn)
The resulting command from aboves code. Working when pasted into PowerShells terminal directly, not working when executed from VBA:
C:\ProgramData\Anaconda3\pythonw.exe -B -c "import sys, os; sys.path[0:0]=os.path.normcase(os.path.expandvars(r'C:\Users\<placeholder>\test1;')).split(';'); import test1;test1.hello_xlwings()" "C:\Users\<placeholder>\test1\test1.xlsm" "from_xl" "C:\Program Files (x86)\Microsoft Office\Office16\Microsoft Excel" "788640" 2> "C:\Users\<placeholder>\AppData\Roaming\xlwings.log"
I expect a simple "Hello, World!" in a specific Excel cell when clicking a button associated with the vba macro. Instead I get this error:
At line:1 char:213
+ ... X0RNZ\test1;')).split(';'); import test1;test1.hello_xlwings() C:\Use ...
+ ~
An expression was expected after "(".
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : ExpectedExpression
If I paste the command in the PowerShell terminal directly I get my expected result, "Hello, World!" shows up in my specific Excel cell.
You are missing the -Command parameter. Depending on what DriveCommand contains, you should add -Command before DriveCommand or RunCommand.
Make sure there is a semicolon between de PowerShell commands and specify the command as a scriptblock, example:
powershell.exe -ExecutionPolicy ByPass -NoExit -Command { cd "c:\folder";c:\folder\run.exe "param1" "param2" }
Run powershell /? for more examples.

Merging two text files using Shell script in VBA

I have three String varibales in my VBA code as below:
Dim FilePath As String
Dim FooterFilePath As String
Dim PlusChar As String
FilePath = Application.ActiveWorkbook.Path & "\Bin\file.txt"
FooterFilePath = Application.ActiveWorkbook.Path & "\Bin\footer.txt"
PlusChar = Chr(43) 'i.e. "+" sign
Now I am trying to merge FilePath and FooterFilePath and write the combined result in FilePath again i.e. overwriting it. For that I am using followng line which doesn't work:
Shell "cmd.exe copy /c & FilePath & PlusChar & FooterFilePath & FilePath", 0
I know I have syntaxing issue here but I just don't know how to pass three variables into Shell command.
You have to put the variables outside the enclosing double-quotes. also you should insert blanks between the different names so that your command does not get the names combined in one name. Try this:
Shell "cmd.exe copy /c " & FilePath & " " & PlusChar & " " & FooterFilePath & " " & FilePath, 0
But:
There's no reason to have a variable for the +
Your drive's name is missing in the paths
/C is an option for the cmd program, not for the copy command.
It is useful to print that expression in the immediate window to verify it before execution. To do so, you can put it in a String variable and debug.print it before execution:
This would be correct:
Dim FilePath As String, FooterFilePath As String, myCommand As String
FilePath = Application.ActiveWorkbook.Path & "C:\Bin\file.txt"
FooterFilePath = Application.ActiveWorkbook.Path & "C:\Bin\footer.txt"
myCommand = "cmd /c copy " & FilePath & " + " & FooterFilePath & " " & FilePath
Debug.Print myCommand
Shell myCommand, 0

Send email from command line

I use Excel VBA to create emails in Thunderbird using command line arguments, found here:
http://kb.mozillazine.org/Command_line_arguments_%28Thunderbird%29
Composing the mails works, but how can I send it automatically?
Alternatively, is there a button in Thunderbird to send all composed emails at once?
VBA code looks like this:
Option Explicit
Sub thunderbird()
Dim strTh As String
Dim strCommand As String
strTh = "C:\Program Files (x86)\Mozilla Thunderbird\thunderbird.exe "
strCommand = strCommand & " -compose " & "to=" & Chr(34) & "foo#bar.de" & Chr(34)
strCommand = strCommand & ",preselectid=id2"
strCommand = strCommand & ",subject=" & Chr(34) & "wow so email" & Chr(34)
strCommand = strCommand & ",body=" & Chr(34) & "anything" & Chr(10) & "more" & Chr(34)
Call Shell(strTh & strCommand, vbNormalFocus)
End Sub
Unfortunately, this is not possible as such. However, https://support.mozilla.org/questions/1144493 lists alternatives, in case they work for you.
From UI, File -> Send Unsent Messages should work (you may need to activate the menu bar) to send all composed messages.

VBscript to monitor system performance leaks memory

I have a simple script that monitors processes' different performance statistics in Windows XP in a loop until it is terminated.
Despite my efforts, the script's memory footprint increases in size over time.
Any advice is greatly appreciated.
Set fso = CreateObject("Scripting.FileSystemObject")
logFileDirectory = "C:\POSrewrite\data\logs"
Dim output
Dim filePath
filePath = "\SCOPerformance-" & Day(Now()) & Month(Now()) & Year(Now()) & ".log"
IF fso.FolderExists(logFileDirectory) THEN
ELSE
Set objFolder = fso.CreateFolder(logFileDirectory)
END IF
logFilePath = logFileDirectory + filePath + ""
IF (fso.FileExists(logFilePath)) THEN
set logFile = fso.OpenTextFile(logFilePath, 8, True)
output = VBNewLine
output = output & (FormatDateTime(Now()) + " Open log file." & VBNewLine)
ELSE
set logFile = fso.CreateTextFile(logFilePath)
output = output & (FormatDateTime(Now()) + " Create log file." & VBNewLine)
END IF
output = output & (FormatDateTime(Now()) + " Begin Performance Log data." & VBNewLine)
output = output & ( "(Process) (Percent Processor Time) (Working Set(bytes)) (Page Faults Per Second) (PrivateBytes) (PageFileBytes)" & VBNewLine)
WHILE (True)
On Error Resume NEXT
IF Err = 0 THEN
strComputer = "."
Set objRefresher = CreateObject("WbemScripting.SWbemRefresher")
Set objServicesCimv2 = GetObject("winmgmts:\\" _
& strComputer & "\root\cimv2")
Set objRefreshableItem = _
objRefresher.AddEnum(objServicesCimv2 , _
"Win32_PerfFormattedData_PerfProc_Process")
objRefresher.Refresh
' Loop through the processes three times to locate
' and display all the process currently using
' more than 1 % of the process time. Refresh on each pass.
FOR i = 1 TO 3
objRefresher.Refresh
FOR Each Process in objRefreshableItem.ObjectSet
IF Process.PercentProcessorTime > 1 THEN
output = output & (FormatDateTime(Now()) & "," & i ) & _
("," & Process.Name & _
+"," & Process.PercentProcessorTime & "%") & _
("," & Process.WorkingSet) & ("," & Process.PageFaultsPerSec) & _
"," & Process.PrivateBytes & "," & Process.PageFileBytes & VBNewLine
END IF
NEXT
NEXT
ELSE
logFile.WriteLine(FormatDateTime(Now()) + Err.Description)
END IF
logFile.Write(output)
output = Empty
set objRefresher = Nothing
set objServicesCimv2 = Nothing
set objRefreshableItem = Nothing
set objFolder = Nothing
WScript.Sleep(10000)
Wend
I think the main problem with your script is that you initialize WMI objects inside the loop, that is, on every iteration of the loop, even though these objects are always the same:
strComputer = "."
Set objRefresher = CreateObject("WbemScripting.SWbemRefresher")
Set objServicesCimv2 = GetObject("winmgmts:\\" _
& strComputer & "\root\cimv2")
Set objRefreshableItem = _
objRefresher.AddEnum(objServicesCimv2 , _
"Win32_PerfFormattedData_PerfProc_Process")
You need to move this code out of the loop, e.g., at the beginning of the script.
Other tips and suggestions:
Use Option Explicit and explicitly declare all variables used in your script. Declared variables are slightly faster than undeclared ones.
Use FileSystemObject.BuildPath to combine multiple parts of the path. The useful thing about this method is that it inserts the necessary path separators for you.
logFileDirectory = "C:\POSrewrite\data\logs"
filePath = "SCOPerformance-" & Day(Now) & Month(Now) & Year(Now) & ".log"
logFilePath = fso.BuildPath(logFileDirectory, filePath)
The objFolder variable isn't used in your script, so there's no need to create it. Also, you can make the FolderExists check more readable by rewriting it as follows:
If Not fso.FolderExists(logFileDirectory) Then
fso.CreateFolder logFileDirectory
End If
Move repeated code into subroutines and functions for easier maintenance:
Function DateTime
DateTime = FormatDateTime(Now)
End Function
...
output = output & DateTime & " Open log file." & vbNewLine
Usually you don't need parentheses when concatenating strings:
output = output & DateTime & "," & i & _
"," & Process.Name & _
"," & Process.PercentProcessorTime & "%" & _
"," & Process.WorkingSet & "," & Process.PageFaultsPerSec & _
"," & Process.PrivateBytes & "," & Process.PageFileBytes & vbNewLine
In this article, Eric Lippert (Literally worked on designing and building VBScript at Microsoft) indicates that the order in which you dispose of things may be important. Maybe you are running into one of these bugs?
I'll let you read the rest...
When Are You Required To Set Objects To Nothing?
I would recommend against running the script in a permanent loop within the script unless you actually need such a tight loop. I would suggest a single iteration within in the script, called from Scheduled tasks.
I have run into the exact same issue, using it for a procmon-style attempt to capture a rogue process that appears to be respawning.
Narrowing it all down, it appears to be the objRefresher.Refresh and there simply appears to be no way around it
What I did to overcome this was use a for...next to run it 100 times, then immediately afterwards run the following, which would simply respawn the script and shutdown:
CreateObject("Wscript.Shell").Run """" & WScript.ScriptFullName & """", 0, False
So I would watch the memory crawl from 5Mb to 40Mb, then drop back down to 5Mb

Resources