i am invoking an exe via c# Diagnostics.Process class and read output from it's StdOut. The process is forcefully terminated in case it doesn't automatically terminates in a specified time, something like:
process.StartInfo.FileName = #"D:\t.exe";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = false;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = false;
process.WaitForExit(500);
if (!process.HasExited)
{
process.Kill();
process.WaitForExit();
}
string stdOutContents = process.StandardOutput.ReadToEnd();
Now the problem is the code works succesfully when the exe terminates normally. But in case it fails to terminate normally (usually the exe gets stuck in an infinite loop somewhere), stdOutContents is read as an empty string.
How can i read StdOut after the process is killed (without using process.OutputDataReceived event technique)? (It has been verified that the exe-in-question does always writes something onto StdOut even if it gets stuck somewhere).
Update 1
Details about Exe which is being invoked (refereed as 'native app' across this question)
It is a small utility implemented in c language and compiled using MS C++ compiler. It does its job while simultaneously outputting status information onto the StdOut (using putchar).
There are only two possible cases of operation:
It will run successfully while simultaneously printing some data onto the StdOut.
It will run normally to a certain point (simultaneously outputting data on StdOut) and then get stuck in an infinite loop. (This is an acceptable behavior).
Both scenarios have been verified using cmd.
Details about new attempts
i wrote a c# app (referred as dummy app) which mimics the native app behavior and this code works fine. However when run for the native app, i get nothing at all.
i don't understand why the code cant read the contents outputted by the native app?
i also tried using event handler for OutputDataReceived. It gets called only once with args.Data = null when the code tries to kill the process. Inspecting the behavior for dummy app revealed that when process.kill is called, the handler is invoked with args.Data = null. So this seems to be a standard behavior of sorts for both apps.
i also tried changing the newline characters for native app. Since it is implemented in c language, it uses \n for newline. i tried using both \r\n pair for newline but StdOut is still blank (for case 2).
I had the same interrogation and the doc of Process.Kill says
Data edited by the process or resources allocated to the process can be lost if you call Kill.
Which seems to indicate that you cannot rely on reading the StandardOutput of a process, although it is not clearly stated that the output / error streams are disposed.
I finally got inspired by this answer
How to spawn a process and capture its STDOUT in .NET?
and I use the following code :
var info = new ProcessStartInfo("some.exe");
info.CreateNoWindow = true;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
using (var p = new Process())
{
p.StartInfo = info;
var output = new StringBuilder();
p.OutputDataReceived += (sender, eventArgs) =>
{
output.AppendLine(eventArgs.Data);
};
p.Start();
p.BeginOutputReadLine();
if (!p.WaitForExit(5000))
{
Console.WriteLine("Taking too long...");
p.Kill();
Console.WriteLine("Process killed, output :\n" + output);
}
}
Same pattern can be used with the ErrorDataReceived
Note that one could miss some unflushed output from the child process, however in my case I don't expect much from a process that requires to be killed, at most some information for debugging purposes.
Related
I have a C# Console application which I am trying to execute from another WinForm application just like batch runner by giving the console application's .exe file like below.
Process.Start("Path of Console application exe to execute")
However I need to wait and handle the output and display the output in WinForm's richtextbox from console application once it has completed the execution. How can I achieve this?
Update
I have changed the code to Start a Process and Read using StandardOutput and BeginOutputReadLine() to Read the output asynchronously, but not able to see output in console window, instead console window is getting closed. Not sure how to solve this.
p.StartInfo.UseShellExecute = false;
// p.StartInfo.CreateNoWindow = True
p.StartInfo.RedirectStandardOutput = true;
string #out = null;
p.StartInfo.RedirectStandardError = true;
p.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
{
#out += e.Data;
});
p.StartInfo.FileName = currentTest;
p.Start();
p.BeginOutputReadLine();
// string output = p.StandardError.ReadToEnd();
// p.WaitForExit()
while (!p.HasExited)
Application.DoEvents();
//Console.WriteLine($#"Output \n'{output.Substring(output.Length - 50)}'");
Console.WriteLine($#"\n Error stream: {#out}");
Console.ReadLine();
You need to redirect stdout (and probably stderr) so that any output comes to you, instead of a console; you may also want to redirect stdin. All of these things are available via ProcessStartInfo, with an example on MSDN. Note that if you want to display updates while the exe is running, you may need a worker thread to read incrementally from StandardOutput and StandardError, rather than ReadToEnd() - which won't return anything at all until the associated output pipe is closed.
However! If the console exe is "yours", it may be simpler to just expose the functionality you want in a library, and invoke it directly in-process. There are times when out-of-process is actively preferred, such as when you need to allow that process to go catastrophically wrong in some scenarios - but usually in-process is preferable, given free rein.
I'm trying to launch a game server (Game: Ark Survival Evolved) via a C# application. (Some kind of wrapper).
This game server is a console application with some output about the current state of the server.
I want to read the output in order to react to it. That's why the reading must happen asynchronously. (I can't wait untill the server has stopped)
My current approach is the following:
public void RunServer()
{
if (Process.GetProcessesByName("ShooterGameServer").Length <= 0)
{
Process p = new Process();
// redirect output stream
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.EnableRaisingEvents = true;
p.OutputDataReceived += (sender, args) => Display(args.Data);
// set Server.exe path
p.StartInfo.FileName = path_server;
// run server
p.Start();
// reading the console
p.BeginOutputReadLine();
p.WaitForExit(); // I've tried it without this line but it doesn't really help
}
}
// Show the console output
void Display(string output)
{
Console.Out.WriteLine(output);
}
The server exe starts perfectly fine, but it stops writing to the console, as soon as the StdOut should appear. (there are some StdErr messages before the StdOut messages which are still shown in the console window).
This is understandable, because I've only enabled p.StartInfo.RedirectStandardOutput = true in my code, so the StdErr channel is not affected by it.
The problem is that the redirected Output never shows up in the Display function. It just vanishes into thin air.
If I put a breaking point into the Display function it never gets called untill I close the server.exe. After that it gets called, but the parameter is null.
Unfortunately I don't have any further knowledge about the game server exe.
What am I missing ?
Edit: Turns out that redirecting the output of other exe files (e.g. cmd exe) works just fine. Is there another way I could read the console in my C# application? Maybe just "copy" the output instead of redirecting it completely?
I need to execute external program with arguments and get result from it (~1000 times with different arguments).
I found solution like this:
using System.Diagnostics;
...
Process process = new Process();
// Configure the process using the StartInfo properties.
process.StartInfo.FileName = "process.exe";
process.StartInfo.Arguments = "qwe 123";
process.Start();
process.WaitForExit();// Waits here for the process to exit.
// And check exit code for result
I need many repetitions with different arguments, but this code every time initiate new process. It is very expensive operation. I think, i can "load" ("save") process and repeat it without everytime initiating.
Or maybe exist other way to solve this problem?
If the started process is under your control it will be much more efficient to pass the input not via command line parameter for a new process instance every time, but re-program it so that the process reads its standard input line-wise in a loop, processes each line, and writes the result to its standard output. It's easy to hook up the calling program to the process' input and output:
// ...
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardInput = true;
// ...
process.Start();
StreamReader results = process.StandardOutput;
StreamWriter processInput = process.StandardInput;
foreach( var arg in args )
{
processInput.WriteLine(arg);
var oneResult = results.ReadLine();
// do something with this oneResult
}
This example assumes that each argument fits in one line (and each result fits in a line, too). Writing and reading a single line each time is our simple "protocol" for knowing when to start processing (on the process side) and when the result is complete (on the C# side).
I should perhaps add that a real program should add error handling and e.g. evaluate Process.Start()'s return value.
I am aware that this may on first glance look like a question you have seen before:
Knowing when an external process' window shows
But this is slightly different.
I have an C# asp.net web application, for helping people create an installer for their programs. (The developers here are mostly mechanical engineers scripting equations in some calculation tools, they are not software people, so we don't want them spending time learning wix, debugging the installers, maintaing GUID's between releases, and so on..)
The serverside will be running the console application "heat.exe" (a tool that is shipped with the wix tools), to harvest information on how to register dll's etc., if and only if they have a dll in their repository..
I do it like this:
public int runHeat(string filePath, string outputFile, ref string response)
{
response += "run heat.exe to harvest file data" + '\r' + '\n';
string args = "file " + '"' + filePath + '"' + " -srd -out" + '"' + outputFile + '"';
string command = Path.Combine(WixBinariesPath, "heat.exe");
string workPath = Path.GetDirectoryName(filePath);
StringBuilder outputBuilder;
ProcessStartInfo processStartInfo;
Process process;
outputBuilder = new StringBuilder();
processStartInfo = new ProcessStartInfo();
processStartInfo.CreateNoWindow = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardInput = true;
processStartInfo.UseShellExecute = false;
processStartInfo.WorkingDirectory = workPath;
processStartInfo.Arguments = args;
processStartInfo.FileName = command;
processStartInfo.ErrorDialog = false;
//create the process handler
process = new Process();
process.StartInfo = processStartInfo;
// enable raising events because Process does not raise events by default
process.EnableRaisingEvents = true;
// attach the event handler for OutputDataReceived before starting the process
process.OutputDataReceived += new DataReceivedEventHandler
(
delegate(object sender, DataReceivedEventArgs e)
{
// append the new data to the data already read-in
outputBuilder.AppendLine(e.Data);
}
);
// start the process
// then begin asynchronously reading the output
// then wait for the process to exit
// then cancel asynchronously reading the output
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
// use the output
response += outputBuilder.ToString();
if (process.ExitCode != 0)
response += '\r' + '\n' + "heat.exe exited with code: " + process.ExitCode;
process.CancelOutputRead();
return process.ExitCode;
}
I thought this worked..
It passed tests, it's been running for a while without problems, and then suddenly, a developer called, that the webtool I made, no longer produces wix xml for him..
When I logged into the server, I found this dialog:
and then clicked [OK] - the web application then continued, and produced the xml, and stuff worked..
I have now found the dll that, makes heat throw this error. It doesn't really need registering (typical right?). So I could probably just write a timeout thing, to kill heat.exe if it takes to long, and thus unlock the waiting script, (and basicly fix the issue untill it happens again with a dll that actually needs registering) But that is not really detecting the error, that is just detecting that stuff takes time...
On this error, I would like to continue the script, but present a warning to the user, that heat.exe failed to run on that particular file. But to do this I need my asp.net application to know that this error was invoked, and dispose it, so that the script can continue..
how the *? do I get information that this runtime error occurred, so I can handle it from the server script?
Have you tried using the -sreg command line option to heat?
I now have, and as a result, heat.exe no longer chrashes, but this is not a solution, as heat also avoids harvesting the registry information that I need for autoregistering the dll's shipped with the code in question.
Working with external "uncooperative" executables often requires some trickery. I'd try the following:
Start the program on the command line, and check if there is any output when the error occurs. Probably it writes to standard error, and you could use RedirectStandardError, read the stream and hopefully get a clue when the error occurs.
Check if there is any logging-possibility in heat.exe that you could enable, and use this to detect the error-case. Maybe a verbose setting, or a log-file...
If none of the above worked, I'd use process monitor (e.g. https://technet.microsoft.com/de-at/sysinternals/bb896645.aspx). Start process monitor and then your application and bring it to the point of error. Filter the enormous output in process monitor to just your application (which is still quite a lot) and search at the end, whether there is any access where the programm might log the error. Maybe some log-files, or a logging-service. You could check this file after your timeout.
But something the would work in any case is the hack you already suggested in your question. To detect whether the dialog opened. There are also possibilities to browse through the content of the dialog box, so you could also read the text and check which kind of error it is. I used this once in production code to get the progress of an external program, which was written in a text field inside the form. I used spy++ (bundled with Visual Studio) to get the name/id of the text field, and accessed it using the (native) windows API. An ugly hack, but worked fine unless the external program's UI is changed.
In your case it is a standard Error Dialog, so it should stay quite consistent.
I've been reading on how to execute Win32 command-line programs from within the C# ASP.NET application, and for some reason, this code below never completes during execution, being stuck indefinitely on output = p.StandardOutput.ReadToEnd(); line.
The code works well when executed from a local debugging server (pressing F5 from Visual Studio), but when it's accessed through the browser via full URL (myexample.com/testapp), debugger never moved past the output=... line.
ProcessStartInfo info = new ProcessStartInfo(#"FetchIntranetPageSpecial.exe");
info.Arguments = "hostname";
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.RedirectStandardInput = true;
info.CreateNoWindow = true;
Process p = System.Diagnostics.Process.Start(info);
p.Start();
// Capture the output
output = p.StandardOutput.ReadToEnd(); // debugger does not move past this line
p.Close();
The .exe is a straightforward http request app, does not access to local files or anything, outputs result to console.
I thought this could be a permissions issue at first, so for debugging purposes set FetchIntranetPageSpecial.exe permissions to "EVERYONE, EVERYTHING" - still works fine when accessed through localhost, but still hangs when accessed remotely.
Any pointers on what I could try next?
EDIT: I also read this page Program doesn’t terminate when using processes, but in that case, the debugger goes into indefinite "waiting" state on this line:
while (read.Peek() >= 0) // stuck on this line
Console.WriteLine(read.ReadLine());