Process DataReceivedEventArgs event is not triggering - c#

I am using ffmpeg to add watermark in the video. I am trying to write log on console but I don't know why my MyEvent event is not calling.
public void ConvertVideo(string path)
{
string command = string.Format("-i {0} -i logo.png -filter_complex "overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2" -codec:a copy output.mp4", Path);
ProcessStartInfo oInfo = new ProcessStartInfo(#"C:\ffmpeg\ffmpeg.exe", command)
{
CreateNoWindow = true,
RedirectStandardError = true,
RedirectStandardOutput = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardInput = true
};
Process p = new Process();
void MyEvent(object s, DataReceivedEventArgs e)
{
Console.Writeline(e.Data);
}
try
{
p.OutputDataReceived += MyEvent;
p.StartInfo = oInfo;
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
p.WaitForExit(10000000);
}
catch (Exception ex)
{
Console.Writeline(ex);
}
finally
{
if (p != null)
{
p.Close();
}
}
}

You first create a new Process object and add the OutputDataReceived event. That looks all good but then you overwrite that Process object by calling Process.Start(oInfo) which means that the subscription you created for the OutputDataReceived event is overwritten.
Instead of doing p = Process.Start(oInfo) you could probably just do p.Start(oInfo) or p.StartInfo = oInfo; and then p.Start();. That way the MyEvent subscription is still there when the process is started and the output should be routed properly.

Related

Executing batch file from C# winforms ignores timeout

All,
I am attempting to execute a series of batch files via a C# winforms app. In this early stage, with a test batch file, I am unable to get the process execution to respect the timeout in my batch file unless i set UseShellExecute = true, which is something i am trying to avoid. My goal is to execute the script file and redirect the output to the GUI as shown in the code here:
Process process;
public void ExecuteScript(string workingDirectory, string batchFileName)
{
if (process != null)
process.Dispose();
process = new Process();
process.StartInfo.WorkingDirectory = workingDirectory;
process.StartInfo.FileName = workingDirectory + batchFileName;
process.StartInfo.Arguments = "";
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardInput = true;
process.EnableRaisingEvents = true;
process.OutputDataReceived += proc_OutputDataReceived;
process.Start();
process.BeginOutputReadLine();
process.Exited += OnProcessExit;
}
private void OnProcessExit(object sender, EventArgs e)
{
Console.WriteLine("the script has ended");
}
private void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
this.Invoke((Action)(() =>
{
textBox1.AppendText(Environment.NewLine + e.Data);
}));
(sender as Process)?.StandardInput.WriteLine();
}
my batch file looks like this:
#echo off
echo This is a running script
timeout /t 10
echo Done sleeping. Will Exit
exit
Is there an appropriate combination of settings i can call to prevent the command window from appearing, while still redirecting the output, and executing the script appropriately?
The problem with your code is that the timeout command is not supported when stdin is redirected. This is a good example of why one should always redirect both stdout and stderr. An error message is actually emitted from the batch file, but because you weren't capturing the stderr stream, you didn't see the error message. All too many questions here on Stack Overflow involving Process scenarios that "don't work" could be easily solved had the person looked at the stderr output.
A work-around to this limitation of the timeout command is to use the waitfor command instead, using a known-nonexistent signal name with a timeout value, e.g. waitfor /t 10 placeholder.
Here is a console program that is entirely self-contained and which demonstrates both the failure of the timeout command when stdin is redirected, as well as the work-around of waitfor:
const string batchFileText =
#"#echo off
echo Starting batch file
timeout /t 5
waitfor /t 5 placeholder
echo Timeout completed
exit";
static void Main(string[] args)
{
string batchFileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".bat");
try
{
File.WriteAllText(batchFileName, batchFileText);
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = batchFileName,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardInput = true,
RedirectStandardError = true,
};
Process process = new Process
{
EnableRaisingEvents = true,
};
process.OutputDataReceived += Process_OutputDataReceived;
process.ErrorDataReceived += Process_ErrorDataReceived;
process.Exited += Process_Exited;
process.StartInfo = psi;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
}
finally
{
File.Delete(batchFileName);
}
}
private static void Process_Exited(object sender, EventArgs e)
{
WriteLine("Process exited");
}
private static void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
WriteLine($"stdout: {DateTime.Now:HH:mm:ss.sss}: {e.Data}");
}
}
private static void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
WriteLine($"stderr: {DateTime.Now:HH:mm:ss.sss}: {e.Data}");
}
}
Note that the waitfor command writes a message to stderr if the timeout occurs (which it always will in this case). You may or may not want that to show up in the captured stderr stream. If not, you can redirect the stderr of that command specifically by using 2>nul. E.g. waitfor /t 10 placeholder 2>nul.

