Console Process Source Code

This code was downloaded from MOBZystems, Home of Tools. No license, use at will. And at your own risk!

using System;
using System.Collections.Generic;
using System.Text;
 
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;
 
namespace ShellRunner
{
  /*
   * This file contains the ConsoleProcess and related classes.
   */
 
  /// <summary>
  /// ConsoleProcess. Controller class for console applications.
  /// </summary>
  class ConsoleProcess
  {
    #region "Windows 32 API"
 
    private struct PROCESS_INFORMATION
    {
      public uint hProcess;
      public uint hThread;
      public uint dwProcessId;
      public uint dwThreadId;
    }
 
    // [StructLayout(LayoutKind.Sequential)]
    private struct PROCESS_BASIC_INFORMATION
    {
      public int ExitStatus;
      public int PebBaseAddress;
      public int AffinityMask;
      public int BasePriority;
      public uint UniqueProcessId;
      public uint InheritedFromUniqueProcessId;
    }
 
    private struct STARTUPINFO
    {
      public uint cb;
      public string lpReserved;
      public string lpDesktop;
      public string lpTitle;
      public uint dwX;
      public uint dwY;
      public uint dwXSize;
      public uint dwYSize;
      public uint dwXCountChars;
      public uint dwYCountChars;
      public uint dwFillAttribute;
      public uint dwFlags;
      public short wShowWindow;
      public short cbReserved2;
      public IntPtr lpReserved2;
      public uint hStdInput;
      public uint hStdOutput;
      public uint hStdError;
    }
 
    private struct SECURITY_ATTRIBUTES
    {
      public int length;
      public IntPtr lpSecurityDescriptor;
      public bool bInheritHandle;
    }
 
    [DllImport("kernel32.dll")]
    static extern bool CreateProcess(
      string lpApplicationName,
      string lpCommandLine,
      /* ref SECURITY_ATTRIBUTES */ IntPtr lpProcessAttributes,
      /* ref SECURITY_ATTRIBUTES */ IntPtr lpThreadAttributes,
      bool bInheritHandles,
      uint dwCreationFlags,
      IntPtr lpEnvironment,
      string lpCurrentDirectory,
      ref STARTUPINFO lpStartupInfo,
      out PROCESS_INFORMATION lpProcessInformation
    );
 
    [DllImport("kernel32.dll")]
    static extern bool FreeConsole();
 
    [DllImport("kernel32.dll")]
    static extern bool AttachConsole(uint dwProcessID);
 
    [DllImport("kernel32.dll")]
    static extern bool ResumeThread(uint hThread);
 
    [DllImport("kernel32.dll")]
    static extern bool CreatePipe(out uint hReadPipe, out uint hWritePipe, ref SECURITY_ATTRIBUTES sattr, uint size);
 
    [DllImport("kernel32.dll")]
    static extern bool SetHandleInformation(uint hHandle, uint mask, uint flags);
 
    [DllImport("kernel32.dll")]
    static extern bool CloseHandle(uint hHandle);
 
    [DllImport("kernel32.dll")]
    static extern bool ReadFile(uint handle, ref byte buffer, uint bufsize, out uint dwRead, IntPtr OverlappedRead);
 
    [DllImport("kernel32.dll")]
    static extern bool WriteFile(uint handle, ref byte buffer, uint bufsize, out uint dwWritten, IntPtr OverlappedRead);
 
    [DllImport("kernel32.dll")]
    static extern uint WaitForMultipleObjects(uint count, ref uint handles, bool waitAll, uint dwMilliSeconds);
 
    [DllImport("kernel32.dll")]
    static extern bool GetExitCodeProcess(uint hProcess, out int exitCode);
 
    [DllImport("kernel32.dll")]
    static extern bool TerminateProcess(uint hProcess, int exitCode);
 
    [DllImport("kernel32.dll")]
    static extern bool TerminateThread(uint hThread, int exitCode);
 
    [DllImport("ntdll.dll")]
    static extern int NtQueryInformationProcess(IntPtr hProcess, int processInformationClass /* 0 */,
      ref PROCESS_BASIC_INFORMATION processBasicInformation, uint processInformationLength, out uint returnLength);
 
    #endregion
 
    // The name of the command to execute
    private string commandFileName;
 
    // The default timeout (infinite) in milliseconds
    private uint timeout = 0xFFFFFFFF;
 
    // The results of reading standard output and standard error
    // Available during execution as well as afterwards
    private StringBuilder standardOutput;
    private StringBuilder standardError;
 
