Stefan Steiger
Stefan Steiger

Reputation: 82196

Controlling cmd.exe from Winforms

Question: I want to control cmd.exe from winforms.

I DO NOT mean every command in a single process, with startupinfo, and then stop.

I mean for example start the (My) SQL or GDB command prompt, send command, receive answer, send next command, receive next answer, stop SQL command prompt
exit process.

Basically I want to write a GUI on top of any console application.

I want to have the output from cmd.exe redirected to a textfield, and the input coming from another textfield (on press enter/OK button).

I don't find any samples for this. Is there a way?

Upvotes: 11

Views: 16752

Answers (4)

Stefan Steiger
Stefan Steiger

Reputation: 82196

This is the perfect answer:

using System;
using System.Windows.Forms;


namespace WindowsConsole
{


    public partial class Form1 : Form
    {
        System.Diagnostics.Process spdTerminal;
        System.IO.StreamWriter swInputStream;


        public delegate void fpTextBoxCallback_t(string strText);
        public fpTextBoxCallback_t fpTextBoxCallback;


        public Form1()
        {
            fpTextBoxCallback = new fpTextBoxCallback_t(AddTextToOutputTextBox);
            InitializeComponent();
        } // End Constructor


        public void AddTextToOutputTextBox(string strText)
        {
            this.txtOutput.AppendText(strText);
        } // End Sub AddTextToOutputTextBox


        private void btnQuit_Click(object sender, EventArgs e)
        {
            swInputStream.WriteLine("exit");
            swInputStream.Close();
            //spdTerminal.WaitForExit();
            spdTerminal.Close();
            spdTerminal.Dispose();
            Application.Exit();
        } // End Sub btnQuit_Click


        private void ConsoleOutputHandler(object sendingProcess, System.Diagnostics.DataReceivedEventArgs outLine)
        {
            if (!String.IsNullOrEmpty(outLine.Data))
            {
                //this.Invoke(new fpTextBoxCallback_t(AddTextToOutputTextBox), Environment.NewLine + outLine.Data);
                if(this.InvokeRequired)
                    this.Invoke(fpTextBoxCallback, Environment.NewLine + outLine.Data);
                else
                    fpTextBoxCallback(Environment.NewLine + outLine.Data);
            } // End if (!String.IsNullOrEmpty(outLine.Data))

        } // End Sub ConsoleOutputHandler


        private void btnExecute_Click(object sender, EventArgs e)
        {
            if (this.spdTerminal.HasExited)
            {
                MessageBox.Show("You idiot, you have terminated the process", "Error");
                return;
            } // End if (this.spdTerminal.HasExited)

            swInputStream.WriteLine(txtInputCommand.Text);
        } // End Sub btnExecute_Click


        public void ProcessExited(object sender, EventArgs e)
        {
            MessageBox.Show("You idiot, you terminated the process.", "PBKAC");
        } // End Sub ProcessExited


        private void Form1_Load(object sender, EventArgs e)
        {
            spdTerminal = new System.Diagnostics.Process();

            if(Environment.OSVersion.Platform == PlatformID.Unix)
                //spdTerminal.StartInfo.FileName = "/usr/bin/gnome-terminal";
                spdTerminal.StartInfo.FileName = "/bin/bash";
            else
                spdTerminal.StartInfo.FileName = "cmd.exe";

            AddTextToOutputTextBox("Using this terminal: " + spdTerminal.StartInfo.FileName);

            spdTerminal.StartInfo.UseShellExecute = false;
            spdTerminal.StartInfo.CreateNoWindow = true;
            spdTerminal.StartInfo.RedirectStandardInput = true;
            spdTerminal.StartInfo.RedirectStandardOutput = true;
            spdTerminal.StartInfo.RedirectStandardError = true;

            spdTerminal.EnableRaisingEvents = true;
            spdTerminal.Exited += new EventHandler(ProcessExited);
            spdTerminal.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler(ConsoleOutputHandler);
            spdTerminal.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(ConsoleOutputHandler);

            spdTerminal.Start();

            swInputStream = spdTerminal.StandardInput;
            spdTerminal.BeginOutputReadLine();
            spdTerminal.BeginErrorReadLine();
        } // End Sub Form1_Load


    } // End Class Form1


} // End Namespace WindowsConsole

Earlier on I tried with wile outputstream.Peek() != -1 but this gets crashed by a bug in the .NET framework Peek function, which doesn't timeout or throw an error if you read over the end of stream...

