Programmatically controlling another application - redirecting input and output

Programmatically controlling another command-line application requires redirecting the input and output of that application. This requires passing new input and output handles in the STARTUPINFO when calling CreateProcess. You will need anonymous pipes to do this. See the following example to learn how to start the command interpeter, perform several commands and exit. The first example is in C, the second one in Visual Basic 6. Both examples have been tested on Czech Windows XP SP1.

Example in C/C++:

  SECURITY_ATTRIBUTES saAttr;
  STARTUPINFO sti;
  PROCESS_INFORMATION ps;

  // THE HANDLES MUST BE INHERITABLE -
  // create the security descriptor
  saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
  saAttr.bInheritHandle = TRUE;
  saAttr.lpSecurityDescriptor = NULL;

  // Create three pipes STDIN, STDOUT, STDERR
  // Pass the read end of STDIN and write ends of
  // STDOUT and STDERR to the process.
  // Use the write end of STDIN to command the process
  // Use the read ends STDOUT and STDERR to get response
  // from the process.
  HANDLE hStdInRead, hStdInWrite;
  HANDLE hStdOutRead, hStdOutWrite;
  HANDLE hStdErrRead, hStdErrWrite;

  CreatePipe(  &hStdInRead,  &hStdInWrite, &saAttr, 0);
  CreatePipe( &hStdOutRead, &hStdOutWrite, &saAttr, 0);
  CreatePipe( &hStdErrRead, &hStdErrWrite, &saAttr, 0);

  // Create startup information. Must specify
  // STARTF_USESTDHANDLES and the handles
  sti.cb = sizeof(sti);
  sti.lpReserved = 0;
  sti.lpDesktop= NULL;
  sti.lpTitle = "Bla";
  sti.dwX = 0;
  sti.dwY = 0;
  sti.dwXSize = 0;
  sti.dwYSize = 0;
  sti.dwXCountChars = 0;
  sti.dwYCountChars = 0;
  sti.dwFillAttribute = 0;
  sti.dwFlags = STARTF_USESTDHANDLES;
  sti.wShowWindow = 0;
  sti.cbReserved2 = 0;
  sti.lpReserved2 = 0;

  sti.hStdInput = hStdInRead;
  sti.hStdOutput = hStdOutWrite;
  sti.hStdError = hStdErrWrite;

  // Create the process. If you want its window to be
  // visible, do not use the CREATE_NO_WINDOW attribute
  // Starts a new command interpreter.  Note that using
  // command.com dos not work for unknown reasons
  CreateProcess( 0, "cmd.exe", 0, 0, TRUE,
                 NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,
                 0, 0, &sti, &ps);

  // Give some command to the interpreter
  char *s = "echo Blah Blah Blah\n";
  char buffer[512];
  DWORD d;

  WriteFile( hStdInWrite, s, strlen(s), &d, 0);
  // Give it some time to initialize and process the command
  Sleep(5000);
  // Read response
  ReadFile( hStdOutRead, buffer, sizeof(buffer)-1, &d, 0);
  buffer[d]='\0';
  // Display the response
  ::MessageBox( 0, buffer, "Result", MB_OK);

  // Instruct the interpreter to quit
  s = "exit\n";
  WriteFile( hStdInWrite, s, strlen(s), &d, 0);

  // Close all handles
  CloseHandle( ps.hProcess);
  CloseHandle( ps.hThread);
  CloseHandle(  hStdInRead); CloseHandle(  hStdInWrite);
  CloseHandle( hStdOutRead); CloseHandle( hStdOutWrite);
  CloseHandle( hStdErrRead); CloseHandle( hStdErrWrite);

Example in Visual Basic 6:

Option Explicit

Public Const CREATE_NO_WINDOW = &H8000000
Public Const NORMAL_PRIORITY_CLASS = &H20
Public Const STARTF_USESTDHANDLES = &H100

Public Type SECURITY_ATTRIBUTES
  nLength As Long
  lpSecurityDescriptor As Long
  bInheritHandle As Long
End Type

Public Type STARTUPINFO
  cb As Long
' I have changed lpReserved, lpDesktop and lpTitle from string
' to long to allow passing 0 (NULL).
  lpReserved As Long
  lpDesktop As Long
  lpTitle As Long
  dwX As Long
  dwY As Long
  dwXSize As Long
  dwYSize As Long
  dwXCountChars As Long
  dwYCountChars As Long
  dwFillAttribute As Long
  dwFlags As Long
  wShowWindow As Integer
  cbReserved2 As Integer
  lpReserved2 As Long
  hStdInput As Long
  hStdOutput As Long
  hStdError As Long
End Type

Public Type PROCESS_INFORMATION
  hProcess As Long
  hThread As Long
  dwProcessId As Long
  dwThreadId As Long
End Type

Public Type OVERLAPPED
  Internal As Long
  InternalHigh As Long
  offset As Long
  OffsetHigh As Long
  hEvent As Long
End Type

Public Declare Function CreatePipe Lib "kernel32" (phReadPipe As Long, _
  phWritePipe As Long, lpPipeAttributes As SECURITY_ATTRIBUTES, _
  ByVal nSize As Long) As Long

  ' lpOverlapped has been declared as byval long to allow passing zero (NULL)