    // The currently running process
    // private Process runningProcess;
    private PROCESS_INFORMATION processInformation = new PROCESS_INFORMATION();
 
    // Handles to pipes
    private uint hChildStdinRd, hChildStdinWr, hChildStdoutRd, hChildStdoutWr, hChildStdErrorRd, hChildStdErrorWr;
 
    private string currentOutputLine;
    private string currentErrorLine;
 
    // Read buffer size
    private const int BUFSIZE = 1;
 
    /// <summary>
    /// Delegate for the OnOutputReceived and OnErrorReceived events
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="output"></param>
    public delegate void OutputReceivedHandler(object sender, string output);
 
    /// <summary>
    /// Raised when output is available on standard output
    /// </summary>
    public event OutputReceivedHandler OnOutputReceived;
    private void RaiseOnOutputReceived(string output)
    {
      if (this.OnOutputReceived != null)
        this.OnOutputReceived(this, output);
    }
 
    /// <summary>
    /// Raised when output is available on standard error
    /// </summary>
    public event OutputReceivedHandler OnErrorReceived;
    private void RaiseOnErrorReceived(string output)
    {
      if (this.OnErrorReceived != null)
        this.OnErrorReceived(this, output);
    }
 
    /// <summary>
    /// Property CommandFileName (string). The file name of the console executable.
    /// Must include a path if not in PATH.
    /// </summary>
    public string CommandFileName
    {
      get
      {
        return this.commandFileName;
      }
      set
      {
        this.commandFileName = value;
      }
    }
 
    /// <summary>
    /// Property Timeout (int). Get/Set the timeout in ms
    /// </summary>
    public uint Timeout
    {
      get
      {
        return this.timeout;
      }
      set
      {
        this.timeout = value;
      }
    }
 
