Is there a good way to stream the results from an external process into a Visual Studio output pane?

只谈情不闲聊 提交于 2019-12-05 14:47:36

Apparently you have to use the Process.BeginOutputReadLine() method. An example can be found here: System.Diagnostics.Process.BeginOutputReadLine.

Updated RunDiagnostics, mostly utilizing J. Tihon's answer and some of the_drow's answer:

///--------------------------------------------------------------------------------  
/// <summary>This method launches the diagnostics to review the solution.</summary>  
///--------------------------------------------------------------------------------  
protected void RunDiagnostics()  
{  
    try  
    {   
        // set up diagnostics process  
        string solutionDir = System.IO.Path.GetDirectoryName(_applicationObject.Solution.FullName);  
        System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo(@"MyDiagnostics.exe", solutionDir);  
        procStartInfo.RedirectStandardOutput = true;  
        procStartInfo.UseShellExecute = false;  
        procStartInfo.CreateNoWindow = true;  
        System.Diagnostics.Process proc = new System.Diagnostics.Process();  
        proc.StartInfo = procStartInfo;
        proc.StartInfo.RedirectStandardOutput = true;
        proc.OutputDataReceived += (object sendingProcess, DataReceivedEventArgs outLine)
             => ShowOutput(String.Empty, outLine.Data, true, false);

        // execute the diagnostics  
        proc.Start();
        proc.BeginOutputReadLine();
    }  
    catch (Exception ex)  
    {  
        // put exception message in output pane  
        CustomPane.OutputString(ex.Message);  
    }  
}  

Although the OutPutDataReceived+BeginOutputReadLine looks like a more elegant and simple solution, I'll give an alternative. I solved the problem with a BackgroundWorker, and a ProcessOutPutHandler inspired from here. This approach also handles messages from stdout and stderr separately, and I can report progress to the BackgroundWorker depending on the output. Here I use the standard VS Output Window for the output, but should work with your OutputPane just as well:

public class ProcessOutputHandler
{
    public Process proc { get; set; }
    public string StdOut { get; set; }
    public string StdErr { get; set; }
    private IVsOutputWindowPane _pane;
    private BackgroundWorker _worker;

    /// <summary>  
    /// The constructor requires a reference to the process that will be read.  
    /// The process should have .RedirectStandardOutput and .RedirectStandardError set to true.  
    /// </summary>  
    /// <param name="process">The process that will have its output read by this class.</param>  
    public ProcessOutputHandler(Process process, BackgroundWorker worker)
    {
        _worker = worker;
        proc = process;
        IVsOutputWindow outputWindow =
        Package.GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow;

        Guid guidGeneral = Microsoft.VisualStudio.VSConstants.OutputWindowPaneGuid.GeneralPane_guid;
        int hr = outputWindow.CreatePane(guidGeneral, "Phone Visualizer", 1, 0);
        hr = outputWindow.GetPane(guidGeneral, out _pane);
        _pane.Activate();
        _pane.OutputString("Starting Ui State workers..");

        StdErr = "";
        StdOut = "";
        Debug.Assert(proc.StartInfo.RedirectStandardError, "RedirectStandardError must be true to use ProcessOutputHandler.");
        Debug.Assert(proc.StartInfo.RedirectStandardOutput, "RedirectStandardOut must be true to use ProcessOutputHandler.");
    }

    /// <summary>  
    /// This method starts reading the standard error stream from Process.  
    /// </summary>  
    public void ReadStdErr()
    {
        string line;
        while ((!proc.HasExited) && ((line = proc.StandardError.ReadLine()) != null))
        {
            StdErr += line;
            _pane.OutputString(line + "\n");
            // Here I could do something special if errors occur
        }
    }
    /// <summary>  
    /// This method starts reading the standard output sream from Process.  
    /// </summary>  
    public void ReadStdOut()
    {
        string line;
        while ((!proc.HasExited) && ((line = proc.StandardOutput.ReadLine()) != null))
        {
            StdOut += line;
            _pane.OutputString(line + "\n");
            if (_worker != null && line.Contains("Something I'm looking for"))
            {                            
               _worker.ReportProgress(20, "Something worth mentioning happened");
            }
        }
    }

}

And usage:

void RunProcess(string fileName, string arguments, BackgroundWorker worker)
{
  // prep process
  ProcessStartInfo psi = new ProcessStartInfo(fileName, arguments);
  psi.UseShellExecute = false;
  psi.RedirectStandardOutput = true;
  psi.RedirectStandardError = true;
  // start process
  using (Process process = new Process())
  {
    // pass process data
    process.StartInfo = psi;
    // prep for multithreaded logging
    ProcessOutputHandler outputHandler = new ProcessOutputHandler(process,worker);
    Thread stdOutReader = new Thread(new ThreadStart(outputHandler.ReadStdOut));
    Thread stdErrReader = new Thread(new ThreadStart(outputHandler.ReadStdErr));
    // start process and stream readers
    process.Start();
    stdOutReader.Start();
    stdErrReader.Start();
    // wait for process to complete
    process.WaitForExit();
   }
}

And this is called from the BackgroundWorker DoWork method, the Worker passed as a reference.

You can use a listener and attach the process's stdout to it.

ConsoleTraceListener listener = new ConsoleTraceListener(process.StandardOutput);
Debug.Listeners.Add(listener);

Make sure you remove it after the process ends:

proc.Exited += () => Debug.Listeners.Remove(listener);

You'll need to use the process' OutputDataReceived event and than attach a listener:

ConsoleTraceListener listener = new ConsoleTraceListener();
Debug.Listeners.Add(listener);

proc.OutputDataReceived += (object sendingProcess, DataReceivedEventArgs outLine) => Trace.WriteLine(outLine.Data);

Make sure you remove it after the process ends:

proc.Exited += (object sender, EventArgs e) => Debug.Listeners.Remove(listener);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!