Sending commands to cmd prompt in C#

后端 未结 3 1690
慢半拍i
慢半拍i 2020-12-06 02:31

For one of my implementations I am working on a tool that is supposed to send/retrieve commands/results to/from the cmd window. Everything works fine but the Use case below

相关标签:
3条回答
  • 2020-12-06 02:55

    There's not enough code in your question to figure out exactly what your application is hanging on. There are some things in your code which look odd. For example, why are you starting your own error and output read loops instead of using the ones built into the Process class? Like this:

    var shellProcess = System.Diagnostics.Process.Start(info);
    shellProcess.EnableRaisingEvents = true;
    shellProcess.Exited += ProcessExited;
    
    shellProcess.OutputDataReceived += ShellProcess_OutputDataReceived;
    shellProcess.ErrorDataReceived  += ShellProcess_ErrorDataReceived;
    shellProcess.BeginOutputReadLine();
    shellProcess.BeginErrorReadLine();
    
    void ShellProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
    {
        // Do Something
    }
    
    void ShellProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
    {
        // Do Something
    }
    

    Since your error and output async events are not firing, it leads me to believe that there might be a lifetime issue with the shellProcess. If you post more of your code, we can give better guidance.

    0 讨论(0)
  • 2020-12-06 03:16

    I can't see all your code, but you can easily use Steam objects to write/send commands to the CMD Window created by you. e.g.:

    StreamWriter inputStream = shellProcess.StandardInput;
    //send command to cmd prompt and wait for command to execute with thread sleep
    inputStream.WriteLine("echo "CMD just received input");
    inputStream.Flush();
    

    In the above example for instance, Command prompt will receive the echo command just like it was entered in the window. To show the output you will have to create StreamReader object and assign it to the process's StandardOutput.

    0 讨论(0)
  • 2020-12-06 03:18

    You cannot send commands to a shell this way. The string in info.Arguments is the arguments provided to the program on the command line. If you want the cmd.exe shell to execute a series of command and then quit you will have to provide the /c argument. If you have multiple commands that you want it to perform you will either have to put the commands in a batch file and execute that or enclose them in quotes and separate them with &&, i.e. info.Arguments = @"/c ""cd \ && dir""";. Your other issue with never returning is that cmd.exe opens in interactive mode by default when it is executed without any, or proper, arguments. The /c option tells cmd.exe to execute the relevant commands and then quit.

    Additionally, interpreters like python and perl sometimes have weird behaviors when launched directly from ProcessStartInfo. If info.Arguments = @"""MyPerlProgram.pl"""; with perl.exe doesn't work, you may find it necessary to launch them inside cmd.exe to get normal behavior out of them, i.e. info.Arguments = @"/c ""perl.exe ""MyPerlProgram.pl""""";.

    See Cmd and ProcessStartInfo.Arguments Property.

    To answer your Edit 3 problem, you're probably not correctly hooking into the outputs. Instead of trying to hook the StreamReader's BaseStream, hook the OutputDataReceived event with this.shellProcess.OutputDataReceived += ProcessOutputHandler; before you call Start where ProcessOutputHandler has a signature like public static void ProcessOutputHandler(object sendingProcess, DataReceivedEventArgs outLine). Immediately after calling Start, call this.shellProcess.BeginOutputReadLine();. The process is similar for the error ouput as well. See Process.BeginOutputReadLine Method and Process.BeginErrorReadLine Method for more details.

    If you still have a problem, what do you get if you just try process.StartInfo.Arguments = @"/c ""python.exe -c ""import sys; print 'Test.';""""";?

    Also, the code below demonstrates most of the necessary concepts for shell communication:

    public static void Main()
    {
        using (Process process = new Process())
        {
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardError = true;
            process.StartInfo.WorkingDirectory = @"C:\";
            process.StartInfo.FileName = Path.Combine(Environment.SystemDirectory, "cmd.exe");
    
            // Redirects the standard input so that commands can be sent to the shell.
            process.StartInfo.RedirectStandardInput = true;
            // Runs the specified command and exits the shell immediately.
            //process.StartInfo.Arguments = @"/c ""dir""";
    
            process.OutputDataReceived += ProcessOutputDataHandler;
            process.ErrorDataReceived += ProcessErrorDataHandler;
    
            process.Start();
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();
    
            // Send a directory command and an exit command to the shell
            process.StandardInput.WriteLine("dir");
            process.StandardInput.WriteLine("exit");
    
            process.WaitForExit();
        }
    }
    
    public static void ProcessOutputDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
    {
        Console.WriteLine(outLine.Data);
    }
    
    public static void ProcessErrorDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
    {
        Console.WriteLine(outLine.Data);
    }
    

    You may have threading issues causing your problems. I've done some further work with this and was able to get a textbox on a form to update with the following code:

    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Timers;
    
    namespace DummyFormsApplication
    {
        class ProcessLauncher : IDisposable
        {
            private Form1 form;
            private Process process;
            private bool running;
    
            public bool InteractiveMode
            {
                get;
                private set;
            }
    
            public ProcessLauncher(Form1 form)
            {
                this.form = form;
    
                process = new Process();
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;
                process.StartInfo.WorkingDirectory = @"C:\";
                process.StartInfo.FileName = Path.Combine(Environment.SystemDirectory, "cmd.exe");
    
                // Redirects the standard input so that commands can be sent to the shell.
                process.StartInfo.RedirectStandardInput = true;
    
                process.OutputDataReceived +=new DataReceivedEventHandler(process_OutputDataReceived);
                process.ErrorDataReceived += new DataReceivedEventHandler(process_ErrorDataReceived);
                process.Exited += new EventHandler(process_Exited);
            }
    
            public void Start()
            {
                if (running == false)
                {
                    running = true;
                    InteractiveMode = true;
    
                    // Runs the specified command and exits the shell immediately upon completion.
                    process.StartInfo.Arguments = @"/c ""C:\python27\python.exe -i""";
    
                    process.Start();
    
                    process.BeginOutputReadLine();
                    process.BeginErrorReadLine();
                }
            }
    
            public void Start(string scriptFileName)
            {
                if (running == false)
                {
                    running = true;
                    InteractiveMode = false;
    
                    // Runs the specified command and exits the shell immediately upon completion.
                    process.StartInfo.Arguments = string.Format(@"/c ""C:\python27\python.exe ""{0}""""", scriptFileName);
                }
            }
    
            public void Abort()
            {
                process.Kill();
            }
    
            public void SendInput(string input)
            {
                process.StandardInput.Write(input);
                process.StandardInput.Flush();
            }
    
            private void process_OutputDataReceived(object sendingProcess, DataReceivedEventArgs outLine)
            {
                if (outLine.Data != null)
                {
                    form.Invoke(form.appendConsoleTextDelegate, new object[] { outLine.Data });
                }
            }
    
            private void process_ErrorDataReceived(object sendingProcess, DataReceivedEventArgs outLine)
            {
                if (outLine.Data != null)
                {
                    form.Invoke(form.appendConsoleTextDelegate, new object[] { outLine.Data });
                }
            }
    
            private void process_Exited(object sender, EventArgs e)
            {
                running = false;
            }
    
            public void Dispose()
            {
                if (process != null)
                {
                    process.Dispose();
                }
            }
        }
    }
    

    I created a form and added a textbox and the following code in the form:

        public delegate void AppendConsoleText(string text);
        public AppendConsoleText appendConsoleTextDelegate;
    
        private void Form1_Load(object sender, EventArgs e)
        {
            appendConsoleTextDelegate = new AppendConsoleText(textBox1_AppendConsoleText);
            using (ProcessLauncher launcher = new ProcessLauncher(this))
            {
                launcher.Start();
    
                launcher.SendInput("import sys;\n");
                launcher.SendInput("print \"Test.\";\n");
                launcher.SendInput("exit()\n");
            }
        }
    
        private void textBox1_AppendConsoleText(string text)
        {
            textBox1.AppendText(string.Format("{0}\r\n", text));
        }
    

    One thing to note is that if the Form1_Load event doesn't complete, Invoke will hang until it does. If you have long-running code in an event you'll either need to invoke asynchronously using BeginInvoke, or periodically call DoEvents in your long-running code.

    EDIT

    Per your comment, I've modified the code to work with interactive submissions. There is, however, a problem. The python prompt (>>>) is provided on the StandardError output and it does not echo the StandardInput. It also does not terminate the line. This makes detecting a prompt difficult and causes some out of order output of the prompt characters due to the process_ErrorDataReceived not firing until either the process ends or a line end is seen.

    0 讨论(0)
提交回复
热议问题