Interactively reading from and writing to command line with UI application - c#

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.

Related

Process DataReceivedEventArgs event is not triggering

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.

Using Process to run old programs with hidden window

I have a couple of old windows programs that we still need to use. Both are designed to run from batch files with command line options so using Process to start and monitor them works fine.
Both programs display progress windows after starting and I would like to hide those windows but they don't seem to respond to the Process setting that control window display.
ProcessStartInfo info = new ProcessStartInfo() {
FileName = appName,
Arguments = arguments,
RedirectStandardError = true,
RedirectStandardOutput = true,
WorkingDirectory = workingDirectory,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
};
StringBuilder errorData = new StringBuilder(OUTPUT_SIZE);
StringBuilder outputData = new StringBuilder(OUTPUT_SIZE);
using (Process process = new Process { StartInfo = info }) {
process.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => ErrorDataReceived(sender, e, errorData));
process.OutputDataReceived += new DataReceivedEventHandler((sender, e) => OutputDataReceived(sender, e, outputData));
if (exited != null) {
process.EnableRaisingEvents = true;
process.Exited += exited;
}
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
}
Is there another way to force the window hidden or do I just have to use them they way they are?
I've done a bunch of searching but all the answer I find just use some variation of the code above.

Stuck while trying to run "cmd.exe /c SYSTEMINFO" in C#

I'm trying to run the following code in my C# WPF application. Whenever I use something like dir (I should probably mention the output is the directory of my working folder in Visual Studio, not System32), it allows it. However, if I use systeminfo or set the working directory to C:\Windows\System32, it hangs...
MessageBox.Show("STARTED");
var processInfo = new ProcessStartInfo("cmd.exe", "/c systeminfo") {
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
//WorkingDirectory = #"C:\Windows\System32\"
};
// *** Redirect the output ***
Process process = Process.Start(processInfo);
if (process == null) return false;
process.WaitForExit();
MessageBox.Show("Done");
string output = process.StandardOutput.ReadToEnd().ToLower();
string error = process.StandardError.ReadToEnd();
int exitCode = process.ExitCode;
MessageBox.Show(output);
MessageBox.Show(error);
MessageBox.Show(exitCode.ToString(CultureInfo.InvariantCulture));
Any ideas?
Just tried this and works as expected.
Of course you need to read the redirected output before the process closes.
var processInfo = new ProcessStartInfo("cmd.exe", "/c systeminfo")
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
WorkingDirectory = #"C:\Windows\System32\"
};
StringBuilder sb = new StringBuilder();
Process p = Process.Start(processInfo);
p.OutputDataReceived += (sender, args) => sb.AppendLine(args.Data);
p.BeginOutputReadLine();
p.WaitForExit();
Console.WriteLine(sb.ToString());

how to execute a command prompt command in my own console application

how can i make my console application window to behave like a command prompt window and execute my command line arguments?
This should get you started:
public class Program
{
public static void Main(string[] args)
{
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
proc.Start();
new Thread(() => ReadOutputThread(proc.StandardOutput)).Start();
new Thread(() => ReadOutputThread(proc.StandardError)).Start();
while (true)
{
Console.Write(">> ");
var line = Console.ReadLine();
proc.StandardInput.WriteLine(line);
}
}
private static void ReadOutputThread(StreamReader streamReader)
{
while (true)
{
var line = streamReader.ReadLine();
Console.WriteLine(line);
}
}
}
The basics are:
open cmd.exe process and capture all three streams (in, out, err)
pass input from outside in
read output and transfer to your own output.
The "Redirect" options are important - otherwise you can't use the process' respective streams.
The code above is very basic, but you can improve on it.
I believe you are looking for this
var command = "dir";
System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command);
procStartInfo.RedirectStandardOutput = true;
procStartInfo.UseShellExecute = false;
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo = procStartInfo;
proc.Start();
string result = proc.StandardOutput.ReadToEnd();
Console.WriteLine(result);

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

Categories