Results 1 to 3 of 3

Thread: Classic VB - How do I shell a command line program and capture the output?

  1. #1

    Thread Starter
    I'm about to be a PowerPoster! Joacim Andersson's Avatar
    Join Date
    Jan 1999
    Location
    Sweden
    Posts
    14,649

    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.
    VB Code:
    1. Call Shell(Environ("COMSPEC") & " /c MyCommand.exe > c:\output.txt", vbHide)
    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.

    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:
    VB Code:
    1. Public Function GetCommandOutput( _
    2.  sCommandLine As String, _
    3.  Optional blnStdOut As Boolean = True, _
    4.  Optional blnStdErr As Boolean = False, _
    5.  Optional blnOEMConvert As Boolean = True _
    6. ) As String
    sCommandLine is of course the command line you want to execute.
    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.
    Attached Files Attached Files
    Last edited by Joacim Andersson; Oct 7th, 2005 at 08:56 AM.

  2. #2
    Old Member moeur's Avatar
    Join Date
    Nov 2004
    Location
    Wait'n for Free Stuff
    Posts
    2,712

    Re: Classic VB - How do I shell a command line program and capture the output?

    Very useful code Joacim
    There are three problems with the technique outlined above:
    1. Your program hangs while it waits for the console program to complete its execution
    2. Stdout and stderr are returned together in one variable
    3. No output is received until the console program has completed running
    The following is a modification of Joacim's code that adresses problems 1 and 2. Unfortunately I doubt that there is a solution for problem number 3.

    Problem 1 is caused because calling ReadFile is a synchronous operation that waits for the console process to complete before returning. One way around this problem is to call PeekNamedPipe instead. This way you can continue to poll the console process to see if it has completed. Once it has, make your call to ReadFile to get the information.

    Problem 2 can be solved by simply using seperate read pipes for stdout and stderr.

    I've wrapped all this up in a class that uses an API timer to poll the console process and raises an event when data is ready.

    The class has one method:
    lErr = RunCommand(hwnd, strCommand)

    hwnd - a window handle to you form that created the class
    strCommand - The console command you want to run i.e."C:\bin\Test.exe"

    Returns - zero on success, and an API error code on failure
    And one event:
    Complete(stdOut As String, stdErr As String)

    stdOut - returns all stdOut text the console program produced
    stdErr - returns all stdErr text the console program produced
    Attached is the class and a demo program
    Attached Files Attached Files
    Last edited by Hack; Jul 19th, 2012 at 05:46 AM. Reason: Removed EXE From Attachment

  3. #3

    Thread Starter
    I'm about to be a PowerPoster! Joacim Andersson's Avatar
    Join Date
    Jan 1999
    Location
    Sweden
    Posts
    14,649

    Re: Classic VB - How do I shell a command line program and capture the output?

    Another solution to problem 1 is actually mentioned in the source code, and that is to compile the code to an ActiveX EXE and raise an event from inside the procedure. However for the application I wrote this code for I had to wait for the shelled application to end.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width