Interactively reading from and writing to command line with UI application

I have UI app that calls cmd periodically with different arguments and I want to periodically update UI with cmd output results.
Here's the code I use, but the problem is that UI is updated only when all commands are executed and not after each command and I didn't find solution for periodic update of UI when each command is executed:
ProcessStartInfo psi = new ProcessStartInfo()
{
FileName = "cmd.exe",
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
};
Process p = new Process();
p.StartInfo = psi;
p.Start();
var reposToUpdate = ConfigurationManager.AppSettings["UpdateAndMergeReposOnBranch"];
foreach (XmlNode repoPathNode in reposPaths)
{
var repoPath = repoPathNode.Attributes["root"].Value;
p.StandardInput.WriteLine(string.Format("cd {0}", repoPath));
p.StandardInput.WriteLine(#"hg update --check");
p.StandardInput.Flush();
}
p.StandardInput.Close();
string output = p.StandardOutput.ReadToEnd();
rtbOutput.Text = output;
You could subscribe to the Process.OutputDataReceived event instead of using the Process.StandardOutput.ReadToEnd method:
ProcessStartInfo psi = new ProcessStartInfo()
{
FileName = "cmd.exe",
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
};
Process p = new Process();
p.StartInfo = psi;
p.Start();
// Output handling:
p.OutputDataReceived += (o, e) => Console.WriteLine(e.Data);
p.BeginOutputReadLine();
var reposToUpdate = ConfigurationManager.AppSettings["UpdateAndMergeReposOnBranch"];
foreach (XmlNode repoPathNode in reposPaths)
{
var repoPath = repoPathNode.Attributes["root"].Value;
p.StandardInput.WriteLine(string.Format("cd {0}", repoPath));
p.StandardInput.WriteLine(#"hg update --check");
p.StandardInput.Flush();
}
p.StandardInput.Close();
In the example above all data printed to the Console. Alternatively you can append output to the TextBox:
p.OutputDataReceived += (o, e) => rtbOutput.Invoke((MethodInvoker)(() => rtbOutput.Text += e.Data));
Please note that you should also handle the Process.Exited event.
What you are looking for is the BeginOutputReadLine method and associated event to receive data as it happens in the process.
p.OutputDataReceived += OnOutputDataReceived;
p.BeginOutputReadLine ();
p.WaitForExit();
then elsewhere add a method
void OnOutputDataReceived (object sender, DataReceivedEventArgs e)
{
// DO Something with the output
}
there is also a ErrorDataReceived event that will hook to stderr.

Show Command Prompt Output with BackgroundWorker.Backgroundworker

I have a program that executes a batch file using an asynchronous background worker. Here is the code:
public static void Execute(CmdObj obj, bool batch)
{
CmdObj = obj;
var theWorker = new BackgroundWorker();
theWorker.RunWorkerCompleted += WorkerCompleted;
theWorker.WorkerReportsProgress = true;
if(batch)
{
theWorker.DoWork += WorkerBatchWork;
}else{
theWorker.DoWork += WorkerCommandWork;
}
theWorker.RunWorkerAsync();
}
private static void WorkerBatchWork(object sender, DoWorkEventArgs e)
{
RunBatch(CmdObj);
}
private static void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
var temp = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
if (temp != null)
ProgramPath = temp.Substring(6);
WriteLog(CmdObj.Activity, false);
WriteLog(CmdObj.Error, true);
CmdObj.TheButton.Enabled = true;
}
private static void RunBatch(CmdObj obj)
{
var process = new Process();
var startInfo = new ProcessStartInfo
{
FileName = obj.SrcFile,
WindowStyle = ProcessWindowStyle.Normal,
CreateNoWindow = false,
RedirectStandardInput = true,
RedirectStandardOutput = false,
RedirectStandardError = true,
UseShellExecute = false
};
try
{
if (!obj.SrcFile.ToLower().Trim().EndsWith(".bat"))
throw new FileLoadException("Not a valid .bat file",obj.SrcFile);
process.StartInfo = startInfo;
process.Start();
process.WaitForExit();
//obj.Activity = process.StandardOutput.ReadToEnd();
obj.Error = process.StandardError.ReadToEnd();
}
catch (Exception ex)
{
obj.Exception = ex;
}
finally
{
process.Close();
}
}
class CmdObj
{
public string Command { get; set; }
public string SrcFile { get; set; }
public string Activity { get; set; }
public string Error { get; set; }
public Exception Exception { get; set; }
public Button TheButton { get; set; }
}
So when I run this program and choose a batch file to execute, I get a blank CMD window. Is there some way to show the output in the CMD window when my program executes the batch file? Alternatively, if I could have the CMD output sent to a textbox or some other control (in real-time), that would work as well.
Thanks!
Since you have one of the following properties set to true, you'll get a blank black Window
RedirectStandardInput
RedirectStandardOutput
RedirectStandardError
This will actually happen because you've told your application to receive/send the output/input from the target process
I see that you were trying to get the output from the batch file using this line
//obj.Activity = process.StandardOutput.ReadToEnd();
Unfortunately, this won't work unless you set RedirectStandardOutput to True so that your application would have the ability to read the output from the target batch file. Otherwise, the output will be empty
Example
private void RunBatch(CmdObj obj)
{
var process = new Process();
var startInfo = new ProcessStartInfo
{
FileName = obj.SrcFile,
WindowStyle = ProcessWindowStyle.Normal,
CreateNoWindow = false,
RedirectStandardInput = true,
RedirectStandardOutput = true, //This is required if we want to get the output
RedirectStandardError = true,
UseShellExecute = false
};
try
{
if (!obj.SrcFile.ToLower().Trim().EndsWith(".bat"))
throw new FileLoadException("Not a valid .bat file",obj.SrcFile);
process.StartInfo = startInfo;
process.Start();
process.WaitForExit();
obj.Activity = process.StandardOutput.ReadToEnd(); //Get the output from the target process
obj.Error = process.StandardError.ReadToEnd();
}
catch (Exception ex)
{
obj.Exception = ex;
}
finally
{
process.Close(); //Terminate the process after getting what is required
}
}
Notice that: UseShellExecute must be False in order to use RedirectStandardOutput
Thanks,
I hope you find this helpful :)
Don't you need to call
process.BeginErrorReadLine();
process.BeginOutputReadLine();
after process starts?
I had some problems without this if I remember right.

