Oct 6th, 2005, 02:04 AM
Classic VB - How do I shell a command line program and capture the output?
To write a graphical user interface, or GUI, around a command line program is probably something every VB programmer at some point have had the need to do. Doing that for a command line program (from here on simply called CmdApp) that takes a bunch of parameters and switches on the command line and then does its job and exit is very easy to do. We would just create a Form with checkboxes, option buttons, and/or textboxes in which the user can enter these command line options and then we simply call the Shell function.
All of that is pretty straight forward, but what about capturing the output this CmdApp might produce? The most common way I've seen is to shell the command line interpreter (command.com or cmd.exe depending on the Windows OS) and redirect the output to a file.
The above code uses the environment variable "COMSPEC" to get the path and name of the interpreter, and uses the /c switch so it will run MyCommand.exe and then close the command line window. It redirects the output (written to StdOut) to a file named c:\output.txt. We need to shell the intepreter to be able to use the redirection character (>) since the Shell function can't handle that, so we can't shell MyCommand.exe directly.
Call Shell(Environ("COMSPEC") & " /c MyCommand.exe > c:\output.txt", vbHide)
The problem with the above approach is that we have no idea when the CmdApp is done and the output.txt file is ready to be read by our VB app. I've seen different solutions to this problem, one is to pause our VB program for a while (often by calling the Sleep API function) and hope the CmdApp is done after this time. Another (better) approach, I've seen, is not to use the Shell function at all but instead use the CreateProcess API function to start the CmdApp since we can then use one of the Wait functions to pause our app until it is done (the WaitForSingleObject API function is the most commonly used). After that we could read the file and show the content (and also delete the output.txt file since we don't need it anymore).
All of this works but IMHO is not the best way of doing it. We can redirect the StdOut and/or StdErr pipes directly from our VB app and read the result directly without redirecting the output to a temporary file. Another problem that might arise is that the Windows GUI use another character mapping than a CmdApp normally does. This is a problem for characters in the extended ASCII area, characters with an ASCII value greater then 127. Many international letters are in this area and also other special characters like © copyright sign for example. If our CmdApp outputs any of these characters and we read them and shown them in for example a TextBox they will not look the same as they did on the command line. So we actually show the output incorrectly.
We can convert the OEM characters used by CmdApps to Windows ANSI characters using the OemToCharBuff API function.
I have written a BAS module containing all the API declarations needed to create pipes, to duplicate handles, and to convert OEM characters. All that is needed to shell a CmdApp and to read the output directly. This module also has one single VB function called GetCommandOutput. This function will create the process and read StdOut and/or StdErr and return the output. It can optionally (and does it by default) convert the OEM characters.
The function has the following signature:
sCommandLine is of course the command line you want to execute.
Public Function GetCommandOutput( _
sCommandLine As String, _
Optional blnStdOut As Boolean = True, _
Optional blnStdErr As Boolean = False, _
Optional blnOEMConvert As Boolean = True _
) As String
blnStdOut = set to True to capture output written to StdOut (True is default)
blnStdErr = set to True to capture output written to StdErr (False is default)
blnOEMConver = convert OEM characters to Windows ANSI (True is default)
At least one of blnStdOut or blnStdErr must be set to True (you can set both to True if you want to capture both).
The function returns a string containing the output, so this function will not return until the CmdApp has finished executing so you can use it to Shell and Wait even if you're not interested in the output it produces.
So download the attached module and add it to the next project you need to capture any output from a CmdApp. The code is well commented so I hope it's not to difficult to understand but feel free to ask if you have any questions.
Last edited by Joacim Andersson; Oct 7th, 2005 at 08:56 AM.
If anyone's answer has helped you, please show your appreciation by rating that answer.
I'd rather run ScriptBrix...
Joacim's view on stuff
Click Here to Expand Forum to Full Width