Invoking Command Lines Programmatically

Introduction

From time to time, the same situation occurs in my life as a programmer: I am able to accomplish a certain task quickly and easily – such as for example rearranging a computers TCP/IP settings to a set of predefined values or extracting some data from an Oracle database — by using a certain command line tool specialized on that task. But this tool would be far too troublesome to handle for an average computer user, because it is lacking a reasonable user interface and only accepts some strange, hard to understand jargon that must be typed in at the console. This is clearly not what ‘using a computer’ should be about in the 21st century!

What I was missing was a class that would be capable of executing every valid command line without having the caller caring much about things like console windows, system processes and the like. He simply gets back the output from the program he called – the same output that he would be presented with on the console window. The way Visual Studio’s output window works might give you an idea of what I had in mind.

I soon discovered that programmatically executing a command line and receiving the resulting text output is an all but easy and straightforward business. First, you have to create and start a new system process that executes the desired command line. Then you have to create an additional thread that monitors the process’ output pipes. And finally, to make things a bit more entertaining, you have to find out where exactly the output can be found and what code page is needed to make it look like something that one can present to an innocent user. And that’s only the simple case, things get even more complicated when you want to execute a ‘DOS command’ such as e.g. dir

I searched the web for something that at least could be a help in accomplishing that task. I found nothing. In the end, I did what desperate developers often do (because they have to): I wrote my own solution from scratch. I came up with CommandLineProcess, a component class implemented as a Windows Class Library project.

This article describes in the first part some of the most important implementation aspects of this class. The second part presents a test and demo application for the CommandLineProcess class and, by examining some parts of the source code, demonstrates its principal usage.

The CommandLineProcess class

The CommandLineProcess class may be best described as a kind of ‘windowless console’. It is a component class designed for calling an external command line from within a Windows Forms application (or any other application that wants to avoid opening a console window) and receiving its text output. This works for stand-alone command line applications as well as for commands invoking the Windows command line processor (so-called ‘DOS commands’).

CommandLineProcess exposes a set of properties and methods, with which a caller can define a command line, and start and stop its (asynchronous) execution. Information from the executing command line process, including its text output, is provided by various different events.

Some of the class’ most important methods and properties are:

  • The Start method starts the execution of a given command line.
  • The Abort method kills the running process immediately.
  • The Close method frees all the resources (memory, handles etc.) that are associated with the process.
  • The Command property (string) defines which executable or ‘DOS command’ to execute. For Example: netsh.
  • The Arguments property (string) defines the argument list that the executable shall be called with. For example: -f c:some_ip_settings.txt.
  • The CommandLine property (string, read-only) is simply the concatenation of Command and Arguments, i.e. the command line as a whole, as it would be executed on a call to the Start() method. In the example, this would be: netsh -f c:some_ip_settings.txt.
  • The UseComSpec property (bool) determines whether or not the Windows command interpreter (cmd.exe) shall be used to execute the command line (in other words: whether or not the CommandLine property defines a ‘DOS command’).
  • The OutputCodepage property (int) defines the number of the codepage to use for re-encoding of the output text. Initially, this value will be set to the systems default OEM codepage.
  • The IsRunning property (bool, read-only) indicates whether the process is currently executing.

In the following, I will go through some of the main issues of the class’ implementation and I will describe the events that are raised by a CommandLineProcess instance.

Starting the process

To execute a command line, the caller simply sets the values of the Command and Arguments properties to the desired values and then calls the Start() method. Internally, the CommandLineProcess class uses an object of type System.Diagnostics.Process as a wrapper for the system process that is actually executing the given command line.

As mentioned above, CommandLineProcess works for stand-alone applications accessible via a command line interface – for example H2Reg, some variations of the netsh command or some Oracle utilities – as well as for commands addressing the Windows command line processor – ‘DOS commands’ such as dir or ping. Whether or not cmd.exe shall be used can be determined by setting the UseComSpec property to true or false   (‘COMSPEC’ is the name of the environment variable that holds the path to the command line processor, most often its value is C:Windowssystem32cmd.exe).

The code snippet below calls the tnsping Oracle tool for a database called SomeDB:

CommandLineProcess cmd = new CommandLineProcess();
cmd.Command = "tnsping";
cmd.Arguments = "SomeDB";
bool bSuccess = cmd.Start();

A ‘DOS command’ would be called like this:

CommandLineProcess cmd = new CommandLineProcess();
cmd.Command = "dir";
cmd.Arguments = "C:";
cmd.UseComSpec = true;
bool bSuccess = cmd.Start();

