【问题标题】:How to capture Shell command output in C#?如何在 C# 中捕获 Shell 命令输出?
【发布时间】:2011-01-03 19:04:55
【问题描述】:

总结:

  • 查询远程机器上的注册表
  • 捕获输出以在应用程序中使用
  • 需要在 csharp 中
  • 目前使用的所有方法都只能在本机查询
  • 非常感谢任何希望

完整问题:

我需要找到一种在 csharp 中运行命令行命令并捕获其输出的方法。我知道如何在 Perl 中执行此操作,下面是我将在 Perl 中使用的代码。

#machine to check
my $pc = $_[0];
#create location of registry query
my $machine = "\\\\".$pc."\\HKEY_USERS";
#run registry query
my @regQuery= `REG QUERY $machine`;

欢迎任何有关如何在 csharp 中执行此操作的建议。到目前为止,我尝试使用 RegistryKey OurKey = Registry.Users 方法,效果很好,但我无法在远程机器上查询注册表。

如果您需要更多信息,请告诉我。

解决方案:(感谢@Robaticus)

private void reg(string host)
        {

            string build = "QUERY \\\\" + host + "\\HKEY_USERS";
            string parms = @build;
            string output = "";
            string error = string.Empty;

            ProcessStartInfo psi = new ProcessStartInfo("reg.exe", parms);

            psi.RedirectStandardOutput = true;
            psi.RedirectStandardError = true;
            psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
            psi.UseShellExecute = false;
            System.Diagnostics.Process reg;
            reg = System.Diagnostics.Process.Start(psi);
            using (System.IO.StreamReader myOutput = reg.StandardOutput)
            {
                output = myOutput.ReadToEnd();
            }
            using (System.IO.StreamReader myError = reg.StandardError)
            {
                error = myError.ReadToEnd();

            }
            Output.AppendText(output + "\n");


        }  

【问题讨论】:

标签: c# .net registry capture


【解决方案1】:

您可能需要稍微调整一下,但这里有一些(对原始版本稍作修改)代码,用于重定向进程的 stdout 和 stderr:

        string parms = @"QUERY \\machine\HKEY_USERS";
        string output = "";
        string error = string.Empty;

        ProcessStartInfo psi = new ProcessStartInfo("reg.exe", parms);

        psi.RedirectStandardOutput = true;
        psi.RedirectStandardError = true;
        psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
        psi.UseShellExecute = false;
        System.Diagnostics.Process reg;
        reg = System.Diagnostics.Process.Start(psi);
        using (System.IO.StreamReader myOutput = reg.StandardOutput)
        {
            output = myOutput.ReadToEnd();
        }
        using(System.IO.StreamReader myError = reg.StandardError)
        {
            error = myError.ReadToEnd();

        }

【讨论】:

  • 感谢您的代码。我只是尝试实现并执行它。我在从中获取输出时遇到问题。目前我正在使用 Output.AppendText(output + "\n");打印输出。它是否正确?我是 csharp 新手(总共大约 3 小时的经验 :))
  • 这是我的解决方案。我只需要实际将机器名称扔到变量中:)。非常感谢!
  • 请注意,此代码应该适用于 reg.exe,但对于写入其标准错误流以填满默认缓冲区大小的程序会出现死锁.一般情况下的正确解决方案是使用单独的线程同时读取两个输出流。
【解决方案2】:

几乎可以在命令行中运行的任何东西都可以在具有类似约束的 C# 程序中运行。有几种方法可以做到这一点,一种是通过我在blog 中显示的异步进程命令。您只需以主动方式写入和读取命令行。从这里,只需弄清楚您想要完成什么以及如何使用命令行来完成它。然后插入到程序中

class Program
{
static void Main(string[] args)
{
LaunchCommandAsProcess cmd = new LaunchCommandAsProcess();
cmd.OutputReceived += new LaunchCommandAsProcess.OutputEventHandler(launch_OutputReceived);
cmd.SendCommand("help");
cmd.SendCommand("ipconfig");
cmd.SyncClose();
}
/// Outputs normal and error output from the command prompt.
static void launch_OutputReceived(object sendingProcess, EventArgsForCommand e)
{
Console.WriteLine(e.OutputData);
}
}

如您所见,您只需实例化类,处理输出事件,然后像在命令提示符中键入一样开始编写命令。

它是这样工作的:

public class LaunchCommandAsProcess
{
public delegate void OutputEventHandler(object sendingProcess, EventArgsForCommand e);
public event OutputEventHandler OutputReceived;
private StreamWriter stdIn;
private Process p;
public void SendCommand(string command)
{
stdIn.WriteLine(command);
}
public LaunchCommandAsProcess()
{
p = new Process();
p.StartInfo.FileName = @"C:\Windows\System32\cmd.exe";
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
p.Start();

stdIn = p.StandardInput;
p.OutputDataReceived += Process_OutputDataReceived;
p.ErrorDataReceived += Process_OutputDataReceived;
p.BeginOutputReadLine();
p.BeginErrorReadLine();

}
///
/// Raises events when output data has been received. Includes normal and error output.
/// 

/// /// private void Process_OutputDataReceived(object sendingProcess, DataReceivedEventArgs outLine)
{
if (outLine.Data == null)
return;
else
{
if (OutputReceived != null)
{
EventArgsForCommand e = new EventArgsForCommand();
e.OutputData = outLine.Data;
OutputReceived(this, e);
}
}
}
///
/// Synchronously closes the command promp.
/// 

public void SyncClose()
{
stdIn.WriteLine("exit");
p.WaitForExit();
p.Close();
}
///
/// Asynchronously closees the command prompt.
/// 

public void AsyncClose()
{
stdIn.WriteLine("exit");
p.Close();
}
}
public class EventArgsForCommand : EventArgs
{
public string OutputData { get; internal set; }
}

【讨论】:

  • 非常感谢您提供的代码。我需要几分钟的时间来尝试将其应用到我的应用程序中。
【解决方案3】:

这是我使用的一个类。它改编自我不久前在blog posting 中找到的代码,但进行了各种其他修改。

using System;
using System.Diagnostics;
using System.Text;
using System.Threading;

namespace SonomaTechnologyInc {
    /// <summary>
    /// Utility class for working with command-line programs.
    /// </summary>
    public class Subprocess {  
        private Subprocess() { }

        /// <summary>
        /// Executes a command-line program, specifying a maximum time to wait
        /// for it to complete.
        /// </summary>
        /// <param name="command">
        /// The path to the program executable.
        /// </param>
        /// <param name="args">
        /// The command-line arguments for the program.
        /// </param>
        /// <param name="timeout">
        /// The maximum time to wait for the subprocess to complete, in milliseconds.
        /// </param>
        /// <returns>
        /// A <see cref="SubprocessResult"/> containing the results of
        /// running the program.
        /// </returns>
        public static SubprocessResult RunProgram(string command, string args, int timeout) {
            bool timedOut = false;
            ProcessStartInfo pinfo = new ProcessStartInfo(command);
            pinfo.Arguments = args;
            pinfo.UseShellExecute = false;
            pinfo.CreateNoWindow = true;
            //pinfo.WorkingDirectory = ?
            pinfo.RedirectStandardOutput = true;
            pinfo.RedirectStandardError = true;
            Process subprocess = Process.Start(pinfo);

            ProcessStream processStream = new ProcessStream();
            try {
                processStream.Read(subprocess);

                subprocess.WaitForExit(timeout);
                processStream.Stop();
                if(!subprocess.HasExited) {
                    // OK, we waited until the timeout but it still didn't exit; just kill the process now
                    timedOut = true;
                    try {
                        subprocess.Kill();
                        processStream.Stop();
                    } catch { }
                    subprocess.WaitForExit();
                }
            } catch(Exception ex) {
                subprocess.Kill();
                processStream.Stop();
                throw ex;
            } finally {
                processStream.Stop();
            }

            TimeSpan duration = subprocess.ExitTime - subprocess.StartTime;
            float executionTime = (float) duration.TotalSeconds;
            SubprocessResult result = new SubprocessResult(
                executionTime, 
                processStream.StandardOutput.Trim(), 
                processStream.StandardError.Trim(), 
                subprocess.ExitCode, 
                timedOut);
            return result;
        }
    }

    /// <summary>
    /// Represents the result of executing a command-line program.
    /// </summary>
    public class SubprocessResult {
        readonly float executionTime;
        readonly string stdout;
        readonly string stderr;
        readonly int exitCode;
        readonly bool timedOut;

        internal SubprocessResult(float executionTime, string stdout, string stderr, int exitCode, bool timedOut) {
            this.executionTime = executionTime;
            this.stdout = stdout;
            this.stderr = stderr;
            this.exitCode = exitCode;
            this.timedOut = timedOut;
        }

        /// <summary>
        /// Gets the total wall time that the subprocess took, in seconds.
        /// </summary>
        public float ExecutionTime {
            get { return executionTime; }
        }

        /// <summary>
        /// Gets the output that the subprocess wrote to its standard output stream.
        /// </summary>
        public string Stdout {
            get { return stdout; }
        }

        /// <summary>
        /// Gets the output that the subprocess wrote to its standard error stream.
        /// </summary>
        public string Stderr {
            get { return stderr; }
        }

        /// <summary>
        /// Gets the subprocess's exit code.
        /// </summary>
        public int ExitCode {
            get { return exitCode; }
        }

        /// <summary>
        /// Gets a flag indicating whether the subprocess was aborted because it
        /// timed out.
        /// </summary>
        public bool TimedOut {
            get { return timedOut; }
        }
    }