    /// <summary>
    /// Execute a console command line in a hidden window. Returns the exit code of the process
    /// </summary>
    /// <param name="commandline">The command line to execute</param>
    /// <param name="timeoutMilliSeconds">A timeout in milliseconds of 0 is none</param>
    /// <param name="standardInput">The string to send to the command's standard input</param>
    /// <returns>The exit code of the process</returns>
    public int ExecuteCommand(string commandline, uint timeoutMilliSeconds, string standardInput, string currentDir)
    {
      if (this.processInformation.hProcess != 0)
        throw new ApplicationException("Cannot execute a command when another command is still executing");
 
      try
      {
        // Create secutiry attributes to be able to set pipes to NO INHERIT
        SECURITY_ATTRIBUTES saAttr;
 
        saAttr.length = 10; // sizeof(SECURITY_ATTRIBUTES); 
        saAttr.bInheritHandle = true;
        saAttr.lpSecurityDescriptor = IntPtr.Zero;
 
        // Create a pipe for the child process's STDIN. 
        if (!CreatePipe(out this.hChildStdinRd, out this.hChildStdinWr, ref saAttr, 0))
          throw new ApplicationException("Could not redirect stdin");
 
        // Ensure the write handle to the pipe for STDIN is not inherited. 
        SetHandleInformation(this.hChildStdinWr, 1 /* HANDLE_FLAG_INHERIT */, 0);
 
        // Create a pipe for the child process's STDOUT. 
        if (!CreatePipe(out this.hChildStdoutRd, out this.hChildStdoutWr, ref saAttr, 1))
          throw new ApplicationException("Could not redirect stdout");
 
        // Ensure the read handle to the pipe for STDOUT is not inherited.
        SetHandleInformation(hChildStdoutRd, 1 /* HANDLE_FLAG_INHERIT */, 1);
 
        // Create a pipe for the child process's STDERR. 
        if (!CreatePipe(out this.hChildStdErrorRd, out this.hChildStdErrorWr, ref saAttr, 0))
          throw new ApplicationException("Could not redirect stderr");
 
        // Ensure the write handle to the pipe for STDIN is not inherited. 
        SetHandleInformation(this.hChildStdErrorRd, 1 /* HANDLE_FLAG_INHERIT */, 0);
 
        // Now create the child process in a suspended state
        // with handles redirected to pipes
        STARTUPINFO si = new STARTUPINFO();
        si.cb = 17 * 4;
        si.hStdInput = this.hChildStdinRd;
        si.hStdOutput = this.hChildStdoutWr;
        si.hStdError = this.hChildStdErrorWr;
        si.dwFlags |= 0x100; /*  STARTF_USESTDHANDLES; */
        si.wShowWindow = 0;
 
        this.processInformation = new PROCESS_INFORMATION();
 
        string effectiveCommandLine = "\"" + CommandFileName + "\"";
        if (commandline != null)
          effectiveCommandLine += " " + commandline;
 
        if (!CreateProcess(
          CommandFileName,
          effectiveCommandLine,
          IntPtr.Zero,  /* process attributes */
          IntPtr.Zero,  /* thread attributes */
          true,         /* inherit handles */
          4 /* suspended*/ | 0x08000000 /* CREATE_NO_WINDOW */,
          IntPtr.Zero,  /* environment */
          currentDir,   /* current dir */
          ref si,       /* Startup-info */
          out this.processInformation)
         )
          throw new ApplicationException("Could not create process");
 
        // Close the write end of the pipes before reading from the 
        // read end of the pipes. 
 
        if (!CloseHandle(hChildStdoutWr))
          throw new ApplicationException("Could not close stdout");
 
        if (!CloseHandle(hChildStdErrorWr))
          throw new ApplicationException("Could not close stderr");
 
        this.standardOutput = new StringBuilder();
        this.standardError = new StringBuilder();
 
        this.currentOutputLine = "";
        this.currentErrorLine = "";
 
        // Start a new thread to write to standard input if necessary
 
        Thread standardInputWriter = null;
        if (standardInput != null)
        {
          // Print to standard input:
          standardInputWriter = new Thread(new ParameterizedThreadStart(WriteStandardInput));
          standardInputWriter.Start(standardInput);
        }
 
        // Start two new threads to read standard output and standard error on the process
 
        // Create the 'standard output reader' thread
        Thread standardOutputReader = new Thread(new ThreadStart(ReadStandardOutput));
        standardOutputReader.Start();
 
        // Create the 'standard error reader' thread
        Thread standardErrorReader = new Thread(new ThreadStart(ReadStandardError));
        standardErrorReader.Start();
 
        // Resume the thread, i.e. start the process
        ResumeThread(this.processInformation.hThread);
 
        // Wait for the process to end
        switch (WaitForMultipleObjects(1, ref this.processInformation.hProcess, true, timeoutMilliSeconds))
        {
          case 0:
            // The process ended normally, or it was Terminate()d
            break;
          case 0x102: // There was a timeout
            TerminateThread(this.processInformation.hThread, -1);
            TerminateProcess(this.processInformation.hProcess, -1);
            break;
          default:
            // This is unexpected!
            break;
        }
 
        // Wait for the two threads to rejoin:
        standardOutputReader.Join();
        standardErrorReader.Join();
 
        // Kill the standard input writer at the end of the process
        if (standardInputWriter != null)
        {
          standardInputWriter.Abort();
        }
 
        // Return exit code of the process
        int exitCode;
 
        if (!GetExitCodeProcess(this.processInformation.hProcess, out exitCode))
          throw new ApplicationException("Could not retrieve exit code");
 
        return exitCode;
      }
      finally
      {
        // Clean up when exception happens
        if (this.processInformation.hProcess != 0)
          CloseHandle(this.processInformation.hProcess);
        if (this.processInformation.hThread != 0)
          CloseHandle(this.processInformation.hThread);
 
        // Reset the running process
        this.processInformation = new PROCESS_INFORMATION();
      }
    }
 
    public int ExecuteCommand(string commandline, uint timeoutMilliSeconds, string standardInput)
    {
      return ExecuteCommand(commandline, timeoutMilliSeconds, standardInput, null);
    }
 
    /// <summary>
    /// Execute a console command in a hidden window. Returns the exit code of the process
    /// </summary>
    /// <param name="commandline">The command line to ecexute</param>
    /// <param name="timeoutMilliSeconds">A timeout in milliseconds.</param>
    /// <returns>The exit code of the process</returns>
    public int ExecuteCommand(string commandline, uint timeoutMilliSeconds)
    {
      return ExecuteCommand(commandline, timeoutMilliSeconds, null);
    }
 
    /// <summary>
    /// Execute a console command with a default timeout
    /// </summary>
    /// <param name="commandline"></param>
    /// <returns></returns>
    public int ExecuteCommand(string commandline)
    {
      return ExecuteCommand(commandline, this.timeout);
    }
 
    /// <summary>
    /// Force the command to end with an exit code
    /// </summary>
    /// <param name="exitCode"></param>
    public void Terminate(int exitCode)
    {
      if (this.processInformation.hProcess == 0)
        throw new ApplicationException("Process is not running");
 
      // Terminate the process TREE here
      TerminateProcessTree((IntPtr)this.processInformation.hProcess, this.processInformation.dwProcessId, exitCode);
    }
 