Dependent on whether a genuine command line application or a ‘DOS command’ is executed, the internal handling of the command line itself and of the executing process component substantially differs. (However, this difference is totally transparent to the caller.) A command line like tnsping SomeDB can be executed directly by the process component by simply setting the appropriate StartInfo values and then starting it. But executing something like the seemingly simple dir C: requires much more than that. First, an instance of the command line processor cmd.exe has to be started, then the desired command line as a whole must be written to its input stream. Furthermore, there have to be taken some additional steps in order to cause cmd.exe to terminate automatically after executing the desired command and also not to produce text output other than the one originating directly from the executed command (like the path-including command prompts you know from the console window).

The following code excerpt shows the two different approaches (m_proc is a member of type System.Diagnostics.Process):

if (UseComSpec)
{
       // set Process start info to call the 'DOS interpreter'
       // (the 'COMSPEC' environment variable points to it)
       m_proc.StartInfo.FileName  = Environment.GetEnvironmentVariable("COMSPEC");
}
else
{
       // set Process start info to execute command line program directly
       m_proc.StartInfo.FileName  = Command;
       m_proc.StartInfo.Arguments = Arguments;
}

if (!m_proc.Start()) return false;      // start process

...

// Some extra actions have to be taken for commands executed by the 'DOS interpreter'
if (UseComSpec)
{
       // first use "echo off" to suppress path prompts ...
       m_proc.StandardInput.WriteLine("echo off");
       // ... then execute actual command ...
       m_proc.StandardInput.WriteLine(CommandLine);
       // ... and finally cause the command line processor (cmd.exe) to quit
       // immediately after finishing execution.
       m_proc.StandardInput.WriteLine("exit");
}

Intercepting the process output

After the process that executes the desired command line has successfully been started, the next objective is to intercept the text output it produces. Every process is provided by the operating system with two output streams, called StandardOutput und StandardError, both have to be checked permanently for possible process output. This is done by starting an additional thread, whose thread procedure checks the two streams everytime the thread is called by the operating system.

This is the method that starts the listening thread (m_threadRec is a member of type System.Threading.Thread):

protected virtual bool StartOutputRecording()
{
       try
       {
             // Create listening thread that will execute our thread procedure
             m_threadRec = new Thread(new ThreadStart(ReadProcessOutput));

              ...

             m_threadRec.Start();
             return true;
       }
       catch
       {
             m_threadRec = null;
             return false;       // failure
       }
}

And this is an extract from the related thread procedure that does the actual work:

protected virtual void ReadProcessOutput()
{
       string strLine;

       // check process' StandardOutput stream
       try
       {
             while ((strLine = m_proc.StandardOutput.ReadLine()) != null)
             {
                    ...

                    RaiseOutputLineEvent(OutputType.StdOut, strLine);     // send line
             }
       }
       catch {}

       // check process' StandardError stream
       try
       {
             while ((strLine = m_proc.StandardError.ReadLine()) != null)
             {
                     ...

                    RaiseOutputLineEvent(OutputType.StdErr, strLine);     // send line
             }
       }
       catch {}
}

One last note: One might presume an application to write ist regular output to the StandardOutput stream and its error messages to StandardError. Well, in a perfect world this may be the case. However, in this world, you better not rely blindly on that fact …

Stopping the process

Besides starting a process, CommandLineProcess provides the caller also with the possibility to immediately kill the running process (via its Abort() method). This is the same as pressing Ctrl+C on the console window whilst some command is being executed. Again, the actions that have to be taken greatly vary dependent on whether a command line is run by the command line processor or by a regular executable.

For ‘normal’ executables aborting simply means killing the related process. In this case, the code line m_ proc.Kill(); does all the job. But if we are running a ‘DOS command’, again it is not that simple at all.

Consider, for example, the simple command tree C:. If you would simply call the Kill() method on the executing process, you would terminate the instance of the command line processor that was started to execute the tree command. That’s ok so far. The problem is: tree.com itself is an executable, and thus a process, that in turn was started by the command line processor to execute our tree C: command. Thus, if we would simply call m_ proc.Kill(), we would leave behind an abandoned system process (named tree.com in this case). Obviously we need to find and kill our tree.com process instead of killing the related cmd.exe process, which then will terminate by itself as a reaction to the termination of the related tree.com process.

But it does not necessarily have to be that difficult. There are also many commands that are native to the command line processor. The dir command is an example for that. If you want to kill a process that is currently executing the command dir C:, killing the cmd.exe process is actually all you need to do. The thing is: You cannot know in advance whether or not you have to deal with an additional process. That’s why CommandLineProcessAbort() method has become quite long. Here’s an excerpt from that method that demonstrates the logic I described:

