I've been tearing my hair out this afternoon. From Access VBA I've been issuing a Command line via a shell to FTP a file to a 3rd party server using cURL.exe.
Things were working great until I brought my code to production where it is now failing silently. I suspect the multiple unpredictable file paths producing "strCmd" are just too long to pass thru the Command shell. >> Is there a limit? <<
fShellRun (strCmd)
curl -k -T testfile.txt --ftp-ssl --ftp-pasv -u "username\$domain:password" ftp://ftp-domain.egnyte.com/Shared/testdirectory/
This is the Shell function I am using (not mine):
Function fShellRun(sCommandStringToExecute)
Dim oShellObject, oFileSystemObject, sShellRndTmpFile
Dim oShellOutputFileToRead, iErr
Set oShellObject = CreateObject("Wscript.Shell")
Set oFileSystemObject = CreateObject("Scripting.FileSystemObject")
sShellRndTmpFile = oShellObject.ExpandEnvironmentStrings("%temp%") & oFileSystemObject.GetTempName
On Error Resume Next
oShellObject.Run sCommandStringToExecute & " > " & sShellRndTmpFile, 0, True
iErr = Err.Number
'~on error goto 0
If iErr <> 0 Then
fShellRun = ""
Exit Function
End If
'~on error goto err_skip
fShellRun = oFileSystemObject.OpenTextFile(sShellRndTmpFile, 1).ReadAll
oFileSystemObject.DeleteFile sShellRndTmpFile, True
Exit Function
err_skip:
fShellRun = ""
oFileSystemObject.DeleteFile sShellRndTmpFile, True
End Function
I am noticing strCmd longer than ~200 chars fails silently.
Questions:
Is there a string length limit using a command shell?
How might I circumvent this limit?
Thanks!
Edit: The long command string (copy/paste from debug.print) works just fine in an open command window. Leads me to think there is an issue with the shell command itself. (?)
Related
I'm trying to run a shell command for google speech recognition. I'm able to run the command only if I provide an output file to the command string.
As you can see my test code sample below, I would attach the ">outputFile" if one is provided and also coded in a timeout loop to abort the process after a set time limit.
strCommand = "cmd /c ipconfig /all"
If outputFile <> "" Then
strCommand = strCommand & " > """ & outputFile & """"
End If
Set wshShellExec = wshShell.Exec(strCommand)
expiration = DateAdd("s", 600, Now)
Do While wshShellExec.Status = WshRunning And Now < expiration
WScript.Sleep 5000
Loop
Select Case wshShellExec.Status
Case WshRunning
wshShellExec.Terminate
TestFunction = "{""error"": ""TestFunction Command Timed Out""}"
Case WshFinished
TestFunction = WshShellExec.StdOut.ReadAll()
Case WshFailed
TestFunction = wshShellExec.StdErr.ReadAll()
End Select
If I leave outputFile empty and try to expect the output to be returned from the function, all it does is sit still for 5 minutes before timing out and sending me my error message.
Why does it need an output file to run?
If I run the command line manually on a Command Prompt, it runs perfectly fine.
Output buffers have limited capacity. If your command writes too much text to stdout the buffer will fill up and block the command from writing more until you clear the buffer (e.g. by reading from it). ReadAll can't be used for that, though, because that method will only return after the command has finished and block otherwise, thus creating a deadlock.
Your best option is to redirect output to one or more (temp) files, and read the output from those files after the command has finished.
outfile = "C:\out.txt"
errfile = "C:\err.txt"
cmd = "cmd /c ipconfig /all >""" & outfile & """ 2>""" & errfile & """"
timeout = DateAdd("s", 600, Now)
Set sh = CreateObject("WScript.Shell")
Set ex = sh.Exec(cmd)
Do While ex.Status = WshRunning And Now < timeout
WScript.Sleep 200
Loop
Set fso = CreateObject("Scripting.FileSystemObject")
outtxt = fso.OpenTextFile(outfile).ReadAll
errtxt = fso.OpenTextFile(errfile).ReadAll
If you don't want to do that for some reason you must read from StdOut repeatedly.
outtxt = ""
errtxt = ""
cmd = "ipconfig /all"
timeout = DateAdd("s", 600, Now)
Set sh = CreateObject("WScript.Shell")
Set ex = sh.Exec(cmd)
Do While ex.Status = WshRunning And Now < timeout
WScript.Sleep 200
outtxt = outtxt & ex.StdOut.ReadLine & vbNewLine
Loop
Note that you may also need to read from StdErr, because that buffer might fill up too if there is too much error output. However, reading both buffers might create another deadlock, because IIRC ReadLine blocks until it can read a full line, so if the script might hang waiting for error output that never appears. You might be able to work around that by using Read instead of ReadLine, but it'll still be very fragile.
So, again, your best option is to redirect command output to files and read those files after the command terminates.
Once the wshShellExec is terminated in the WshRunning case, instead of assigning the error message, the output should be assigned.
Select Case wshShellExec.Status
Case WshRunning
wshShellExec.Terminate
TestFunction = "Terminated: " & vbcrlf & WshShellExec.StdOut.ReadAll()
Case WshFinished
TestFunction = "Finished: " & vbcrlf & WshShellExec.StdOut.ReadAll()
Case WshFailed
TestFunction = wshShellExec.StdErr.ReadAll()
End Select
Set sh = CreateObject("Wscript.Shell")
Set objFSO = CreateObject("Scripting.FileSystemObject")
Dim counter, myNum, fileLine
myNum = 0
counter = 9000000
Do While myNum < counter
myNum = myNum + 1
Call GetConnections()
Loop
Function GetConnections()
i = 0
outFile = "netband_logger_vbs.tmp"
Set objFile = objFSO.CreateTextFile(outFile, True)
Set shExec = sh.Exec("netstat -e")
Do While Not shExec.StdOut.AtEndOfStream
fileLine = shExec.StdOut.ReadLine()
objFile.Write fileLine & vbCrLf
objFile.Close
Loop
End Function
I have the VBScript above. What I want to do is to run the netstat -e command 9000000 times and write every line of output to a text file line by line. And each time the after the first round of executions have terminated the script should overwrite the previous content of the netband_logger_vbs.tmp file with the values from the new round of executions.
Currently I have two problems: I can't seem to write the entire output to my .tmp file and I am also faced with an "object variable not set" error.
The error you're getting is probably because you're closing the file handle after the first iteration. To fix this move the line objFile.Close after the loop.
With that said, I wouldn't recommend using the Exec method here anyway. In your scenario it's much easier to shell out to CMD and use output redirection:
sh.Run "%COMSPEC% /c netstat -e >""" & outFile & """", 0, True
As for why you can't get all the entire output into one file; the FAT32 filesystem has a 4GB cap, and if you're writing lot's of small .tmp files there's a directory cap.
And to prevent the file from being closed during a lapse in the StdOut stream move objFile.Close to after the Loop
I created the following script, but it only returns 0, even finding errors in DNS test.
Dim consult, objShell
Set objShell = WScript.CreateObject ("WScript.shell")
consult = objShell.run ("dcdiag /test:DNS | findstr /i failed", 0)
If consult = "0" THEN
WScript.Echo "OK"
else
WScript.Echo "ERROR"
end If
I understand that is not running the findstr.
Thank attention.
You don't use "Option Explicit"; that's reckless.
You don't init near/immediately after your Dims; that's error prone.
You use variables (objShell) just once; that's wasteful.
You mix data types (integer vs string) in a comparison; that's stupid.
You don't read the Docs (carefully); that's impertinent.
From the Docs:
bWaitOnReturn
Optional. Boolean value indicating whether the script should wait for
the program to finish executing before continuing to the next
statement in your script. If set to true, script execution halts until
the program finishes, and Run returns any error code returned by the
program. If set to false (the default), the Run method returns
immediately after starting the program, automatically returning 0 (not
to be interpreted as an error code).
To avoid unnecessary problems caused by missing dcdiag or different versions of findstr or mis-understanding/use of those programs, I use "minimal errorlevel setters":
type ex0.vbs, ex1.vbs
ex0.vbs
WScript.Quit 0
ex1.vbs
WScript.Quit 1
and this code:
Option Explicit
Dim consult : consult = WScript.CreateObject("WScript.Shell").Run("%comspec% /c ex0.vbs | ex1.vbs", 0, True)
If consult = "0" THEN
WScript.Echo consult, "OK"
else
WScript.Echo consult, "ERROR"
end If
output:
cscript 36531325.vbs
1 ERROR
Use
.Run("ex0.vbs | ex1.vbs", 0, True)
to see that you need a shell (%comspec%) to use a shell's features (|) and
.Run("%comspec% /c ex0.vbs | ex1.vbs", 0)
for understandig the importance of the bWaitOnReturn parameter.
I'm trying to run a program (with argument /config) using Shell.Run from VBS. However I'm having an exit code = 87 (cannot find the file specified).
1st piece of code I've tried:
strCommand = """c:\Program Files\Test\launch.exe""" & " /config:C:\sample.xml"
intExit = objShell.Run(strCommand, 0, True)
2nd piece of code:
Dim FileExe, Argum
FileExe = "%ProgramFiles%\Test\launch.exe"
Argum = "/config:C:\sample.xml"
RunMe FileExe, Argum
Function RunMe(FileExe, Argum)
Dim Titre, ws, Command, Exec
Titre = "Execution avec argument"
Set ws = CreateObject("WScript.Shell")
command = "cmd /c "& qq(FileExe) & " " & Argum &" "
Msgbox command, 64, Titre
Exec = ws.Run(command, 0, True)
End Function
Function qq(str)
qq = chr(34)& str &chr(34)
End Function
Of course yes, the Run command is supposed to return something.
I was here because I was hesitant about the second parameter. I found it there :
Documentation of Windows Script Host Run method, on vbsedit.com
Shell.Run returns the return value of the command line that it executes, so in this case, you will find the signification of the return code in the documentation of the Launch program, in the Test folder. (Test means what it means ...)
Of course it would ease the comprehension if the Launch program respected the conventions about the significations of the codes, but for a test you do not always enter into those details. Probably because of this, 87 remains me nothing. A missing file is a quite classical error, with code 2. But perhaps 2 would be for a data file.
Hoping for some help
I am using this script to ftp to one of my servers.
<%
Set oShell = CreateObject("WScript.Shell")
cmdLine = "c:\windows\system32\ftp.exe -v -i -s:C:\windows\system32\ftp.exe -s:"+Request.Form("website")+""
tempRet = oShell.Run("c:\windows\system32\cmd.exe /c " & cmdLine, 0, true)
set oShell = nothing
waitTime = numberOfFiles * 2
startTime = Timer
do while timer < startTime + waitTime
loop
%>
I suddenly get this error code, and can just not figure it out.
error 'fffffffe'
The line
cmdLine = "c:\windows\system32\ftp.exe -v -i -s:C:\windows\system32\ftp.exe -s:"+Request.Form("website")+""
seems to have two + symbols. This converts the cmdLine variable to an int with value 0. Replace the crosses with pretzels (&).
I do not know if that is the source of the problem, but it is definitely not correct.
The line
cmdLine = "c:\windows\system32\ftp.exe -v -i -s:C:\windows\system32\ftp.exe -s:"+Request.Form("website")+""
seems to be have the ftp executable as the value to the s: option. Possibly a copy n' paste error? s needs to be given a filename that includes ftp commands