Sending commands to cmd prompt in C#

后端 未结 3 1692
慢半拍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 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.

提交回复
热议问题