    /// <summary>
    /// Terminate a process tree
    /// </summary>
    /// <param name="hProcess">The handle of the process</param>
    /// <param name="processID">The ID of the process</param>
    /// <param name="exitCode">The exit code of the process</param>
    public void TerminateProcessTree(IntPtr hProcess, uint processID, int exitCode)
    {
      // Retrieve all processes on the system
      Process[] processes = Process.GetProcesses();
      foreach (Process p in processes)
      {
        // Get some basic information about the process
        PROCESS_BASIC_INFORMATION pbi = new PROCESS_BASIC_INFORMATION();
        try
        {
          uint bytesWritten;
          NtQueryInformationProcess(p.Handle, 0, ref pbi, (uint)Marshal.SizeOf(pbi), out bytesWritten); // == 0 is OK
 
          // Is it a child process of the process we're trying to terminate?
          if (pbi.InheritedFromUniqueProcessId == processID)
            // The terminate the child process and its child processes
            TerminateProcessTree(p.Handle, pbi.UniqueProcessId, exitCode);
        }
        catch (Exception /* ex */)
        {
          // Ignore, most likely 'Access Denied'
        }
      }
 
      // Finally, termine the process itself:
      TerminateProcess((uint)hProcess, exitCode);
    }
 
    /// <summary>
    /// Property StandardOutput (string).
    /// Returns the result of the command execution
    /// </summary>
    public string StandardOutput
    {
      get
      {
        lock (this)
        {
          return this.standardOutput.ToString();
        }
      }
    }
 
    /// <summary>
    /// Property StandardError (string).
    /// Returns the error result of the command execution
    /// </summary>
    public string StandardError
    {
      get
      {
        lock (this)
        {
          return this.standardError.ToString();
        }
      }
    }
 
    /// <summary>
    /// Write a string to standard input of the process.
    /// This method is called on its own thread
    /// </summary>
    /// <param name="input">The string to write to standard input</param>
    private void WriteStandardInput(object input)
    {
      string standardInput = (string)input;
      byte[] buf = Encoding.ASCII.GetBytes(standardInput);
      uint dwWritten;
      WriteFile(hChildStdinWr, ref buf[0], (uint)buf.Length, out dwWritten, IntPtr.Zero);
      CloseHandle(hChildStdinWr);
    }
 
    /// <summary>
    /// Read standard output of the process.
    /// This method is called on its own thread
    /// </summary>
    private void ReadStandardOutput()
    {
      uint dwRead;
      byte[] chBuf = new byte[BUFSIZE];
 
      for (; ; )
      {
        if (!ReadFile(hChildStdoutRd, ref chBuf[0], BUFSIZE, out dwRead, IntPtr.Zero))
          break;
 
        if (dwRead != 0)
        {
          string output = Encoding.ASCII.GetString(chBuf, 0, (int)dwRead);
 
          this.currentOutputLine += output;
 
          lock (this)
          {
            int p;
            while ((p = this.currentOutputLine.IndexOf("\r\n")) >= 0)
            {
              string line = this.currentOutputLine.Substring(0, p);
              if (line.Length > 0)
                this.RaiseOnOutputReceived(line);
              if (p + 2 >= this.currentOutputLine.Length)
              {
                this.currentOutputLine = "";
                break;
              }
              else
                this.currentOutputLine = this.currentOutputLine.Substring(p + 2);
            }
          }
 
          this.standardOutput.Append(output);
        }
      }
    }
 
    /// <summary>
    /// Read standard error output of the process.
    /// This method is called on its own thread
    /// </summary>
    private void ReadStandardError()
    {
      uint dwRead;
      byte[] chBuf = new byte[BUFSIZE];
 
      for (; ; )
      {
        if (!ReadFile(hChildStdErrorRd, ref chBuf[0], BUFSIZE, out dwRead, IntPtr.Zero))
          break;
 
        if (dwRead != 0)
        {
          string error = System.Text.Encoding.ASCII.GetString(chBuf, 0, (int)dwRead);
 
          this.currentErrorLine += error;
 
          lock (this)
          {
            int p;
            while ((p = this.currentErrorLine.IndexOf("\r\n")) >= 0)
            {
              string line = this.currentErrorLine.Substring(0, p);
              if (line.Length > 0)
                this.RaiseOnErrorReceived(line);
              if (p + 2 >= this.currentErrorLine.Length)
              {
                this.currentErrorLine = "";
                break;
              }
              else
                this.currentErrorLine = this.currentErrorLine.Substring(p + 2);
            }
          }
 
          this.standardError.Append(error);
        }
      }
    }
  }
}