    internal class ProcessStream {
        /*
         * Class to get process stdout/stderr streams
         * Author: SeemabK (seemabk@yahoo.com)
         * Usage:
            //create ProcessStream
            ProcessStream myProcessStream = new ProcessStream();
            //create and populate Process as needed
            Process myProcess = new Process();
            myProcess.StartInfo.FileName = "myexec.exe";
            myProcess.StartInfo.Arguments = "-myargs";

            //redirect stdout and/or stderr
            myProcess.StartInfo.UseShellExecute = false;
            myProcess.StartInfo.RedirectStandardOutput = true;
            myProcess.StartInfo.RedirectStandardError = true;

            //start Process
            myProcess.Start();
            //connect to ProcessStream
            myProcessStream.Read(ref myProcess);
            //wait for Process to end
            myProcess.WaitForExit();

            //get the captured output :)
            string output = myProcessStream.StandardOutput;
            string error = myProcessStream.StandardError;
         */
        private Thread StandardOutputReader;
        private Thread StandardErrorReader;
        private Process RunProcess;
        private string _StandardOutput = "";
        private string _StandardError = "";

        public string StandardOutput {
            get { return _StandardOutput; }
        }
        public string StandardError {
            get { return _StandardError; }
        }

        public ProcessStream() {
            Init();
        }

        public void Read(Process process) {
            try {
                Init();
                RunProcess = process;

                if(RunProcess.StartInfo.RedirectStandardOutput) {
                    StandardOutputReader = new Thread(new ThreadStart(ReadStandardOutput));
                    StandardOutputReader.Start();
                }
                if(RunProcess.StartInfo.RedirectStandardError) {
                    StandardErrorReader = new Thread(new ThreadStart(ReadStandardError));
                    StandardErrorReader.Start();
                }

                int TIMEOUT = 1 * 60 * 1000; // one minute
                if(StandardOutputReader != null)
                    StandardOutputReader.Join(TIMEOUT);
                if(StandardErrorReader != null)
                    StandardErrorReader.Join(TIMEOUT);

            } catch { }
        }

        private void ReadStandardOutput() {
            if(RunProcess == null) return;
            try {
                StringBuilder sb = new StringBuilder();
                string line = null;
                while((line = RunProcess.StandardOutput.ReadLine()) != null) {
                    sb.Append(line);
                    sb.Append(Environment.NewLine);
                }
                _StandardOutput = sb.ToString();
            } catch { }
        }

        private void ReadStandardError() {
            if(RunProcess == null) return;
            try {
                StringBuilder sb = new StringBuilder();
                string line = null;
                while((line = RunProcess.StandardError.ReadLine()) != null) {
                    sb.Append(line);
                    sb.Append(Environment.NewLine);
                }
                _StandardError = sb.ToString();
            } catch { }
        }

        private void Init() {
            _StandardError = "";
            _StandardOutput = "";
            RunProcess = null;
            Stop();
        }

        public void Stop() {
            try { if(StandardOutputReader != null) StandardOutputReader.Abort(); } catch { }
            try { if(StandardErrorReader != null) StandardErrorReader.Abort(); } catch { }
            StandardOutputReader = null;
            StandardErrorReader = null;
        }
    }
}

【讨论】:

  • 我修改了你的代码并创建了一个 I've put up on GitHub 的类库。有什么问题吗?
  • @KennyEvitt:我没问题。据我所知,代码是我的(当然,来自 Scott Hanselman 博客上的评论者“SeemabK”的部分除外)。当我写这篇文章时,我不再为我工作的雇主工作,但我不相信他们也对此有任何要求。所以我觉得你很好。
【解决方案4】:

这并没有回答问题,但是Registry.OpenRemoteBaseKey 方法连接到另一台机器的注册表,方式与REG 命令相同。调用RegistryKey.GetSubKeyNames得到与REG QUERY相同的输出。

【讨论】:

    【解决方案5】:

    您可以使用 System.Diagnostics.Process 类捕获 StandardOutput 和 StandardError。

    http://msdn.microsoft.com/en-us/library/system.diagnostics.process.aspx

    请务必阅读文档的备注部分。必须正确设置流程类的某些属性才能使用 StandardOutput(例如,必须将 UseShellExecute 设置为 false)。

    【讨论】:

      【解决方案6】:

      正如我在 this answer 上的 this comment 中提到的,我修改了该答案中的代码并创建了一个库:

      来自库自述文件:

      示例用法:

      CommandLineProgramProcessResult result =
          CommandLineProgramProcess.RunProgram(
              @"C:\Program Files (x86)\SomeFolder\SomeProgram.exe",             // Path of executable program
              @"C:\Program Files (x86)\SomeFolder\",                            // Path of working directory
              String.Format(@"""{0}""", filePathThatNeedsToBeQuotedArgument),   // Command line arguments
              10 * 60 * 1000);                                                  // Timeout, in milliseconds
              
      string standardError = result.StandardError;
      string standardOutput = result.StandardOutput;
      int exitCode = result.ExitCode;
      

      所有的库代码都在这个文件中:

      基本上,(唯一的)库类使用 .NET 流来写入命令进程的输入流并读取其输出和错误流。使用单独的线程(即并行)读取输出和错误流以避免"a deadlock for a program that writes enough to its standard error stream to fill up the default buffer size"

      【讨论】: