As is asked in this post, there are two ways to call another process in C#.
Process.Start("hello");
And
Process p = new Process();
p.StartInfo.FileName = "hello.exe";
p.Start();
p.WaitForExit();
Q1 : What are the pros/cons of each approach?
Q2 : How to check if error happens with the Process.Start() method?
With the first method you might not be able to use WaitForExit, as the method returns null if the process is already running.
How you check if a new process was started differs between the methods. The first one returns a Process object or null:
Process p = Process.Start("hello");
if (p != null) {
// A new process was started
// Here it's possible to wait for it to end:
p.WaitForExit();
} else {
// The process was already running
}
The second one returns a bool:
Process p = new Process();
p.StartInfo.FileName = "hello.exe";
bool s = p.Start();
if (s) {
// A new process was started
} else {
// The process was already running
}
p.WaitForExit();
For simple cases, the advantage is mainly convenience. Obviously you have more options (working path, choosing between shell-exec, etc) with the ProcessStartInfo route, but there is also a Process.Start(ProcessStartInfo) static method.
Re checking for errors; Process.Start returns the Process object, so you can wait for exit and check the error code if you need. If you want to capture stderr, you probably want either of the ProcessStartInfo approaches.
Very little difference. The static method returns a process object, so you can still use the "p.WaitForExit()" etc - using the method where you create a new process it would be easier to modify the process parameters (processor affinity and such) before launching the process.
Other than that - no difference. A new process object is created both ways.
In your second example - that is identical to this:
Process p = Process.Start("hello.exe");
p.WaitForExit();
Related
I need to write a wrapper for an interactive command line program.
That means I need to be able to send commands to the other program via its standard input und receive the response via its standard output.
The problem is, that the standard output stream seems to be blocked while the input stream is still open. As soon as I close the input stream I get the response. But then I cannot send more commands.
This is what I am using at the moment (mostly from here):
void Main() {
Process process;
process = new Process();
process.StartInfo.FileName = "atprogram.exe";
process.StartInfo.Arguments = "interactive";
// Set UseShellExecute to false for redirection.
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
// Redirect the standard output of the command.
// This stream is read asynchronously using an event handler.
process.StartInfo.RedirectStandardOutput = true;
// Set our event handler to asynchronously read the output.
process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
// Redirect standard input as well. This stream is used synchronously.
process.StartInfo.RedirectStandardInput = true;
process.Start();
// Start the asynchronous read of the output stream.
process.BeginOutputReadLine();
String inputText;
do
{
inputText = Console.ReadLine();
if (inputText == "q")
{
process.StandardInput.Close(); // After this line the output stream unblocks
Console.ReadLine();
return;
}
else if (!String.IsNullOrEmpty(inputText))
{
process.StandardInput.WriteLine(inputText);
}
}
}
I also tried reading the standard output stream synchronously, but with the same result. Any method call on the output stream block indefinitely until the input stream is closed - even Peek() and EndOfStream.
Is there any way to communicate with the other process in a full duplex kind of way?
I tried to reproduce your problem with a small test suite of my own.
Instead of using event handlers I do it in the most trivial way I could conceive: Synchronously. This way no extra complexity is added to the problem.
Here my little "echoApp" I wrote in rust, just for the giggles and also to have a chance to run into the eternal line termination wars problem ( \n vs \r vs \r\n). Depending on the way your command line application is written, this could indeed be one of your problems.
use std::io;
fn main() {
let mut counter = 0;
loop {
let mut input = String::new();
let _ = io::stdin().read_line(&mut input);
match &input.trim() as &str {
"quit" => break,
_ => {
println!("{}: {}", counter, input);
counter += 1;
}
}
}
}
And - being a lazy bone who does not like creating a solution for such a small test, I used F# instead of C# for the controlling side - it is easy enough to read I think:
open System.Diagnostics;
let echoPath = #"E:\R\rustic\echo\echoApp\target\debug\echoApp.exe"
let createControlledProcess path =
let p = new Process()
p.StartInfo.UseShellExecute <- false
p.StartInfo.RedirectStandardInput <- true
p.StartInfo.RedirectStandardOutput <- true
p.StartInfo.Arguments <- ""
p.StartInfo.FileName <- path
p.StartInfo.CreateNoWindow <- true
p
let startupControlledProcess (p : Process) =
if p.Start()
then
p.StandardInput.NewLine <- "\r\n"
else ()
let shutdownControlledProcess (p : Process) =
p.StandardInput.WriteLine("quit");
p.WaitForExit()
p.Close()
let interact (p : Process) (arg : string) : string =
p.StandardInput.WriteLine(arg);
let o = p.StandardOutput.ReadLine()
// we get funny empty lines every other time...
// probably some line termination problem ( unix \n vs \r\n etc -
// who can tell what rust std::io does...?)
if o = "" then p.StandardOutput.ReadLine()
else o
let p = createControlledProcess echoPath
startupControlledProcess p
let results =
[
interact p "Hello"
interact p "World"
interact p "Whatever"
interact p "floats"
interact p "your"
interact p "boat"
]
shutdownControlledProcess p
Executing this in f# interactive (CTRL-A ALT-Enter in Visual Studio) yields:
val echoPath : string = "E:\R\rustic\echo\echoApp\target\debug\echoApp.exe"
val createControlledProcess : path:string -> Process
val startupControlledProcess : p:Process -> unit
val shutdownControlledProcess : p:Process -> unit
val interact : p:Process -> arg:string -> string
val p : Process = System.Diagnostics.Process
val results : string list =
["0: Hello"; "1: World"; "2: Whatever"; "3: floats"; "4: your"; "5: boat"]
val it : unit = ()
I could not reproduce any blocking or deadlocks etc.
So, in your case I would try to investigate if maybe your NewLine property needs some tweaking (see function startupControlledProcess. If the controlled application does not recognize an input as a line, it might not respond, still waiting for the rest of the input line and you might get the effect you have.
process.BeginOutputReadLine();
Doesn't work like expected, because it waits until output stream will be closed, which will happen when process will end, and process will end when its input stream will be closed.
As workaround just use combinations of process.StandardOutput.ReadLine() and asynchronous made by yourself
The .Exited is not working for all cases, for example: to C:\foo.png when I close the responsible application that show the image, I don't get the MessageBox.Show("exited!");
here's my code:
public static void TryOpenFile(string filename)
{
Process proc = new Process();
proc.StartInfo = new ProcessStartInfo(filename);
proc.EnableRaisingEvents = true;
proc.Exited += (a,b) => { MessageBox.Show("Exited!"); }
proc.Start();
}
how I call the function TryOpenFile(#"C:\foo.png");. How to fix this?
Is it possible that you already have your image editing program open? When you call proc.Start(), if the process is already running, then the existing process is reused. You should check the return value of proc.Start() to see if this is the case.
From MSDN:
Return Value
true if a process resource is started; false if no new
process resource is started (for example, if an existing process is
reused).
...
Remarks
...
If the process resource specified by the FileName member of the StartInfo property is
already running on the computer, no additional process resource is started. Instead, the
running process resource is reused and false is returned.
Scenario: I have a MFC code which call an exe created in C# (it is a windows form application)
Need: I need that the exe would return a value when closed and on the basis of the return value the same exe will started again
psudocode
int result = RunExe("exename", arguments)
if(result == 1)
{
result = RunExe("exename", arguments)
}
do I have to put the if condition in loop?
plz give me some suggestion.
1.How to return a value from exe
2. How to collect the return value
3. How to restart the exe
Your C# EXE can return an int value like this:
[STAThread]
public static int Main() {
return 5;
}
Your other app has to handle the return value like the others here has explained.
var proc = Process.Start("mycsharwinformapp.exe"):
proc.WaitForExit();
//If the code is 5 restart app!
if(proc.ExitCode==5) Process.Start("mycsharwinformapp.exe"):
The following method should do the trick;
private static int RunProcess(string processName, string arguments)
{
Process process = new Process();
process.StartInfo.FileName = processName;
process.StartInfo.Arguments = arguments;
process.Start();
process.WaitForExit();
return process.ExitCode;
}
Then call it like so;
int returnCode;
do
{
returnCode = RunProcess("...", "...");
}
while (returnCode == 1);
you can use the process.ExitCode and create a new EXE which controls the exitvalue and starts the original EXE if needed, or you save the information in a file on the disk if its more than an integer so you can process it from the parent process (the new EXE you create).
like O.D wrote, Process.ExitCode is the value you are looking for ...
to start the process you can use Process.Start(string_path_to_exe,string_args) which will return a Process object that represents the started process ... to wait until the process has ended use the WaitForExit() method of that object
see Process Class # MSDN
I've 2 exe (A, B) and one dll (C).
A is an exe that user invokes from commandline with argument -ui or -file_path.
if -ui is passed: B is used to show UI.
if -file_path is passed, C is used for further functionality.
if -ui is passed, i use following code (in Main method):
System.Threading.Thread a = new System.Threading.Thread(yah);
a.Start();
static void yah()
{
SyngoViaInstallerUI.Program.Main();
}
but it blocks the command line from where exe A was invoked. is it possible to unblock the cmdLine or i should to create a new process for -ui argument?
Thanks.
you have to create seperate process for B in order to release the process A and finish gracefully.
Following code works, but is this correct way?
System.Diagnostics.Process pr = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
psi.FileName = #"file_path";
pr.StartInfo = psi;
pr.Start();
Thanks.
I wrote a quick and dirty wrapper around svn.exe to retrieve some content and do something with it, but for certain inputs it occasionally and reproducibly hangs and won't finish. For example, one call is to svn list:
svn list "http://myserver:84/svn/Documents/Instruments/" --xml --no-auth-cache --username myuser --password mypassword
This command line runs fine when I just do it from a command shell, but it hangs in my app. My c# code to run this is:
string cmd = "svn.exe";
string arguments = "list \"http://myserver:84/svn/Documents/Instruments/\" --xml --no-auth-cache --username myuser --password mypassword";
int ms = 5000;
ProcessStartInfo psi = new ProcessStartInfo(cmd);
psi.Arguments = arguments;
psi.RedirectStandardOutput = true;
psi.WindowStyle = ProcessWindowStyle.Normal;
psi.UseShellExecute = false;
Process proc = Process.Start(psi);
StreamReader output = new StreamReader(proc.StandardOutput.BaseStream, Encoding.UTF8);
proc.WaitForExit(ms);
if (proc.HasExited)
{
return output.ReadToEnd();
}
This takes the full 5000 ms and never finishes. Extending the time doesn't help. In a separate command prompt, it runs instantly, so I'm pretty sure it's unrelated to an insufficient waiting time. For other inputs, however, this seems to work fine.
I also tried running a separate cmd.exe here (where exe is svn.exe and args is the original arg string), but the hang still occurred:
string cmd = "cmd";
string arguments = "/S /C \"" + exe + " " + args + "\"";
What could I be screwing up here, and how can I debug this external process stuff?
EDIT:
I'm just now getting around to addressing this. Mucho thanks to Jon Skeet for his suggestion, which indeed works great. I have another question about my method of handling this, though, since I'm a multi-threaded novice. I'd like suggestions on improving any glaring deficiencies or anything otherwise dumb. I ended up creating a small class that contains the stdout stream, a StringBuilder to hold the output, and a flag to tell when it's finished. Then I used ThreadPool.QueueUserWorkItem and passed in an instance of my class:
ProcessBufferHandler bufferHandler = new ProcessBufferHandler(proc.StandardOutput.BaseStream,
Encoding.UTF8);
ThreadPool.QueueUserWorkItem(ProcessStream, bufferHandler);
proc.WaitForExit(ms);
if (proc.HasExited)
{
bufferHandler.Stop();
return bufferHandler.ReadToEnd();
}
... and ...
private class ProcessBufferHandler
{
public Stream stream;
public StringBuilder sb;
public Encoding encoding;
public State state;
public enum State
{
Running,
Stopped
}
public ProcessBufferHandler(Stream stream, Encoding encoding)
{
this.stream = stream;
this.sb = new StringBuilder();
this.encoding = encoding;
state = State.Running;
}
public void ProcessBuffer()
{
sb.Append(new StreamReader(stream, encoding).ReadToEnd());
}
public string ReadToEnd()
{
return sb.ToString();
}
public void Stop()
{
state = State.Stopped;
}
}
This seems to work, but I'm doubtful that this is the best way. Is this reasonable? And what can I do to improve it?
One standard issue: the process could be waiting for you to read its output. Create a separate thread to read from its standard output while you're waiting for it to exit. It's a bit of a pain, but that may well be the problem.
Jon Skeet is right on the money!
If you don't mind polling after you launch your svn command try this:
Process command = new Process();
command.EnableRaisingEvents = false;
command.StartInfo.FileName = "svn.exe";
command.StartInfo.Arguments = "your svn arguments here";
command.StartInfo.UseShellExecute = false;
command.StartInfo.RedirectStandardOutput = true;
command.Start();
while (!command.StandardOutput.EndOfStream)
{
Console.WriteLine(command.StandardOutput.ReadLine());
}
I had to drop an exe on a client's machine and use Process.Start to launch it.
The calling application would hang - the issue ended up being their machine assuming the exe was dangerous and preventing other applications from starting it.
Right click the exe and go to properties. Hit "Unblock" toward the bottom next to the security warning.
Based on Jon Skeet's answer this is how I do it in modern day (2021) .NET 5
var process = Process.Start(processStartInfo);
var stdErr = process.StandardError;
var stdOut = process.StandardOutput;
var resultAwaiter = stdOut.ReadToEndAsync();
var errResultAwaiter = stdErr.ReadToEndAsync();
await process.WaitForExitAsync();
await Task.WhenAll(resultAwaiter, errResultAwaiter);
var result = resultAwaiter.Result;
var errResult = errResultAwaiter.Result;
Note that you can't await the standard output before the error, because the wait will hang in case the standard error buffer gets full first (same for trying it the other way around).
The only way is to start reading them asynchronously, wait for the process to exit, and then complete the await by using Task.WaitAll
I know this is an old post but maybe this will assist someone. I used this to execute some AWS (Amazon Web Services) CLI commands using .Net TPL tasks.
I did something like this in my command execution which is executed within a .Net TPL Task which is created within my WinForm background worker bgwRun_DoWork method which holding a loop with while(!bgwRun.CancellationPending). This contains the reading of the Standard Output from the Process via a new Thread using the .Net ThreadPool class.
private void bgwRun_DoWork(object sender, DoWorkEventArgs e)
{
while (!bgwRun.CancellationPending)
{
//build TPL Tasks
var tasks = new List<Task>();
//work to add tasks here
tasks.Add(new Task(()=>{
//build .Net ProcessInfo, Process and start Process here
ThreadPool.QueueUserWorkItem(state =>
{
while (!process.StandardOutput.EndOfStream)
{
var output = process.StandardOutput.ReadLine();
if (!string.IsNullOrEmpty(output))
{
bgwRun_ProgressChanged(this, new ProgressChangedEventArgs(0, new ExecutionInfo
{
Type = "ExecutionInfo",
Text = output,
Configuration = s3SyncConfiguration
}));
}
if (cancellationToken.GetValueOrDefault().IsCancellationRequested)
{
break;
}
}
});
});//work Task
//loop through and start tasks here and handle completed tasks
} //end while
}
I know my SVN repos can run slow sometimes, so maybe 5 seconds isn't long enough? Have you copied the string you are passing to the process from a break point so you are positive it's not prompting you for anything?