Capturing Process Output via Outputdatareceived Event

Capturing process output via OutputDataReceived event

You need to call

mProcess.BeginOutputReadLine();

BeginOutputReadLine - "Begins asynchronous read operations on the redirected StandardOutput stream of the application."

C# Capturing the output of a process asynchronous is always null

Reading the documentation of OutputDataReceived, you must call testProcess.BeginOutputReadLine().

You also might have to call testProcess.Start() after you set the OutputDataReceived event, as the example does.

How to capture process output asynchronously in powershell?

Unfortunately asynchronous reading is not that easy if you want to do it properly. If you call WaitForExit() without timeout you could use something like this function I wrote (based on C# code):

function Invoke-Executable {
# Runs the specified executable and captures its exit code, stdout
# and stderr.
# Returns: custom object.
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[String]$sExeFile,
[Parameter(Mandatory=$false)]
[String[]]$cArgs,
[Parameter(Mandatory=$false)]
[String]$sVerb
)

# Setting process invocation parameters.
$oPsi = New-Object -TypeName System.Diagnostics.ProcessStartInfo
$oPsi.CreateNoWindow = $true
$oPsi.UseShellExecute = $false
$oPsi.RedirectStandardOutput = $true
$oPsi.RedirectStandardError = $true
$oPsi.FileName = $sExeFile
if (! [String]::IsNullOrEmpty($cArgs)) {
$oPsi.Arguments = $cArgs
}
if (! [String]::IsNullOrEmpty($sVerb)) {
$oPsi.Verb = $sVerb
}

# Creating process object.
$oProcess = New-Object -TypeName System.Diagnostics.Process
$oProcess.StartInfo = $oPsi

# Creating string builders to store stdout and stderr.
$oStdOutBuilder = New-Object -TypeName System.Text.StringBuilder
$oStdErrBuilder = New-Object -TypeName System.Text.StringBuilder

# Adding event handers for stdout and stderr.
$sScripBlock = {
if (! [String]::IsNullOrEmpty($EventArgs.Data)) {
$Event.MessageData.AppendLine($EventArgs.Data)
}
}
$oStdOutEvent = Register-ObjectEvent -InputObject $oProcess `
-Action $sScripBlock -EventName 'OutputDataReceived' `
-MessageData $oStdOutBuilder
$oStdErrEvent = Register-ObjectEvent -InputObject $oProcess `
-Action $sScripBlock -EventName 'ErrorDataReceived' `
-MessageData $oStdErrBuilder

# Starting process.
[Void]$oProcess.Start()
$oProcess.BeginOutputReadLine()
$oProcess.BeginErrorReadLine()
[Void]$oProcess.WaitForExit()

# Unregistering events to retrieve process output.
Unregister-Event -SourceIdentifier $oStdOutEvent.Name
Unregister-Event -SourceIdentifier $oStdErrEvent.Name

$oResult = New-Object -TypeName PSObject -Property ([Ordered]@{
"ExeFile" = $sExeFile;
"Args" = $cArgs -join " ";
"ExitCode" = $oProcess.ExitCode;
"StdOut" = $oStdOutBuilder.ToString().Trim();
"StdErr" = $oStdErrBuilder.ToString().Trim()
})

return $oResult
}

It captures stdout, stderr and exit code. Example usage:

$oResult = Invoke-Executable -sExeFile 'ping.exe' -cArgs @('8.8.8.8', '-a')
$oResult | Format-List -Force

For more info and alternative implementations (in C#) read this blog post.

Get Live output from Process

Take a look at this page, it looks this is the solution for you: http://msdn.microsoft.com/en-us/library/system.diagnostics.process.beginoutputreadline.aspx and http://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput.aspx

[Edit]
This is a working example:

        Process p = new Process();
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.FileName = @"C:\Program Files (x86)\gnuwin32\bin\ls.exe";
p.StartInfo.Arguments = "-R C:\\";

p.OutputDataReceived += new DataReceivedEventHandler((s, e) =>
{
Console.WriteLine(e.Data);
});
p.ErrorDataReceived += new DataReceivedEventHandler((s, e) =>
{
Console.WriteLine(e.Data);
});

p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();

Btw, ls -R C:\ lists all files from the root of C: recursively. These are a lot of files, and I'm sure it isn't done when the first results show up in the screen.
There is a possibility 7zip holds the output before showing it. I'm not sure what params you give to the proces.

Capture output from multiple processes asynchronously

The ID of a Process can get reused‌​. Therefore I recommend not using the process ID as an identifier after the process has exited.

Instead, you could use your itemId as an identifier, associating the process output with it and encapsulating the process and itemId in some container, for instance (tested lightly, seems OK):

public class ProcessExitedEventArgs<TKey> : EventArgs
{
public ProcessExitedEventArgs(TKey key, string[] output)
{
this.Key = key;
this.Output = output;
}

public TKey Key { get; private set; }
public string[] Output { get; private set; }
}

public delegate void ProcessExitedEventHandler<TKey>(object sender, ProcessExitedEventArgs<TKey> e);

public class ProcessLauncher<TKey>
{
public string FileName { get; private set; }
public string Arguments { get; private set; }
public TKey Key { get; private set; }

object locker = new object();
readonly List<string> output = new List<string>();
Process process = null;
bool launched = false;

public ProcessLauncher(string fileName, string arguments, TKey key)
{
this.FileName = fileName;
this.Arguments = arguments;
this.Key = key;
}

public event ProcessExitedEventHandler<TKey> Exited;

public bool Start()
{
lock (locker)
{
if (launched)
throw new InvalidOperationException();
launched = true;
process = new Process();
process.StartInfo.CreateNoWindow = true;
process.StartInfo.ErrorDialog = false;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardOutput = true;
process.EnableRaisingEvents = true;
process.Exited += new EventHandler(proc_Exited);
process.OutputDataReceived += proc_OutputDataReceived;
process.ErrorDataReceived += proc_ErrorDataReceived;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.FileName = FileName;
process.StartInfo.Arguments = Arguments;
try
{
var started = process.Start();

process.BeginErrorReadLine();
process.BeginOutputReadLine();
return started;
}
catch (Exception)
{
process.Dispose();
process = null;
throw;
}
}
}

void proc_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
// Fill in as appropriate.
Debug.WriteLine(string.Format("Error data received: {0}", e.Data));
}
}

void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data == null)
return;
lock (locker)
{
output.Add(e.Data);
}
}

void proc_Exited(object sender, EventArgs e)
{
lock (locker)
{
var exited = Exited;
if (exited != null)
{
exited(this, new ProcessExitedEventArgs<TKey>(Key, output.ToArray()));
// Prevent memory leaks by removing references to listeners.
Exited -= exited;
}
}
var process = Interlocked.Exchange(ref this.process, null);
if (process != null)
{
process.OutputDataReceived -= proc_OutputDataReceived;
process.ErrorDataReceived -= proc_ErrorDataReceived;
process.Exited -= proc_Exited;
process.Dispose();
}
}
}

(This class also makes sure the process is disposed.) Then use it like:

    public void LaunchProc(int itemId)
{
var launcher = new ProcessLauncher<int>("someapp.exe", "/id=" + itemId, itemId);
launcher.Exited += launcher_Exited;
launcher.Start();
}

void launcher_Exited(object sender, ProcessExitedEventArgs<int> e)
{
var itemId = e.Key;
var output = e.Output;

// Process output and associate it to itemId.
}


Related Topics



Leave a reply



Submit