VBScript's Shell Command Will Not Execute Unle

2019-07-23 13:24发布

问题:

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.

回答1:

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


回答2:

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.