Start a process in the same console

Can I start a process (using C# Process.Start()) in the same console as the calling program? This way no new window will be created and standard input/output/error will be the same as the calling console application. I tried setting process.StartInfo.CreateNoWindow = true; but the process still starts in a new window (and immediately closes after it finishes).
You shouldn't need to do anything other than set UseShellExecute = false, as the default behaviour for the Win32 CreateProcess function is for a console application to inherit its parent's console, unless you specify the CREATE_NEW_CONSOLE flag.
I tried the following program:
private static void Main()
{
Console.WriteLine( "Hello" );
var p = new Process();
p.StartInfo = new ProcessStartInfo( #"c:\windows\system32\netstat.exe", "-n" )
{
UseShellExecute = false
};
p.Start();
p.WaitForExit();
Console.WriteLine( "World" );
Console.ReadLine();
}
and it gave me this output:
You could try redirecting the output of this process and then printing it on the calling process console:
public class Program
{
static void Main()
{
var psi = new ProcessStartInfo
{
FileName = #"c:\windows\system32\netstat.exe",
Arguments = "-n",
RedirectStandardOutput = true,
UseShellExecute = false
};
var process = Process.Start(psi);
while (!process.HasExited)
{
Thread.Sleep(100);
}
Console.WriteLine(process.StandardOutput.ReadToEnd());
}
}
Alternative approach using the Exited event and a wait handle:
static void Main()
{
using (Process p = new Process())
{
p.StartInfo = new ProcessStartInfo
{
FileName = #"netstat.exe",
Arguments = "-n",
RedirectStandardOutput = true,
UseShellExecute = false
};
p.EnableRaisingEvents = true;
using (ManualResetEvent mre = new ManualResetEvent(false))
{
p.Exited += (s, e) => mre.Set();
p.Start();
mre.WaitOne();
}
Console.WriteLine(p.StandardOutput.ReadToEnd());
}
}

ProcessInfo and RedirectStandardOutput

I have an app which calls another process in a command window and that process has updating stats that output to the console window. I thought this was a fairly simple operation but I can't seem to get it to work. Am I missing something?
string assemblyLocation = Assembly.GetExecutingAssembly().Location;
Process process = new Process
{
ProcessStart =
{
RedirectStandardOutput = true,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
Arguments = arg,
FileName = assemblyLocation.Substring(0, assemblyLocation.LastIndexOf("\\")) + "\\ffmpeg.exe",
CreateNoWindow = true
}
};
process.Start();
Console.WriteLine(process.StandardOutput.ReadToEnd());
process.WaitForExit();
Ideally what I would like is as the output changes within that process I hit or data comes into the reader that I get events off it.
Any help would be great, I feel like this is a newbie question but seem to be missing something.
I've experienced this before. Sometimes, the way in which the process you're calling outputs to the console is not compatible with this sort of output redirection. I've been fortunate enough in this case to be able to modify the external process to get around this.
You might try running your code on another process that outputs to the console, and see if it works properly. It reads about right to me right now.
EDIT:
I went and pulled a code block I've used to do this. This is in a WPF app which redirects the process output to the window. Notice the event binding. Since this is WPF I have to invoke my call to write the data out. Since you aren't worried about blocking, ou should be able to simply replace that with:
Console.WriteLine(e.Data);
Hopefully it helps!
private static void LaunchProcess()
{
Process build = new Process();
build.StartInfo.WorkingDirectory = #"dir";
build.StartInfo.Arguments = "";
build.StartInfo.FileName = "my.exe";
build.StartInfo.UseShellExecute = false;
build.StartInfo.RedirectStandardOutput = true;
build.StartInfo.RedirectStandardError = true;
build.StartInfo.CreateNoWindow = true;
build.ErrorDataReceived += build_ErrorDataReceived;
build.OutputDataReceived += build_ErrorDataReceived;
build.EnableRaisingEvents = true;
build.Start();
build.BeginOutputReadLine();
build.BeginErrorReadLine();
build.WaitForExit();
}
// write out info to the display window
static void build_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
string strMessage = e.Data;
if (richTextBox != null && !String.Empty(strMessage))
{
App.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Send, (ThreadStart)delegate()
{
Paragraph para = new Paragraph(new Run(strMessage));
para.Margin = new Thickness(0);
para.Background = brushErrorBrush;
box.Document.Blocks.Add(para);
});
}
}
I'm not sure exactly what problem you're running into, but if you're looking to act on output as soon as it's generated, try hooking into the process's OutputDataReceived event. You can specify handlers to receive output asynchronously from the process. I've used this approach successfully.
Process p = new Process();
ProcessStartInfo info = p.info;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
p.OutputDataReceived += p_OutputDataReceived;
p.ErrorDataReceived += p_ErrorDataReceived;
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
p.WaitForExit();
..
void p_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine("Received from standard out: " + e.Data);
}
void p_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine("Received from standard error: " + e.Data);
}
See the OutputDataReceived event off Process for more information.
Using lambda expressions, etc:
var info = new ProcessStartInfo(path)
{
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false,
Verb = "runas",
};
var process = new Process
{
EnableRaisingEvents = true,
StartInfo = info
};
Action<object, DataReceivedEventArgs> actionWrite = (sender, e) =>
{
Console.WriteLine(e.Data);
};
process.ErrorDataReceived += (sender, e) => actionWrite(sender, e);
process.OutputDataReceived += (sender, e) => actionWrite(sender, e);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
Interestingly you can't read from standard output and standard error at the same time:
if you redirect both standard output and standard error and then try to read both, for
example using the following C# code.
[C#]
string output = p.StandardOutput.ReadToEnd();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
In this case, if the child process writes any text to standard error it will block the
process, because the parent process cannot read from standard error until it has finished
reading from standard output. However, the parent process will not read from standard
output until the process ends. A recommended solution to this situation is to create two
threads so that your application can read the output of each stream on a separate thread.
http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.redirectstandardoutput(v=vs.71).aspx
flowing code worked in VS2010
void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (String.IsNullOrEmpty(e.Data) == false)
{
new Thread(() =>
{
this.Dispatcher.Invoke(new Action(() =>
{
// Add you code here
}));
}).Start();
}
}
Check that the output you are expecting is not being sent to the StandardError output instead of the StandardOutput output

Categories