// First check if there is a process running that has the name of our 'DOS command'.
// This may or may not be true, because some of the 'DOS commands' are actually
// executed by external processes whereas others are not. E.g. the 'tree' command is
// actually executed by calling the 'tree.com' executable, whereas the 'dir' command is
// handled internally by the command line interpreter.
Process[ ] procs = Process.GetProcessesByName(Command);
int nCount = procs.GetLength(0);
Process procToKill = null;
int nCmdLen = Command.Length;

if (nCount == 1) // We have found one (and only one) process that has the name we look for.
{
	procToKill = procs[0];
}
else if (nCount == 0)     // No such process, but there may be one that has a file extension
{                         // appended to its name. E.g. executing the 'tree' command raises
                          // a process identified to the system as 'tree.com'.
       foreach (Process p in Process.GetProcesses())
       {
             if ((p.StartTime > m_proc.StartTime) && (p.ProcessName.Length > nCmdLen))
             {
                    if (p.ProcessName.Substring(0, nCmdLen) == Command)
                    {
                           if (procToKill == null) procToKill = p;
                           if (p.StartTime < procToKill.StartTime) procToKill = p;
                    }
             }
       }
}
else   // We have found more than one process of the desired name. In this case,
{      // we kill the one which was started immediately after the command line
       // interpreter itself (which is represented by the m_proc variable).
       foreach (Process p in procs)
       {
             if (p.StartTime > m_proc.StartTime)
             {
                    if (procToKill == null) procToKill = p;
                    if (p.StartTime < procToKill.StartTime) procToKill = p;
             }
       }
}

// If we have found an additional system process that was started by the process
// represented by the m_proc variable (which is actually an instance of the command
// line processor), we kill it here and this will cause m_proc to terminate as well.
// If no additional process was found, we have to kill m_proc directly.
if (procToKill != null) procToKill.Kill();
       else m_proc.Kill();

– The above code is certainly not very elegant, but hey! It works. –

Getting information from the process

Since CommandLineProcess is designed to asynchronously execute an external system process, the question arises how the communication between the called and the calling process can be done. For that purpose, CommandLineProcess implements a set of events that enable the calling process to keep track of the various possible object states and to receive the text output from the process. The following events are available:

  • CmdLineChanged signals that the object’s command line text (or its mode of usage with regard to cmd.exe) has changed.
  • OutputLine broadcasts one line of text read from one of the system process’ output streams.
  • Started signals the successful start of the command line process.
  • Aborted signals that a running command line process was killed.
  • Exited signals that the command line process has exited.
  • Closed signals that all system resources allocated for the process component were freed.

Note Because the OutputLine event is triggered by an external thread, any code handling this event must be thread-safe!

In the second part of this article, I will show how these events can be used in your own code. For more implementation details please refer to the code and the documentation included in the src download package.

Getting human-readable text: the code page issue

The last thing I’d like to address here deals with text encoding. Sometimes the text output produced by a process might contain some special characters that are not part of the standard ASCII character set. Something like Ping-Statistik f.r 127.0.0.1: is clearly nothing that one would want to present to a user, or you may try the tree command without using code page no. 850 to see what I mean. That’s why the CommandLineProcess class exposes the Codepage property, that lets the calling process select an appropriate code page to re-encode the text before it is broadcasted via the OutputLine event. Re-encoding of the text takes place in the ReadProcessOutput() method that was already introduced above. An object of type System.Text.Encoding is used to do that. Here’s how the re-encoding is done.

First, the received text (strLine) is converted to a byte array, using the original output encoding:

m_buffer = m_proc.StandardOutput.CurrentEncoding.GetBytes(strLine);

Then the byte array gets re-encoded using the desired code page (which is represented by the m_encOutput variable):

String strResult = m_encOutput.GetString(m_buffer);

The OutputCodepage property will initially be set to a default value reflecting the standard OEM code page of the system. The DefaultOutputCodepage property returns the number of this code page:

// set OEM code page of current system environment by default
return CultureInfo.CurrentCulture.TextInfo.OEMCodePage;

This value should be good enough for ‘DOS commands’ and many command line tools. However, in some cases you might have to experiment a while in order to obtain a text that is human-readable …

The provided online documentation

Besides the usual solution and source files, the src zip file contains an elaborate and extensive online documentation for the CommandLineProcess class that was created using Visual Studio’s XML documentation feature and NDoc 1.3. This documentation gives a thorough overview of the CommandLineProcess class and goes deeper into some implementation details that could not be addressed here. – Admittedly, the extensive XML comments make the source file somewhat hard to read. But Visual Studio’s outlining feature should enable you to find the code between the comments …

I provided two versions of this documentation: one is a HTML 2 .chm help file for stand-alone usage, the other version is for integration with Visual Studio’s MSDN online help system. (There is also a short how-to text file that briefly describes how this can be done, it’s quite easy and straightforward.)

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read