' If you need to pass a real OVERLAPPED structure, you will need to declare
' them as byref OVERLAPPED. I also must have changed lpBuffer from byref as any
' to byval as string to get it working properly
Public Declare Function WriteFile Lib "kernel32" (ByVal hFile As Long, _
  ByVal lpBuffer As String, ByVal nNumberOfBytesToWrite As Long, _
  lpNumberOfBytesWritten As Long, ByVal lpOverlapped As Long) As Long
Public Declare Function ReadFile Lib "kernel32" (ByVal hFile As Long, _
  ByVal lpBuffer As String, ByVal nNumberOfBytesToRead As Long, _
  lpNumberOfBytesRead As Long, ByVal lpOverlapped As Long) As Long

' lpProcessAttributes and lpThreadAttributes have been declared
' as byval long to allow passing zero (NULL). If you need to pass a real
' SECURITY_DESCRIPTOR, you will need to declare them as
' byref SECURITY_DESCRIPTOR. The same holds for lpApplicationName,
' lpEnvironment and lpCurrentDirectory (all originally string)
Public Declare Function CreateProcess Lib "kernel32" Alias "CreateProcessA" _
  (ByVal lpApplicationName As Long, ByVal lpCommandLine As String, _
  ByVal lpProcessAttributes As Long, ByVal lpThreadAttributes As Long, _
  ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
  ByVal lpEnvironment As Long, ByVal lpCurrentDriectory As Long, _
  lpStartupInfo As STARTUPINFO, _
  lpProcessInformation As PROCESS_INFORMATION) As Long
Public Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)





Public Sub StartCMD_EXE()
  Dim saAttr As SECURITY_ATTRIBUTES
  Dim sti As STARTUPINFO
  Dim ps As PROCESS_INFORMATION

  ' THE HANDLES MUST BE INHERITABLE -
  ' create the security descriptor
  saAttr.nLength = LenB(saAttr)
  saAttr.bInheritHandle = True
  saAttr.lpSecurityDescriptor = 0

  ' Create three pipes STDIN, STDOUT, STDERR
  ' Pass the read end of STDIN and write ends of
  ' STDOUT and STDERR to the process.
  ' Use the write end of STDIN to command the process
  ' Use the read ends STDOUT and STDERR to get response
  ' from the process.
  Dim hStdInRead As Long, hStdInWrite As Long
  Dim hStdOutRead As Long, hStdOutWrite As Long
  Dim hStdErrRead As Long, hStdErrWrite As Long

  CreatePipe hStdInRead, hStdInWrite, saAttr, 0
  CreatePipe hStdOutRead, hStdOutWrite, saAttr, 0
  CreatePipe hStdErrRead, hStdErrWrite, saAttr, 0

  ' Create startup information. Must specify
  ' STARTF_USESTDHANDLES and the handles
  sti.cb = LenB(sti)
  sti.lpReserved = 0
  sti.lpDesktop = 0
  sti.lpTitle = 0
  sti.dwX = 0
  sti.dwY = 0
  sti.dwXSize = 0
  sti.dwYSize = 0
  sti.dwXCountChars = 0
  sti.dwYCountChars = 0
  sti.dwFillAttribute = 0
  sti.dwFlags = STARTF_USESTDHANDLES
  sti.wShowWindow = 0
  sti.cbReserved2 = 0
  sti.lpReserved2 = 0

  sti.hStdInput = hStdInRead
  sti.hStdOutput = hStdOutWrite
  sti.hStdError = hStdErrWrite

  ' Create the process. If you want its window to be
  ' visible, do not use the CREATE_NO_WINDOW attribute
  ' Starts a new command interpreter.  Note that using
  ' command.com dos not work for unknown reasons
  CreateProcess 0, "cmd.exe", 0, 0, True, _
                 NORMAL_PRIORITY_CLASS + CREATE_NO_WINDOW, _
                 0, 0, sti, ps

  ' Give some command to the interpreter
  Dim s As String
  s = "echo Blah Blah Blah" & Chr(&HD) & Chr(&HA)
  Dim buffer As String
  buffer = Space$(256)
  Dim d As Long

  WriteFile hStdInWrite, s, Len(s), d, 0
  ' Give it some time to initialize and process the command
  Sleep 5000
  ' Read response
  ReadFile hStdOutRead, buffer, 255, d, 0
  buffer = Left$(buffer, d)
  ' Display the response
  MsgBox buffer, vbOKOnly, "Result"

  ' Instruct the interpreter to quit
  s = "exit" & Chr(&HD) & Chr(&HA)
  WriteFile hStdInWrite, s, Len(s), d, 0

  ' Close all handles
  CloseHandle ps.hProcess
  CloseHandle ps.hThread
  CloseHandle hStdInRead: CloseHandle hStdInWrite
  CloseHandle hStdOutRead: CloseHandle hStdOutWrite
  CloseHandle hStdErrRead: CloseHandle hStdErrWrite
End Sub



Visit also the pages of our other products and services:




Ing. Boleslav Vrany, Ph.D. - software developer, 2011
We vote against software patents.