It works better in that it really catches all output, but it's far from perfect.

Public Class Form1


    ' That's our custom TextWriter class
    Private _writer As System.IO.TextWriter = Nothing

    Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        If p IsNot Nothing Then
            p.Close()
            p.Dispose()
            p = Nothing
        End If
    End Sub


    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        InitProcess()
        '' Instantiate the writer
        '_writer = New ConsoleRedirection.TextBoxStreamWriter(Me.txtConsole)
        '' Redirect the out Console stream
        'Console.SetOut(_writer)
        'Console.WriteLine("Now redirecting output to the text box1")
        'Console.WriteLine("Now redirecting output to the text box2")
    End Sub

    Protected p As Process
    Protected sw As System.IO.StreamWriter
    Protected sr As System.IO.StreamReader
    Protected err As System.IO.StreamReader


    Protected objWriter As System.IO.StreamWriter
    Protected objWriteNumeric As System.IO.StreamWriter

    Private Sub InitProcess()
        p = New Process()

        Dim psI As New ProcessStartInfo("cmd")
        psI.UseShellExecute = False
        psI.RedirectStandardInput = True
        psI.RedirectStandardOutput = True
        psI.RedirectStandardError = True
        psI.CreateNoWindow = True
        p.StartInfo = psI
        p.Start()
        sw = p.StandardInput
        sr = p.StandardOutput
        err = p.StandardError
        sw.AutoFlush = True


        objWriter = New System.IO.StreamWriter("c:\temp\logmy.txt", True, System.Text.Encoding.ASCII)
        objWriteNumeric = New System.IO.StreamWriter("c:\temp\lognum.txt", True, System.Text.Encoding.ASCII)



        Timer1.Enabled = True
        Timer1.Start()

    End Sub

    Private Sub start()

        If Me.txtinput.Text <> "" Then
            sw.WriteLine(Me.txtinput.Text)
        Else
            'execute default command
            sw.WriteLine("dir c:\music")
        End If
        sw.Flush()

        Timer2.Enabled = True
    End Sub


    Private Sub start_original()
        p = New Process()
        Dim sw As System.IO.StreamWriter
        Dim sr As System.IO.StreamReader
        Dim err As System.IO.StreamReader
        Dim psI As New ProcessStartInfo("cmd")
        psI.UseShellExecute = False
        psI.RedirectStandardInput = True
        psI.RedirectStandardOutput = True
        psI.RedirectStandardError = True
        psI.CreateNoWindow = True
        p.StartInfo = psI
        p.Start()
        sw = p.StandardInput
        sr = p.StandardOutput
        err = p.StandardError
        sw.AutoFlush = True

        Me.txtinput.Text = "help"

        If Me.txtinput.Text <> "" Then
            sw.WriteLine(Me.txtinput.Text)
        Else
            'execute default command
            sw.WriteLine("dir \")
        End If
        sw.Close()



        'Me.txtConsole.Text = sr.ReadToEnd()

        'txtinput.Text = sr.ReadToEnd()
        'txtinput.Text += err.ReadToEnd()
    End Sub



    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        start()
    End Sub




    Protected sb As String = ""
    Sub ReadOutputStreamIfAvailable()
        'cbEndOfStream.Checked = sr.EndOfStream

        While True
            objWriteNumeric.WriteLine(sr.Peek().ToString())
            objWriteNumeric.Flush()



            If sr.Peek = -1 Then
                Exit While
            End If


            Dim iCharAsNumber As Integer = sr.Read()

            Dim cNumberAsChar As Char = Nothing
            If Not iCharAsNumber = Nothing Then
                Try
                    cNumberAsChar = Chr(iCharAsNumber)
                Catch
                    Continue While
                    'MsgBox(Prompt:=xx.ToString, Title:="Error")
                    'Exit While
                End Try

            End If

            Dim strCharAsString As String = ""
            If Not cNumberAsChar = Nothing Then
                strCharAsString = cNumberAsChar.ToString()
            End If

            sb += strCharAsString
        End While


        If Not String.IsNullOrEmpty(sb) Then
            'MsgBox(sb)
            MsgBox(sb)
            Me.txtConsole.Text += sb
            'MsgBox(sb)
            sb = ""
        End If
    End Sub






    Protected er As String = ""
    Sub ReadErrorStreamIfAvailable()
        'cbEndOfStream.Checked = sr.EndOfStream

        While True
            objWriteNumeric.WriteLine(sr.Peek().ToString())
            objWriteNumeric.Flush()


            If err.Peek = -1 Then
                Exit While
            End If


            Dim iCharAsNumber As Integer = err.Read()

            Dim cNumberAsChar As Char = Nothing
            If Not iCharAsNumber = Nothing Then
                Try
                    cNumberAsChar = Chr(iCharAsNumber)
                Catch
                    Continue While
                    'MsgBox(Prompt:=xx.ToString, Title:="Error")
                    'Exit While
                End Try

            End If

            Dim strCharAsString As String = ""
            If Not cNumberAsChar = Nothing Then
                strCharAsString = cNumberAsChar.ToString()
            End If

            er += strCharAsString
        End While


        If Not String.IsNullOrEmpty(er) Then
            'MsgBox(sb)
            'MsgBox(er)
            Me.txtConsole.Text += er
            'MsgBox(sb)
            er = ""
        End If
    End Sub



    Protected Shared objOutputStreamLocker As Object = New Object

    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        Timer1.Enabled = False

        SyncLock objOutputStreamLocker
            ReadOutputStreamIfAvailable()
            'ReadErrorStreamIfAvailable()
        End SyncLock

        Timer1.Enabled = True
    End Sub


    Private Sub Timer2_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer2.Tick
        Try
            Timer2.Enabled = False
            sb = Chr(sr.Read()).ToString()
            ''
            'er = Chr(err.Read()).ToString()
            ''

            Timer1.Enabled = True
        Catch ex As Exception
            MsgBox("You have terminated the process", Title:="You idiot!")
        End Try
    End Sub


    ' http://www.c-sharpcorner.com/UploadFile/edwinlima/SystemDiagnosticProcess12052005035444AM/SystemDiagnosticProcess.aspx
    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

    End Sub


End Class

Upvotes: 0

Pieter
Pieter

Reputation: 2199

There is a nice example on CodeProject

Good luck!

-Edit: I think this is more like it, I created a simple form, 2 textboxes and three buttons. First textbox is for command entry, the second (multiline), displays the result.

The first button executes the command, the second button updates the result (because results are read async)

namespace WindowsFormsApplication2
{
    public partial class Form1 : Form
    {
        private static StringBuilder cmdOutput = null;
        Process cmdProcess;
        StreamWriter cmdStreamWriter;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            cmdOutput = new StringBuilder("");
            cmdProcess = new Process();

            cmdProcess.StartInfo.FileName = "cmd.exe";
            cmdProcess.StartInfo.UseShellExecute = false;
            cmdProcess.StartInfo.CreateNoWindow = true;
            cmdProcess.StartInfo.RedirectStandardOutput = true;

            cmdProcess.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
            cmdProcess.StartInfo.RedirectStandardInput = true;
            cmdProcess.Start();

            cmdStreamWriter = cmdProcess.StandardInput;
            cmdProcess.BeginOutputReadLine();
        }

        private void btnExecute_Click(object sender, EventArgs e)
        {
            cmdStreamWriter.WriteLine(textBox2.Text);
        }

        private void btnQuit_Click(object sender, EventArgs e)
        {
            cmdStreamWriter.Close();
            cmdProcess.WaitForExit();
            cmdProcess.Close();
        }

        private void btnShowOutput_Click(object sender, EventArgs e)
        {
            textBox1.Text = cmdOutput.ToString();
        }

        private static void SortOutputHandler(object sendingProcess,
            DataReceivedEventArgs outLine)
        {
            if (!String.IsNullOrEmpty(outLine.Data))
            {
                cmdOutput.Append(Environment.NewLine + outLine.Data);
            }
        }
    }
}

In the screenshot you can see that I entered the cd\ command to change directory and the next command executed in this directory (dir).alt text

Upvotes: 19

Albin Sunnanbo
Albin Sunnanbo

Reputation: 47048

You don't need to use cmd.exe for this, you can call the commands directly with Process.Start(). If you redirect StandardInput and StandardOutput you can control the process.

I have written an example as a response to another question.

Edit
I don't have a complete example for it, but you could listen to StandardOutput with the Process.OutputDataReceived event if you don't want to wait synchronously. There is an example on the MSDN page.

Upvotes: 1

codymanix
codymanix

Reputation: 29490

You do not need interop for this. The .NET Process class gives you all you need, simply redirect standard input stream and output stream and it is done. You can find lots of examples how to do this on the internet.

Upvotes: 2

Related Questions