如何在C#中捕获Shell命令输出?

17

概要:

  • 查询远程机器上的注册表
  • 捕获输出以在应用程序中使用
  • 需要使用C#
  • 到目前为止,所有使用的方法都只能在本地机器上查询
  • 非常感谢任何帮助

完整问题:

我需要找到一种在C#中运行命令行命令并捕获其输出的方法。我知道如何在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`;

欢迎提供在C#中实现此操作的任何建议。到目前为止,我已经尝试使用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");


        }  

2
你尝试过RegistryKey.OpenRemoteBaseKey吗?http://msdn.microsoft.com/zh-cn/library/8zha3xws.aspx - Tim Robinson
@Tim Robinson:这会允许我枚举注册表键吗? - toosweetnitemare
@Daniel DiPaolo:感谢您的帖子。我在那个页面上的代码有一些小问题,但目前看来这是我最好的选择。 - toosweetnitemare
1
@user561621 它会给你一个 RegistryKey 对象,你可以在上面调用 GetSubKeyNames - Tim Robinson
3
老实说,如果@Tim Robinson的方法适用于您,那么他的选项是更好的解决方案。如果可能的话,最好保持代码的自包含性,而不依赖外部进程。 - Daniel DiPaolo
显示剩余4条评论
6个回答

30

你可能需要稍微调整一下,但这里有一些(略作修改自原始代码)重定向进程的标准输出和错误输出的代码:

        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小时的经验 :) )。 - toosweetnitemare
这是我的解决方案。我只需要将机器名称放入变量中即可 :). 非常感谢! - toosweetnitemare
2
请注意,这段代码应该适用于reg.exe,但是对于一个写入足够多数据到其标准错误流以填满默认缓冲区大小的程序来说,它会发生死锁。通用情况的正确解决方案是同时使用单独的线程读取两个输出流。 - Daniel Pryden

8
实际上,你可以在C#程序中以类似的限制条件运行几乎任何可以在命令行中运行的东西。有几种方法可以做到这一点,其中之一是通过异步进程命令,就像我在我的博客中展示的那样。你只需要以主动方式写入和读取命令行即可。从这里开始,只需确定您想要完成的内容以及如何使用命令行完成它。然后将其插入到程序中即可。
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; }
}

非常感谢您提供的代码。我需要花费几分钟时间将其应用到我的应用程序中。谢谢! - toosweetnitemare

5

这是我使用的一个类。它改编自我以前在博客文章中找到的代码,并进行了各种其他修改。

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;
        }
    }
}

2
我修改了你的代码,并创建了一个类库,我已经将其上传到GitHub。有什么问题吗? - Kenny Evitt
1
@KennyEvitt:对我来说没有问题。据我所知,这段代码是我的(当然除了那些来自Scott Hanselman博客评论者“SeemabK”的部分)。我写这段代码时,我已经不再为之工作的雇主,我认为他们也没有任何权利要求它。所以我认为你没问题。 - Daniel Pryden

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

0

0

如我在此留言此答案中提到的那样,我改编了那个答案中的代码并创建了一个库:

来自库的README:

Example usage:

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流来同时写入命令进程的输入流并读取其输出和错误流。为了避免"程序写入足够多的内容到标准错误流以填满默认缓冲区大小而导致死锁", 输出和错误流是使用不同的线程(即并行)读取的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接