How do I execute a command-line program from C# and get back the STD OUT results? Specifically, I want to execute DIFF on two files that are programmatically selected and write the results to a text box.
// Start the child process.
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "YOURBATCHFILE.bat";
p.Start();
// Do not wait for the child process to exit before
// reading to the end of its redirected stream.
// p.WaitForExit();
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
Code is from MSDN.
Here's a quick sample:
//Create process
System.Diagnostics.Process pProcess = new System.Diagnostics.Process();
//strCommand is path and file name of command to run
pProcess.StartInfo.FileName = strCommand;
//strCommandParameters are parameters to pass to program
pProcess.StartInfo.Arguments = strCommandParameters;
pProcess.StartInfo.UseShellExecute = false;
//Set output of program to be written to process output stream
pProcess.StartInfo.RedirectStandardOutput = true;
//Optional
pProcess.StartInfo.WorkingDirectory = strWorkingDirectory;
//Start the process
pProcess.Start();
//Get program output
string strOutput = pProcess.StandardOutput.ReadToEnd();
//Wait for process to finish
pProcess.WaitForExit();
There one other parameter I found useful, which I use to eliminate the process window
pProcess.StartInfo.CreateNoWindow = true;
this helps to hide the black console window from user completely, if that is what you desire.
// usage
const string ToolFileName = "example.exe";
string output = RunExternalExe(ToolFileName);
public string RunExternalExe(string filename, string arguments = null)
{
var process = new Process();
process.StartInfo.FileName = filename;
if (!string.IsNullOrEmpty(arguments))
{
process.StartInfo.Arguments = arguments;
}
process.StartInfo.CreateNoWindow = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
var stdOutput = new StringBuilder();
process.OutputDataReceived += (sender, args) => stdOutput.AppendLine(args.Data); // Use AppendLine rather than Append since args.Data is one line of output, not including the newline character.
string stdError = null;
try
{
process.Start();
process.BeginOutputReadLine();
stdError = process.StandardError.ReadToEnd();
process.WaitForExit();
}
catch (Exception e)
{
throw new Exception("OS error while executing " + Format(filename, arguments)+ ": " + e.Message, e);
}
if (process.ExitCode == 0)
{
return stdOutput.ToString();
}
else
{
var message = new StringBuilder();
if (!string.IsNullOrEmpty(stdError))
{
message.AppendLine(stdError);
}
if (stdOutput.Length != 0)
{
message.AppendLine("Std output:");
message.AppendLine(stdOutput.ToString());
}
throw new Exception(Format(filename, arguments) + " finished with exit code = " + process.ExitCode + ": " + message);
}
}
private string Format(string filename, string arguments)
{
return "'" + filename +
((string.IsNullOrEmpty(arguments)) ? string.Empty : " " + arguments) +
"'";
}
The accepted answer on this page has a weakness that is troublesome in rare situations. There are two file handles which programs write to by convention, stdout, and stderr.
If you just read a single file handle such as the answer from Ray, and the program you are starting writes enough output to stderr, it will fill up the output stderr buffer and block. Then your two processes are deadlocked. The buffer size may be 4K.
This is extremely rare on short-lived programs, but if you have a long running program which repeatedly outputs to stderr, it will happen eventually. This is tricky to debug and track down.
There are a couple good ways to deal with this.
One way is to execute cmd.exe instead of your program and use the /c argument to cmd.exe to invoke your program along with the "2>&1" argument to cmd.exe to tell it to merge stdout and stderr.
var p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c mycmd.exe 2>&1";
Another way is to use a programming model which reads both handles at the same time.
var p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = #"/c dir \windows";
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = false;
p.OutputDataReceived += (a, b) => Console.WriteLine(b.Data);
p.ErrorDataReceived += (a, b) => Console.WriteLine(b.Data);
p.Start();
p.BeginErrorReadLine();
p.BeginOutputReadLine();
p.WaitForExit();
System.Diagnostics.ProcessStartInfo psi =
new System.Diagnostics.ProcessStartInfo(#"program_to_call.exe");
psi.RedirectStandardOutput = true;
psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
psi.UseShellExecute = false;
System.Diagnostics.Process proc = System.Diagnostics.Process.Start(psi); ////
System.IO.StreamReader myOutput = proc.StandardOutput;
proc.WaitForExit(2000);
if (proc.HasExited)
{
string output = myOutput.ReadToEnd();
}
You will need to use ProcessStartInfo with RedirectStandardOutput enabled - then you can read the output stream. You might find it easier to use ">" to redirect the output to a file (via the OS), and then simply read the file.
[edit: like what Ray did: +1]
One-liner run command:
new Process() { StartInfo = new ProcessStartInfo("echo", "Hello, World") }.Start();
Read output of command in shortest amount of reable code:
var cliProcess = new Process() {
StartInfo = new ProcessStartInfo("echo", "Hello, World") {
UseShellExecute = false,
RedirectStandardOutput = true
}
};
cliProcess.Start();
string cliOut = cliProcess.StandardOutput.ReadToEnd();
cliProcess.WaitForExit();
cliProcess.Close();
In case you also need to execute some command in the cmd.exe, you can do the following:
// Start the child process.
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/C vol";
p.Start();
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
Console.WriteLine(output);
This returns just the output of the command itself:
You can also use StandardInput instead of StartInfo.Arguments:
// Start the child process.
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "cmd.exe";
p.Start();
// Read the output stream first and then wait.
p.StandardInput.WriteLine("vol");
p.StandardInput.WriteLine("exit");
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
Console.WriteLine(output);
The result looks like this:
Since the most answers here dont implement the using statemant for IDisposable and some other stuff wich I think could be nessecary I will add this answer.
For C# 8.0
// Start a process with the filename or path with filename e.g. "cmd". Please note the
//using statemant
using myProcess.StartInfo.FileName = "cmd";
// add the arguments - Note add "/c" if you want to carry out tge argument in cmd and
// terminate
myProcess.StartInfo.Arguments = "/c dir";
// Allows to raise events
myProcess.EnableRaisingEvents = true;
//hosted by the application itself to not open a black cmd window
myProcess.StartInfo.UseShellExecute = false;
myProcess.StartInfo.CreateNoWindow = true;
// Eventhander for data
myProcess.Exited += OnOutputDataRecived;
// Eventhandler for error
myProcess.ErrorDataReceived += OnErrorDataReceived;
// Eventhandler wich fires when exited
myProcess.Exited += OnExited;
// Starts the process
myProcess.Start();
//read the output before you wait for exit
myProcess.BeginOutputReadLine();
// wait for the finish - this will block (leave this out if you dont want to wait for
// it, so it runs without blocking)
process.WaitForExit();
// Handle the dataevent
private void OnOutputDataRecived(object sender, DataReceivedEventArgs e)
{
//do something with your data
Trace.WriteLine(e.Data);
}
//Handle the error
private void OnErrorDataReceived(object sender, DataReceivedEventArgs e)
{
Trace.WriteLine(e.Data);
//do something with your exception
throw new Exception();
}
// Handle Exited event and display process information.
private void OnExited(object sender, System.EventArgs e)
{
Trace.WriteLine("Process exited");
}
Here is small example:
using System;
using System.Diagnostics;
class Program
{
static void Main(string[] args)
{
var p = Process.Start(
new ProcessStartInfo("git", "branch --show-current")
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
WorkingDirectory = Environment.CurrentDirectory
}
);
p.WaitForExit();
string branchName =p.StandardOutput.ReadToEnd().TrimEnd();
string errorInfoIfAny =p.StandardError.ReadToEnd().TrimEnd();
if (errorInfoIfAny.Length != 0)
{
Console.WriteLine($"error: {errorInfoIfAny}");
}
else {
Console.WriteLine($"branch: {branchName}");
}
}
}
I believe this is shortest form.
Please notice that most of command line tools easily confuse standard output and standard error, sometimes it makes sense just to clue those together into single string.
Also p.ExitCode might be sometimes useful.
Example above serves for purpose of writing command line utility like tools if you want to do it by yourself. Please note that for cli automation it's also possible to use Cake Frosten and Cake Git extension.
You can launch any command line program using the Process class, and set the StandardOutput property of the Process instance with a stream reader you create (either based on a string or a memory location). After the process completes, you can then do whatever diff you need to on that stream.
This might be useful for someone if your attempting to query the local ARP cache on a PC/Server.
List<string[]> results = new List<string[]>();
using (Process p = new Process())
{
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.Arguments = "/c arp -a";
p.StartInfo.FileName = #"C:\Windows\System32\cmd.exe";
p.Start();
string line;
while ((line = p.StandardOutput.ReadLine()) != null)
{
if (line != "" && !line.Contains("Interface") && !line.Contains("Physical Address"))
{
var lineArr = line.Trim().Split(' ').Select(n => n).Where(n => !string.IsNullOrEmpty(n)).ToArray();
var arrResult = new string[]
{
lineArr[0],
lineArr[1],
lineArr[2]
};
results.Add(arrResult);
}
}
p.WaitForExit();
}
This may not be the best/easiest way, but may be an option:
When you execute from your code, add " > output.txt" and then read in the output.txt file.
There is a ProcessHelper Class in PublicDomain open source code which might interest you.
Julian's solution is tested working with some minor corrections. The following is an example that also used https://sourceforge.net/projects/bat-to-exe/ GenericConsole.cs and https://www.codeproject.com/Articles/19225/Bat-file-compiler program.txt for args part:
using System;
using System.Text; //StringBuilder
using System.Diagnostics;
using System.IO;
class Program
{
private static bool redirectStandardOutput = true;
private static string buildargument(string[] args)
{
StringBuilder arg = new StringBuilder();
for (int i = 0; i < args.Length; i++)
{
arg.Append("\"" + args[i] + "\" ");
}
return arg.ToString();
}
static void Main(string[] args)
{
Process prc = new Process();
prc.StartInfo = //new ProcessStartInfo("cmd.exe", String.Format("/c \"\"{0}\" {1}", Path.Combine(Environment.CurrentDirectory, "mapTargetIDToTargetNameA3.bat"), buildargument(args)));
//new ProcessStartInfo(Path.Combine(Environment.CurrentDirectory, "mapTargetIDToTargetNameA3.bat"), buildargument(args));
new ProcessStartInfo("mapTargetIDToTargetNameA3.bat");
prc.StartInfo.Arguments = buildargument(args);
prc.EnableRaisingEvents = true;
if (redirectStandardOutput == true)
{
prc.StartInfo.UseShellExecute = false;
}
else
{
prc.StartInfo.UseShellExecute = true;
}
prc.StartInfo.CreateNoWindow = true;
prc.OutputDataReceived += OnOutputDataRecived;
prc.ErrorDataReceived += OnErrorDataReceived;
//prc.Exited += OnExited;
prc.StartInfo.RedirectStandardOutput = redirectStandardOutput;
prc.StartInfo.RedirectStandardError = redirectStandardOutput;
try
{
prc.Start();
prc.BeginOutputReadLine();
prc.BeginErrorReadLine();
prc.WaitForExit();
}
catch (Exception e)
{
Console.WriteLine("OS error: " + e.Message);
}
prc.Close();
}
// Handle the dataevent
private static void OnOutputDataRecived(object sender, DataReceivedEventArgs e)
{
//do something with your data
Console.WriteLine(e.Data);
}
//Handle the error
private static void OnErrorDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine(e.Data);
}
// Handle Exited event and display process information.
//private static void OnExited(object sender, System.EventArgs e)
//{
// var process = sender as Process;
// if (process != null)
// {
// Console.WriteLine("ExitCode: " + process.ExitCode);
// }
// else
// {
// Console.WriteLine("Process exited");
// }
//}
}
The code need to compile inside VS2007, using commandline csc.exe generated executable will not show console output correctly, or even crash with CLR20r3 error. Comment out the OnExited event process, the console output of the bat to exe will be more like the original bat console output.
Just for fun, here's my completed solution for getting PYTHON output - under a button click - with error reporting. Just add a button called "butPython" and a label called "llHello"...
private void butPython(object sender, EventArgs e)
{
llHello.Text = "Calling Python...";
this.Refresh();
Tuple<String,String> python = GoPython(#"C:\Users\BLAH\Desktop\Code\Python\BLAH.py");
llHello.Text = python.Item1; // Show result.
if (python.Item2.Length > 0) MessageBox.Show("Sorry, there was an error:" + Environment.NewLine + python.Item2);
}
public Tuple<String,String> GoPython(string pythonFile, string moreArgs = "")
{
ProcessStartInfo PSI = new ProcessStartInfo();
PSI.FileName = "py.exe";
PSI.Arguments = string.Format("\"{0}\" {1}", pythonFile, moreArgs);
PSI.CreateNoWindow = true;
PSI.UseShellExecute = false;
PSI.RedirectStandardError = true;
PSI.RedirectStandardOutput = true;
using (Process process = Process.Start(PSI))
using (StreamReader reader = process.StandardOutput)
{
string stderr = process.StandardError.ReadToEnd(); // Error(s)!!
string result = reader.ReadToEnd(); // What we want.
return new Tuple<String,String> (result,stderr);
}
}
Related
i am trying to call and collect the data returned by the CMD command query user.
Calling this via cmd from the Windows-startbar gives me a normal result.
Calling this via this c# function give 0 output.
public void callQueryUser()
{
ProcessStartInfo psi = new ProcessStartInfo("cmd.exe");
Process p = Process.Start(psi);
string cmd = string.Format(#"/c query user");
psi.Arguments = cmd;
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
psi.WaitForExit();
string result = p.StandardOutput.ReadToEnd();
MessageBox.Show(result);
}
I checked and the Window says command cant befound... I also check if they are both the same cmd.exe and thats also true. It seems like calling the cmd.exe via C# makes somewhat of a differences.
Anyone any idea what i could check next ?
It's not necessary to use cmd to retrieve the information you want using Process. However, if your OS is 64-bit, your program is running as 32-bit, and you're trying to access %windir%\System32\query.exe, you need to use %windir%\Sysnative\query.exe instead.
Try the following:
Option 1:
public void callQueryUser()
{
string queryPath = string.Empty;
//use 'Sysnative' to access 64-bit files (in System32) if program is running as 32-bit process
//use 'SysWow64' to access 32-bit files on 64-bit OS
if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess)
queryPath = System.IO.Path.Combine(Environment.GetEnvironmentVariable("windir"), "Sysnative", "query.exe");
else
queryPath = System.IO.Path.Combine(Environment.GetEnvironmentVariable("windir"), "System32", "query.exe");
Debug.WriteLine("queryPath: " + queryPath);
// create new instance
ProcessStartInfo startInfo = new ProcessStartInfo(queryPath);
startInfo.Arguments = "user"; //arguments
startInfo.CreateNoWindow = true; //don't create a window
startInfo.RedirectStandardError = true; //redirect standard error
startInfo.RedirectStandardOutput = true; //redirect standard output
startInfo.UseShellExecute = false; //if true, uses 'ShellExecute'; if false, uses 'CreateProcess'
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
//create new instance
using (Process p = new Process { StartInfo = startInfo, EnableRaisingEvents = true })
{
//subscribe to event and add event handler code
p.ErrorDataReceived += (sender, e) =>
{
if (!String.IsNullOrEmpty(e.Data))
{
//ToDo: add desired code
Debug.WriteLine("Error: " + e.Data);
}
};
//subscribe to event and add event handler code
p.OutputDataReceived += (sender, e) =>
{
if (!String.IsNullOrEmpty(e.Data))
{
//ToDo: add desired code
Debug.WriteLine("Output: " + e.Data);
string result = e.Data;
MessageBox.Show(result);
}
};
p.Start(); //start
p.BeginErrorReadLine(); //begin async reading for standard error
p.BeginOutputReadLine(); //begin async reading for standard output
//waits until the process is finished before continuing
p.WaitForExit();
}
}
Option 2:
public void callQueryUser()
{
string queryPath = string.Empty;
//environment variable windir has the same value as SystemRoot
//use 'Sysnative' to access 64-bit files (in System32) if program is running as 32-bit process
//use 'SysWow64' to access 32-bit files on 64-bit OS
if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess)
queryPath = System.IO.Path.Combine(Environment.GetEnvironmentVariable("windir"), "Sysnative", "query.exe");
else
queryPath = System.IO.Path.Combine(Environment.GetEnvironmentVariable("windir"), "System32", "query.exe");
Debug.WriteLine("queryPath: " + queryPath);
// create new instance
ProcessStartInfo startInfo = new ProcessStartInfo(queryPath);
startInfo.Arguments = "user"; //arguments
startInfo.CreateNoWindow = true; //don't create a window
startInfo.RedirectStandardError = true; //redirect standard error
startInfo.RedirectStandardOutput = true; //redirect standard output
startInfo.UseShellExecute = false; //if true, uses 'ShellExecute'; if false, uses 'CreateProcess'
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
//create new instance
using (Process p = new Process { StartInfo = startInfo, EnableRaisingEvents = true })
{
p.Start(); //start
//waits until the process is finished before continuing
p.WaitForExit();
string result = p.StandardOutput.ReadToEnd();
MessageBox.Show(result);
}
}
Resources:
Accessing files from System32 directory using 32 bit application on 64 bit machine
Process Class
ProcessStartInfo Class
Environment.GetEnvironmentVariable Method
I have an app which starts another app. This other app prints a few lines into the Console but noone needs this output and it prints it's output betwenn my own. How can I prevent this other app from printing it's stuff into my console?
I tried to run with ProcessStartInfo.UseShellExecutive both on true and false, also tried to change the console output into a MemoryStream before starting but since I need the Console i had to change the output back and it looks like the other app got their input changed back too.
Process serverprocess = new Process();
serverprocess.StartInfo.FileName = Path.GetFileName(serverpath);
serverprocess.StartInfo.Arguments = launch;
serverprocess.StartInfo.UseShellExecute = false;
serverprocess.StartInfo.RedirectStandardOutput = true;
serverprocess.Start();
In your code ensure that you are re-directing both StandardOutput and StandardError that way everything that the "ThirdPartyApp" writes will be captured in either of these streams.
I have written a small Helper Class that helps with this
You can use like
//Launching excel.exe with /safe as arg
var excelExample1 = #"""C:\Program Files (x86)\Microsoft Office\Office15\EXCEL.EXE"" /safe";
LaunchCMD.Invoke(excelExample1);
//To get its output, if any
var getOutput = LaunchCMD.Output;
LaunchCMD Helper Class
class LaunchCMD
{
public static string Output
{
get; set;
} = "";
public static void Invoke(string command, bool waitTillExit = false, bool closeOutputWindow = false)
{
ProcessStartInfo ProcessInfo;
Process Process = new Process();
ProcessInfo = new ProcessStartInfo("cmd.exe", "/C " + command);
ProcessInfo.CreateNoWindow = false;
ProcessInfo.UseShellExecute = false;
ProcessInfo.RedirectStandardOutput = true;
ProcessInfo.RedirectStandardError = true;
Process.EnableRaisingEvents = true;
Process = Process.Start(ProcessInfo);
Process.ErrorDataReceived += ConsoleDataReceived;
Process.OutputDataReceived += ConsoleDataReceived;
Process.BeginOutputReadLine();
Process.BeginErrorReadLine();
if (waitTillExit == true)
{
Process.WaitForExit();
}
if (closeOutputWindow == true)
{
Process.CloseMainWindow();
}
Process.Close();
System.Threading.Thread.Sleep(1000);
Output.ToString();
}
private static void ConsoleDataReceived(object sender, DataReceivedEventArgs e)
{
System.Threading.Thread.Sleep(1000);
if (e.Data != null)
{
Output = Output + e.Data;
}
}
}
i am build application who run Wireshark and start sniffing, Wireshark has dumpcap.exe file who receive arguments (interface number, output file etc) and start sniffing and meanwhile i can see in the cmd window the number of packet and this number growing all the time.
my question is how can i catch this number every few seconds in order to show this number on my application windows.
this is my class who start this sniffing:
public class DumpPcap
{
public int _interfaceNumber;
public string _pcapPath;
public string _dumPcapPath = #"C:\Program Files\Wireshark\dumpcap.exe";
public DumpPcap(int interfaceNumber, string pcapPath)
{
_interfaceNumber = interfaceNumber;
_pcapPath = pcapPath;
}
public void startTheCapture()
{
List<string> stList = new List<string>();
ProcessStartInfo process = new ProcessStartInfo(_dumPcapPath);
process.Arguments = string.Format("-i " + _interfaceNumber + " -s 65535 -w " + _pcapPath);
process.WindowStyle = ProcessWindowStyle.Hidden;
process.RedirectStandardOutput = true;
process.RedirectStandardError = true;
process.CreateNoWindow = true;
process.UseShellExecute = false;
process.ErrorDialog = false;
Process dumpcap = Process.Start(process);
StreamReader reader = dumpcap.StandardOutput;
//dumpcap.WaitForExit(100000);
while (!reader.EndOfStream)
{
stList.Add(reader.ReadLine());
}
}
}
and this is screenshot and i marked in red the field that i want to show in my application:
http://image.torrent-invites.com/images/641Untitled.jpg
Instead of trying to capture output text from the ProcessStartInfo how about doing it from the Process, and intercept the output data via the OutputDataReceived event handler?
Try this replacement to your block of code:
List<string> stList = new List<string>();
var process = new Process();
process.StartInfo.FileName = _dumPcapPath;
process.StartInfo.Arguments =
string.Format("-i " + _interfaceNumber + " -s 65535 -w " + _pcapPath);
process.Startinfo.WindowStyle = ProcessWindowStyle.Hidden;
process.Startinfo.RedirectStandardOutput = true;
process.Startinfo.RedirectStandardError = true;
process.Startinfo.CreateNoWindow = true;
process.Startinfo.UseShellExecute = false;
process.Startinfo.ErrorDialog = false;
// capture the data received event here...
process.OutputDataReceived +=
new DataReceivedEventHandler(process_OutputDataReceived);
process.Start();
process.BeginOutputReadLine();
private void process_OutputDataReceived(object sender, DataReceivedEventArgs arg)
{
// arg.Data contains the output data from the process...
}
NOTE: I just typed this in without compiling or any serious validating, so be warned, LOL...
This question already has answers here:
ProcessStartInfo hanging on "WaitForExit"? Why?
(22 answers)
Closed 5 years ago.
I have a program that frequently uses an external program and reads its outputs.
It works pretty well using your usual process redirect output, but one specific argument for some reason hangs when I try to read it, no error message - no exception, it just 'stops' when it reaches that line.
I of course use a centralized function to call and read output from the program, which is this:
public string ADBShell(string adbInput)
{
try
{
//Create Empty values
string result = string.Empty;
string error = string.Empty;
string output = string.Empty;
System.Diagnostics.ProcessStartInfo procStartInfo
= new System.Diagnostics.ProcessStartInfo(toolPath + "adb.exe");
procStartInfo.Arguments = adbInput;
procStartInfo.RedirectStandardOutput = true;
procStartInfo.RedirectStandardError = true;
procStartInfo.UseShellExecute = false;
procStartInfo.CreateNoWindow = true;
procStartInfo.WorkingDirectory = toolPath;
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo = procStartInfo;
proc.Start();
// Get the output into a string
proc.WaitForExit();
result = proc.StandardOutput.ReadToEnd();
error = proc.StandardError.ReadToEnd(); //Some ADB outputs use this
if (result.Length > 1)
{
output += result;
}
if (error.Length > 1)
{
output += error;
}
Return output;
}
catch (Exception objException)
{
throw objException;
}
}
The line that hangs is result = proc.StandardOutput.ReadToEnd();, but again, not every time, only when sent a specific argument ("start-server"). All other arguments work just fine - it reads the value and returns it.
It's also strange the way it hangs. It doesn't freeze or give an error or anything, it just stops processing. As if it was a 'return' command, except it doesn't even return to the calling function, it just stops everything with the interface still up and running.
Anyone experienced this before? Anyone have any idea what I should try? I'm assuming it's something unexpected within the stream itself, but is there a way I can handle/ignore this so that it reads it anyway?
Proposed solutions with BeginOutputReadLine() are a good way but in situations such as that, it is not applicable, because process (certainly with using WaitForExit()) exits earlier than async output finished completely.
So, I tried to implement it synchronously and found that the solution is in using Peek() method from StreamReader class. I added check for Peek() > -1 to sure that it is not the end of the stream as in MSDN article described and finally it works and stop hanging!
Here is the code:
var process = new Process();
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.WorkingDirectory = #"C:\test\";
process.StartInfo.FileName = "test.exe";
process.StartInfo.Arguments = "your arguments here";
process.Start();
var output = new List<string>();
while (process.StandardOutput.Peek() > -1)
{
output.Add(process.StandardOutput.ReadLine());
}
while (process.StandardError.Peek() > -1)
{
output.Add(process.StandardError.ReadLine());
}
process.WaitForExit();
The problem is that you are using the synchronous ReadToEnd methods on both the StandardOutput and the StandardError streams. This can lead to a potential deadlock you are experiencing. This is even described in the MSDN. The solution is described there. Basically, it is: Use the asynchronous version BeginOutputReadLine to read the data of the StandardOutput stream:
p.BeginOutputReadLine();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
Implementation of Async reading using BeginOutputReadLine see in ProcessStartInfo hanging on "WaitForExit"? Why?
What about something like:
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.OutputDataReceived += (sender, args) =>
{
var outputData = args.Data;
// ...
};
process.ErrorDataReceived += (sender, args) =>
{
var errorData = args.Data;
// ...
};
process.WaitForExit();
I had the same deadlock problem. This code snippet worked for me.
ProcessStartInfo startInfo = new ProcessStartInfo("cmd")
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true
};
Process process = new Process();
process.StartInfo = startInfo;
process.Start();
process.StandardInput.WriteLine("echo hi");
process.StandardInput.WriteLine("exit");
var output = process.StandardOutput.ReadToEnd();
process.Dispose();
Something that is elegant and worked for me is:
Process nslookup = new Process()
{
StartInfo = new ProcessStartInfo("nslookup")
{
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
}
};
nslookup.Start();
nslookup.StandardInput.WriteLine("set type=srv");
nslookup.StandardInput.WriteLine("_ldap._tcp.domain.local");
nslookup.StandardInput.Flush();
nslookup.StandardInput.Close();
string output = nslookup.StandardOutput.ReadToEnd();
nslookup.WaitForExit();
nslookup.Close();
This answer I found here and the trick is using Flush() and Close() on standard input.
The accepted answer's solution didn't work for me. I had to use tasks in order to avoid the deadlock:
//Code to start process here
String outputResult = GetStreamOutput(process.StandardOutput);
String errorResult = GetStreamOutput(process.StandardError);
process.WaitForExit();
With a GetStreamOutput function as follows:
private string GetStreamOutput(StreamReader stream)
{
//Read output in separate task to avoid deadlocks
var outputReadTask = Task.Run(() => stream.ReadToEnd());
return outputReadTask.Result;
}
I had the same kind of problem that error was just hanging.
Based on your response to Daniel Hilgarth I didn't even try using those codes though i think they would have worked for me.
Since I want to be able do some fancier output still eventually i decided that I would do it with both of the outputs being done in a background thread.
public static class RunCommands
{
#region Outputs Property
private static object _outputsLockObject;
private static object OutputsLockObject
{
get
{
if (_outputsLockObject == null)
Interlocked.CompareExchange(ref _outputsLockObject, new object(), null);
return _outputsLockObject;
}
}
private static Dictionary<object, CommandOutput> _outputs;
private static Dictionary<object, CommandOutput> Outputs
{
get
{
if (_outputs != null)
return _outputs;
lock (OutputsLockObject)
{
_outputs = new Dictionary<object, CommandOutput>();
}
return _outputs;
}
}
#endregion
public static string GetCommandOutputSimple(ProcessStartInfo info, bool returnErrorIfPopulated = true)
{
// Redirect the output stream of the child process.
info.UseShellExecute = false;
info.CreateNoWindow = true;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
var process = new Process();
process.StartInfo = info;
process.ErrorDataReceived += ErrorDataHandler;
process.OutputDataReceived += OutputDataHandler;
var output = new CommandOutput();
Outputs.Add(process, output);
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
// Wait for the process to finish reading from error and output before it is finished
process.WaitForExit();
Outputs.Remove(process);
if (returnErrorIfPopulated && (!String.IsNullOrWhiteSpace(output.Error)))
{
return output.Error.TrimEnd('\n');
}
return output.Output.TrimEnd('\n');
}
private static void ErrorDataHandler(object sendingProcess, DataReceivedEventArgs errLine)
{
if (errLine.Data == null)
return;
if (!Outputs.ContainsKey(sendingProcess))
return;
var commandOutput = Outputs[sendingProcess];
commandOutput.Error = commandOutput.Error + errLine.Data + "\n";
}
private static void OutputDataHandler(object sendingProcess, DataReceivedEventArgs outputLine)
{
if (outputLine.Data == null)
return;
if (!Outputs.ContainsKey(sendingProcess))
return;
var commandOutput = Outputs[sendingProcess];
commandOutput.Output = commandOutput.Output + outputLine.Data + "\n";
}
}
public class CommandOutput
{
public string Error { get; set; }
public string Output { get; set; }
public CommandOutput()
{
Error = "";
Output = "";
}
}
This worked for me and allowed me to not have to use a timeout for the read.
Just in case someone stumbles upon this question while wiling to use Windows Forms and TextBox (or RichTextBox) to show the errors and outputs the process returns in real time (as they are written to process.StandardOutput / process.StandardError).
You need to use OutputDataReceived() / ErrorDataReceived() in order to read both streams without deadlocks, there is no way (as far as I know) to avoid deadlocks otherwise, even Fedor's answer, which now holds the "Answer" tag and the most likes up to date, does not do the trick for me.
However, when you use the RichTextBox (or TextBox) to output the data, another problem you encounter is how to actually write the data into the textbox in real time (once it arrives). You receive the access to the data inside one of the background threads OutputDataReceived() / ErrorDataReceived() and you can only AppendText() from the main thread.
What I first tried doing was calling process.Start() from a background thread and then calling BeginInvoke() => AppendText() in OutputDataReceived() / ErrorDataReceived() threads while the main thread was process.WaitForExit().
However, this led to my form freezing and ultimately hanging for eternity. After a few days of trying I ended up with the solution below, that seems to work pretty well.
Shortly speaking, you need to add the messages into a concurrent collection inside OutputDataReceived() / ErrorDataReceived() threads while the main thread should constantly try to extract messages from that collection and append them into the textbox:
ProcessStartInfo startInfo
= new ProcessStartInfo(File, mysqldumpCommand);
process.StartInfo.FileName = File;
process.StartInfo.Arguments = mysqldumpCommand;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.RedirectStandardInput = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
process.EnableRaisingEvents = true;
ConcurrentQueue<string> messages = new ConcurrentQueue<string>();
process.ErrorDataReceived += (object se, DataReceivedEventArgs ar) =>
{
string data = ar.Data;
if (!string.IsNullOrWhiteSpace(data))
messages.Enqueue(data);
};
process.OutputDataReceived += (object se, DataReceivedEventArgs ar) =>
{
string data = ar.Data;
if (!string.IsNullOrWhiteSpace(data))
messages.Enqueue(data);
};
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
while (!process.HasExited)
{
string data = null;
if (messages.TryDequeue(out data))
UpdateOutputText(data, tbOutput);
Thread.Sleep(5);
}
process.WaitForExit();
The only downside of this approach is the fact that you can loose messages in a quite rare case, when process starts writing them between process.Start() and process.BeginErrorReadLine() / process.BeginOutputReadLine(), just keep that in mind. The only way to avoid that is to read the full streams and (or) gain access to them only when the process finishes.
first
// Start the child process.
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "Write500Lines.exe";
p.Start();
// Do not wait for the child process to exit before
// reading to the end of its redirected stream.
// p.WaitForExit();
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
second
// Do not perform a synchronous read to the end of both
// redirected streams.
// string output = p.StandardOutput.ReadToEnd();
// string error = p.StandardError.ReadToEnd();
// p.WaitForExit();
// Use asynchronous read operations on at least one of the streams.
p.BeginOutputReadLine();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
This is from MSDN
I'm trying to execute multiple commands without create a new process each time. Basically, I want to start the DOS command shell, switch to the MySQL command shell, and execute a command. Here's how I am calling the procedure (also below). Also, how do I handle the "\"'s in the command?
ExecuteCommand("mysql --user=root --password=sa casemanager", 100, false);
ExecuteCommand(#"\. " + Environment.CurrentDirectory + #"\MySQL\CaseManager.sql", 100, true);
private void ExecuteCommand(string Command, int Timeout, Boolean closeProcess)
{
ProcessStartInfo ProcessInfo;
Process Process;
ProcessInfo = new ProcessStartInfo("cmd.exe", "/C " + Command);
ProcessInfo.CreateNoWindow = false;
ProcessInfo.UseShellExecute = false;
Process = Process.Start(ProcessInfo);
Process.WaitForExit(Timeout);
if (closeProcess == true) { Process.Close(); }
}
You can redirect standard input and use a StreamWriter to write to it:
Process p = new Process();
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "cmd.exe";
info.RedirectStandardInput = true;
info.UseShellExecute = false;
p.StartInfo = info;
p.Start();
using (StreamWriter sw = p.StandardInput)
{
if (sw.BaseStream.CanWrite)
{
sw.WriteLine("mysql -u root -p");
sw.WriteLine("mypassword");
sw.WriteLine("use mydb;");
}
}
const string strCmdText = "/C command1&command2";
Process.Start("CMD.exe", strCmdText);
Couldn't you just write all the commands into a .cmd file in the temp folder and then execute that file?
As another answer alludes to under newer versions of Windows it seems to be necessary to read the standard output and/or standard error streams otherwise it will stall between commands. A neater way to do that instead of using delays is to use an async callback to consume output from the stream:
static void RunCommands(List<string> cmds, string workingDirectory = "")
{
var process = new Process();
var psi = new ProcessStartInfo();
psi.FileName = "cmd.exe";
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.UseShellExecute = false;
psi.WorkingDirectory = workingDirectory;
process.StartInfo = psi;
process.Start();
process.OutputDataReceived += (sender, e) => { Console.WriteLine(e.Data); };
process.ErrorDataReceived += (sender, e) => { Console.WriteLine(e.Data); };
process.BeginOutputReadLine();
process.BeginErrorReadLine();
using (StreamWriter sw = process.StandardInput)
{
foreach (var cmd in cmds)
{
sw.WriteLine (cmd);
}
}
process.WaitForExit();
}
I prefer to do it by using a BAT file.
With BAT file you have more control and can do whatever you want.
string batFileName = path + #"\" + Guid.NewGuid() + ".bat";
using (StreamWriter batFile = new StreamWriter(batFileName))
{
batFile.WriteLine($"YOUR COMMAND");
batFile.WriteLine($"YOUR COMMAND");
batFile.WriteLine($"YOUR COMMAND");
}
ProcessStartInfo processStartInfo = new ProcessStartInfo("cmd.exe", "/c " + batFileName);
processStartInfo.UseShellExecute = true;
processStartInfo.CreateNoWindow = true;
processStartInfo.WindowStyle = ProcessWindowStyle.Normal;
Process p = new Process();
p.StartInfo = processStartInfo;
p.Start();
p.WaitForExit();
File.Delete(batFileName);
ProcessStartInfo pStartInfo = new ProcessStartInfo();
pStartInfo.FileName = "CMD";
pStartInfo.Arguments = #"/C mysql --user=root --password=sa casemanager && \. " + Environment.CurrentDirectory + #"\MySQL\CaseManager.sql"
pStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
Process.Start(pStartInfo);
The && is the way to tell the command shell that there is another command to execute.
A command-line process such cmd.exe or mysql.exe will usually read (and execute) whatever you (the user) type in (at the keyboard).
To mimic that, I think you want to use the RedirectStandardInput property: http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.redirectstandardinput.aspx
You could also tell MySQL to execute the commands in the given file, like so:
mysql --user=root --password=sa casemanager < CaseManager.sql
You need to READ ALL data from input, before send another command!
And you can't ask to READ if no data is avaliable... little bit suck isn't?
My solutions... when ask to read... ask to read a big buffer... like 1 MEGA...
And you will need wait a min 100 milliseconds... sample code...
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim oProcess As New Process()
Dim oStartInfo As New ProcessStartInfo("cmd.exe", "")
oStartInfo.UseShellExecute = False
oStartInfo.RedirectStandardOutput = True
oStartInfo.RedirectStandardInput = True
oStartInfo.CreateNoWindow = True
oProcess.StartInfo = oStartInfo
oProcess.Start()
Dim Response As String = String.Empty
Dim BuffSize As Integer = 1024 * 1024
Dim x As Char() = New Char(BuffSize - 1) {}
Dim bytesRead As Integer = 0
oProcess.StandardInput.WriteLine("dir")
Threading.Thread.Sleep(100)
bytesRead = oProcess.StandardOutput.Read(x, 0, BuffSize)
Response = String.Concat(Response, String.Join("", x).Substring(0, bytesRead))
MsgBox(Response)
Response = String.Empty
oProcess.StandardInput.WriteLine("dir c:\")
Threading.Thread.Sleep(100)
bytesRead = 0
bytesRead = oProcess.StandardOutput.Read(x, 0, BuffSize)
Response = String.Concat(Response, String.Join("", x).Substring(0, bytesRead))
MsgBox(Response)
End Sub
End Class
I'm using these methods:
public static Process StartCommand(params string[] commands) => StartCommand(commands, false);
public static Process StartCommand(IEnumerable<string> commands, bool inBackground, bool runAsAdministrator = true)
{
Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
if(commands.Any()) p.StartInfo.Arguments = #"/C " + string.Join("&&", commands);
if (runAsAdministrator)
p.StartInfo.Verb = "runas";
if (inBackground)
{
p.StartInfo.CreateNoWindow = true;
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
}
p.Start();
return p;
}
Enjoy...