I have a C# program that wants to interact with an external process written in C++. I believe this C++ process is using correct standard input. I just can't seem to get my C# code to not hang when trying to write to Process.StandardInput.
I've seen countless examples using Process.StandardInput.Close() when done writing. Every StackOverflow answer I found says to use this, and it does work. The problem is I can't close the StreamWriter because I'm not done interacting with the process. The process is a state machine that holds variables created using stdin, parses expressions, and returns an evaluation. I am expected to keep giving the process input after each output.
Does anyone have an example where Process.StandardInput.WriteLine is used more than once without closing or restarting the process?
This is how the C++ process is reading input. This example simply echos back the input and waits for another.
int main () {
std::string input;
while (getline(std::cin, input)) {
std::cout << input << std::endl;
}
}
My C# program tries to interact with this process using this wrapper class.
public class Expression {
System.Diagnostics.Process p;
public Expression () {
p = new System.Diagnostics.Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.FileName = "InputEcho.exe";
p.Start();
p.StandardInput.AutoFlush = true;
}
public void Run (in string input, out string output) {
p.StandardInput.WriteLine(input);
// p.StandardInput.Close();
output = p.StandardOutput.ReadToEnd();
}
}
This works when I uncomment p.StandardInput.Close() but then subsequent calls to Expression.Run() won't work because the writer is closed.
Main program
Expression expn = new();
string output;
Console.WriteLine("Expression start");
expn.Run("Hello", output);
Console.WriteLine(output);
expn.Run("Hi", output);
Console.WriteLine(output);
Expected output
Expression start
Hello
Hi
Actual output
Expression start
EDIT:
#Matthew Andrews provided a really good answer that works, but it's not quite what I'm after. I didn't think about using event delegates to receive output data, and I see why: It's hard to implement this into the wrapper that I want to use to build a process-relevant API. What I mean by this is that I want to write some method that communicates with the process, give it input, receive the output, and return this data to the caller before doing anything else. My Expression.Run method exemplifies this perfectly.
Here's an example of what the root caller would look like in a greater C# program.
bool GetConditionEval (string condition, SomeDataType data) {
// Makes another call to 'Run' that commands the C++ process to store a variable
// Input looks like this: "variableName = true" (aka key/value pairs)
Expression.SetVar(data.name, "true");
// Don't ask why I'm using an external process to set variables using string expressions.
// It's a company proprietary thing.
string output;
Expression.Run(in condition, out output);
if (output.ToLower() == "true") return true;
else if (output.ToLower() == "false") return false;
else throw new Exception("Output is something other than true or false.");
}
This is why I'd like for Run to immediately return the output it receives from the process.
If not, I guess I could find a way for a delegate method to store the output in a global container and the GetConditionEval can just reach into that. I worry about race conditions though.
Side note:
Since I do expect the API that is contained in this C++ process to eventaully take other forms, spinning this up as a standalone process and invoking the API via stdin is really a stopgap for now so I don't have to convert thousands of lines of C++ code into C#.
SOLUTION:
I figured out a solution using the asynchronous method Matthew suggested while having a linear process of sending input and working immediately off the output in the same sequence. I reconfigured my wrapper class to queue each output received from the event listener. This sets up a pattern where I can call one method to send input, and then call another method right after to pop output data off the queue if any. I compensated for the fact that output data might not be avaliable immediately by simply waiting if the queue is empty and then moving forward once something is there. This unfortuately makes it a blocking call if it does have to wait, but it's the best I have so far. I also implemented a failsafe so it doesn't wait indefinately.
public class Expression {
System.Diagnostics.Process p = new();
System.Collections.Generic.Queue<string> outputQ = new();
public Expression () {
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.FileName = "C2E2.exe";
p.OutputDataReceived += (s, e) => {
outputQ.Enqueue(e.Data);
};
p.Start();
p.BeginOutputReadLine();
}
/// Returns custom exception object if error is encountered.
public GRLib.Exception Run (in string input) {
if (p == null) return GRLib.Exception.New("Expression Evaluator not operational.");
try {
p.StandardInput.WriteLine(input);
}
catch (Exception e) {
return GRLib.Exception.New(e.Message);
}
return null;
}
/// Returns error code 1 if timeout occured.
/// Timeout is represented in milliseconds.
/// Blocking call.
public GRLib.Exception GetOutput (out string output, int timeout = 2000) {
/// Wait for something to show in the queue.
/// Waits indefinitely if timeout is 0.
/// If anyone knows a better way to implement this waiting loop,
/// please let me know!
int timeWaited = 0;
while (outputQ.Count == 0) {
System.Threading.Thread.Sleep(100);
if (timeout != 0 && (timeWaited += 100) > timeout) {
output = "ERR";
return GRLib.Exception.New(1, "Get timed out.");
}
}
output = outputQ.Dequeue();
return null;
}
...
}
Example usage
Expression expression = new();
var e = expression.Run("3 > 2");
if (e != null) // Handle error
string output;
e = expression.GetOutput(out output);
if (e != null) // Handle error
// 'output' should now be 'true' which can then be used in other parts of this program.
While the event listener in a standalone fashion works great, I need the output from the process to be returned in the same stack where the input is given because this is going to be part of a more complex call graph.
The problem you're observing is due to the synchronous nature of Process.StandardOutput.ReadToEnd(). Instead, you should listen for your output asynchronously by setting Process.BeginOutputReadLine() and utilizing the Process.OutputDataReceived event.
Here is a quick example to get you started:
var p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = #"ConsoleApplication1.exe";
p.OutputDataReceived += (s, e) =>
{
Console.WriteLine(e.Data);
};
p.Start();
p.BeginOutputReadLine();
while (true)
{
var readLine = Console.ReadLine();
p.StandardInput.WriteLine(readLine);
}
And here is the c++ I used for ConsoleApplication1.exe:
int main()
{
std::cout << "Hello World!\n";
std::string input;
while (std::getline(std::cin, input)) {
std::cout << input << std::endl;
}
}
Running my example will print Hello World! and then proceed to parrot whatever else you enter into the console.
Related
I'm using powershell in C# with system.management.automation and I can access both the output and the error stream sucessfully. For most applications this is great but i'm right now in a situation where I need to get the output of a powershell command while it is running in c# and i'm lost.
I've tried subscribing to outputcollection.DataAdded, i've tried subscribing to the powershell instance verbose stream, but neither of them are getting called when powershell gives an output.
Here's the code I have so far
public async Task<string> CMD(string script)
{
ps = PowerShell.Create();
string errorMsg = "";
string output;
ps.AddScript(script);
ps.AddCommand("Out-String");
PSDataCollection<PSObject> outputCollection = new();
ps.Streams.Error.DataAdded += (object sender, DataAddedEventArgs e) =>
{ errorMsg = ((PSDataCollection<ErrorRecord>)sender)[e.Index].ToString(); };
IAsyncResult result = ps.BeginInvoke<PSObject, PSObject>(null, outputCollection);
while (!result.IsCompleted)
{
await Task.Delay(100);
}
StringBuilder stringBuilder = new();
foreach (PSObject outputItem in outputCollection)
{
stringBuilder.AppendLine(outputItem.BaseObject.ToString());
}
output = stringBuilder.ToString();
//Clears commands added to runspace
ps.Commands.Clear();
Debug.WriteLine(output);
if (!string.IsNullOrEmpty(errorMsg))
MessageBox.Show(errorMsg, "Error");
return output.Trim();
}
I've also tried checking the outputcollection in the while loop but it doesn't give me the output until the command is done.
The command i'm trying to use is Connect-ExchangeOnline -Device
To simulate it in C# it would work the same as doing sleep 5;echo test;sleep 5
where I then want the program to display test after 5 seconds not after the full 10 seconds.
EDIT:
When using "Connect-ExchangeOnline -Device" powershell will deliver this output and wait for the user to complete said task. The issue being that I can't display this in C# because my C# code waits for the powershell command to be finished. And outputcollection.DataAdded never seems to be called.
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code CDWS27A56 to authenticate.
Unfortunately, Connect-ExchangeOnline is meant only for interactive console use, and specifically gets around allowing the output to be captured (Possibly by writing directly to the $host window it was called from?).
Normally, you could try using Tee-Object or Start-Transcript/Stop-Transcript with redirection to dump all output:
Connect-ExchangeOnline -Device *>&1 | Tee-Object -FilePath C:\temp\tee.txt -Append
# Then from another process:
Get-Content C:\temp\tee.txt
Or try starting a powershell Job, which keeps all of its output in the job object's properties:
$job = Start-ThreadJob -Name Connecting -ScriptBlock { Connect-ExchangeOnline -Device }
# Wait for prompt...
$job.Output
$job.Information
However, neither of these actually grab the device authentication code.
Currently to use -Device, you need to have a visible powershell window and have the user complete their device authentication there.
You can always use one of the other authentication types:
Connect-ExchangeOnline -UserPrincipalName username#domain.tld
This version will automatically launch a Modern Authentication prompt or a browser page. Depending on your use case, it is effectively the same.
I would pipe the script outputs to a file, then have your c# code read that file, and filter out the code.
Alternatively you could use a class that exposes 2 StringBuilders as properties through which you can use to get the script output and filter out the code:
using System.Diagnostics;
using System.Text;
public sealed class ProcessOptions
{
public bool WaitForProcessExit { get; set; }
public bool Executing { get; internal set; } = true;
}
public class InvokePowershell
{
public static StringBuilder stdout { get; private set; } = null;
public static StringBuilder stderr { get; private set; } = null;
public void string Start(string script)
{
var process = new Process();
var options = new ProcessOptions()
{
WaitForProcessExit = true,
};
process.StartInfo.FileName = "powershell"; // (or pwsh for powershell core).
process.StartInfo.Arguments = script;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = true; // true does not always work so use caution with this.
process.StartInfo.CreateNoWindow = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.WorkingDirectory = Directory.GetCurrentDirectory();
process.Shell(options)
}
private static void Shell(this Process process, ProcessOptions options)
{
if (stdout is not null && stderr is not null)
{
stdout = null;
stderr = null;
}
process.OutputDataReceived += (_, e) =>
{
if (e.Data is null)
{
return;
}
if (stdout is null)
{
stdout = new StringBuilder();
}
else
{
stdout.AppendLine();
}
stdout.Append(e.Data);
};
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is null)
{
return;
}
if (stderr is null)
{
stderr = new StringBuilder();
}
else
{
stderr.AppendLine();
}
stderr.Append(e.Data);
};
process.Start();
options.Executing = false;
if (process.StartInfo.RedirectStandardError)
process.BeginErrorReadLine();
if (process.StartInfo.RedirectStandardOutput)
process.BeginOutputReadLine();
if (options.WaitForProcessExit)
process.WaitForExit();
}
}
And then you could then make a way to Fire and forget that code (so that way it does not get blocked until powershell exits, then simply in the code you can do the following in your normal code:
while (InvokePowershell.stdout is null || InvokePowershell.stdout.Length < /* length you expect it to be*/)
{
// do nothing but by wait by sleeping for a few milliseconds to avoid wasting cpu cycles with this.
}
// strip the code from the stdout property then use it.
I find doing something like this is much more cleaner, plus then it could easily be ported to powershell core by changing powershell on the Start function to use pwsh which is the program name for powershell core and plus then the code would work on all platforms that powershell core supports which are Windows, Mac, and various other linux distributions out there.
Additionally for this to work powershell or even pwsh if powershell core is wanted to be used instead of powershell that the program must be in the PATH environment variable so it can be invoked inside of the terminal directly.
Also with the code above, you could theoretically not Wait for process exit, however I do not know if those events would trigger and populate the StringBuilders then, likewise the process instance would leave scope and be GC'd resulting in the events also getting GC'd and then the StringBuilders never getting assigned to.
As such that is why I recommend calling InvokePowershell.Start(script); as a delegate as a fire-and-forget call. Then doing a loop that checks if null or is smaller than the expected length of the string outputs then sleep for a few cpu clockcycles (each clockcycle is less than a second), and then filtering out the results from there after that while loop ensures that it is populated for the preprocessing that comes after the loop.
Edit: Instead of having to call InvokePowershell.Start(script); in a fire-and-forget, you can replace the call to process.WaitForExit() and the if check for it entirely with the while loop shown above, pass in the length you expect to the method, and to the (Shell method by adding it as a parameter to it and remove the options argument, instantiation, and type entirely), and then after the while loop breaks (to allow time for the event handlers to add what you need to the property's stringbuilders, you can call process.Kill(); to kill powershell or powershell core.
You can use the code below to get the output of a PowerShell command in real time from a C# application.
This uses a PowerShell Pipeline, which allows you to call a notification handler whenever the PowerShell command/script writes output into the Pipeline. I've implemented the solution below as an async enumerable but if you wanted something non-async you can also just use the Pipeline.Output.DataReady handler to trigger some code to read from the pipeline.
https://gist.github.com/OnKey/83cf98e6adafe5a2b4aaf561b138087b
static async Task Main(string[] args)
{
var script = #"
For ($i=0; $i -le 5; $i++) {
$i
Start-Sleep -s 1
}
";
var p = new Program();
await foreach (var item in p.PowerShellAsyncEnumerable(script))
{
Console.WriteLine(item);
}
}
private IAsyncEnumerable<PSObject> PowerShellAsyncEnumerable(string script)
{
var rs = RunspaceFactory.CreateRunspace();
rs.Open();
var pipeline = rs.CreatePipeline();
pipeline.Commands.AddScript(script);
return new PsAsyncEnumerable(pipeline);
}
internal class PsAsyncEnumerable : IAsyncEnumerable<PSObject>
{
private readonly Pipeline pipe;
public PsAsyncEnumerable(Pipeline pipe) => this.pipe = pipe;
public IAsyncEnumerator<PSObject> GetAsyncEnumerator(CancellationToken cancellationToken = new())
=> new PsAsyncEnumerator(this.pipe);
}
internal class PsAsyncEnumerator : IAsyncEnumerator<PSObject>
{
private readonly Pipeline pipe;
private TaskCompletionSource dataReady = new();
public PsAsyncEnumerator(Pipeline pipe)
{
this.pipe = pipe;
this.pipe.Output.DataReady += NotificationHandler;
this.pipe.Error.DataReady += NotificationHandler;
this.pipe.InvokeAsync();
}
private void NotificationHandler(object sender, EventArgs e)
{
this.dataReady.SetResult();
}
public ValueTask DisposeAsync()
{
this.pipe.Dispose();
return ValueTask.CompletedTask;
}
public async ValueTask<bool> MoveNextAsync()
{
while (!this.pipe.Output.EndOfPipeline)
{
var item = this.pipe.Output.NonBlockingRead(1).FirstOrDefault();
if (item != null)
{
this.Current = item;
return true;
}
await this.dataReady.Task;
this.dataReady = new TaskCompletionSource();
}
return false;
}
public PSObject Current { get; private set; }
}
1. In C# Start BackgroundWorker bw;
2. In bw.DoWork(...) Start PowerShell.
3. In PowerShell write to a File and close it.
4. In the Main thread of C# read the File.
===
using System.ComponentModel;
BackgroundWorker bw = new();
bw.DoWork += Bw_DoWork;
private void Bw_DoWork(object sender, DoWorkEventArgs e)
{
<Start ps>
}
=== in ps
Set obj=CreateObject("Scripting.FileSystemObject")
outFile="C:\File.txt"
Set objFile = obj.CreateTextFile(outFile,True)
objFile.Write "test string"
objFile.Close # it makes file accessible outside ps
=== In the Main thread
<read the C:\File.txt>
===
I have asked this question the other day, but neither I had an answer nor could I made it work. So I tried to slim it down as it was a lot of noise in the question.
Thing is, if I expose in a web api a all to a method that runs cmd.exe it works fine if I don't call it two times per request.
I mean, this code works fine:
public class FilesController : ApiController
{
private readonly IRunner _runner;
public FilesController(IRunner runner)
{
_runner = runner;
}
public string Get()
{
return _runner.GetFiles();
}
}
public class Runner : IRunner
{
public Runner()
{
//var cd = #"cd C:\DummyFolder";
//RunCmdPromptCommand(cd);
}
public string GetFiles()
{
var dir = #"cd C:\DummyFolder & dir";
//var dir = "dir";
return RunCmdPromptCommand(dir);
}
private string RunCmdPromptCommand(string command)
{
var process = new Process
{
StartInfo =
{
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
RedirectStandardError = true,
RedirectStandardOutput = true,
FileName = #"cmd.exe",
Arguments = string.Format("/C {0}", command)
}
};
process.Start();
var error = process.StandardError.ReadToEnd();
if (!string.IsNullOrEmpty(error))
{
throw new Exception(error);
}
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return output;
}
}
But if I uncomment the lines commented (and obviously comment out the first line of GetFiles, when the code reaches for the second time (i.e. with "dir") the RunCmdPromptCommand it gets stuck in the line where it tries to read the standard error.
I don't know why, and I don't know how to force the exit whenever it could happen (might be other scenarios that can happen)
Thanks,
This is because the:
process.StandardOutput.ReadToEnd();
Is a synchronous operation.
Excerpt from MSDN:
The redirected StandardError stream can be read synchronously or
asynchronously. Methods such as Read, ReadLine, and ReadToEnd perform
synchronous read operations on the error output stream of the process.
These synchronous read operations do not complete until the associated
Process writes to its StandardError stream, or closes the stream.
In other words, as long as the process doesn't write any standard error or closes the stream, it will get stuck there forever.
To fix this, I recommend to use Async BeginErrorReadLine. Excerpt from MSDN:
In contrast, BeginErrorReadLine starts asynchronous read operations on
the StandardError stream. This method enables a designated event
handler for the stream output and immediately returns to the caller,
which can perform other work while the stream output is directed to the event handler.
Which I think will be suitable for your need.
To use that. the example given in the MSDN is pretty straightforward. Check out especially these lines:
netProcess.ErrorDataReceived += new DataReceivedEventHandler(NetErrorDataHandler); //note this event handler add
if (errorRedirect) //in your case, it is not needed
{
// Start the asynchronous read of the standard
// error stream.
netProcess.BeginErrorReadLine(); //note this
}
And how to define the event handler:
private static void NetErrorDataHandler(object sendingProcess,
DataReceivedEventArgs errLine)
{
// Write the error text to the file if there is something
// to write and an error file has been specified.
if (!String.IsNullOrEmpty(errLine.Data))
{
if (!errorsWritten)
{
if (streamError == null)
{
// Open the file.
try
{
streamError = new StreamWriter(netErrorFile, true);
}
catch (Exception e)
{
Console.WriteLine("Could not open error file!");
Console.WriteLine(e.Message.ToString());
}
}
if (streamError != null)
{
// Write a header to the file if this is the first
// call to the error output handler.
streamError.WriteLine();
streamError.WriteLine(DateTime.Now.ToString());
streamError.WriteLine("Net View error output:");
}
errorsWritten = true;
}
if (streamError != null)
{
// Write redirected errors to the file.
streamError.WriteLine(errLine.Data);
streamError.Flush();
}
}
}
The project makes many different calls to the command line for a variety of purposes. To make this easier, I wrote a method that simply requires a person to enter the command in as a parameter :
public string AsyncCommandCall(string sCommand, List<string> lOutput, int timeout)
{
if (!sCommand.ToLower().Substring(0, 5).Contains("/k"))
sCommand = "/k " + sCommand;
using(Process process = new Process())
{
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = "cmd.exe";
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.UseShellExecute = false;
startInfo.Arguments = sCommand;
startInfo.CreateNoWindow = true;
process.StartInfo = startInfo;
List<string> output = new List<string>();
List<string> error = new List<string>();
using(AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
using(AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
{
process.OutputDataReceived += (sender, e) =>
{
if (e.Data == null)
{
outputWaitHandle.Set();
}
else
{
if(!String.IsNullOrEmpty(e.Data))
output.Add(e.Data);
}
};
process.ErrorDataReceived += (sender, e) =>
{
if(e.Data == null)
{
errorWaitHandle.Set();
}
else
{
output.Add(e.Data);
}
};
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
if(process.WaitForExit(timeout) && outputWaitHandle.WaitOne(timeout) && errorWaitHandle.WaitOne(timeout))
{
m_sCmdOutput.Clear();
m_sCmdError.Clear();
m_sCmdOutput.AddRange(output);
m_sCmdError.AddRange(error);
if(lOutput != null)
{
lOutput.AddRange(output);
}
return AggregateList(output);
}
else
{
process.Close();
//a time out doens't necessarily mean that stuff didn't happen, it's likely that it didn't process.
if(error.Count > 0)
{
m_sCmdError.Clear();
m_sCmdError.AddRange(error);
}
Debug("Thread time out for " + sCommand);
if (output.Count > 0)
{
m_sCmdOutput.Clear();
m_sCmdOutput.AddRange(output);
if (lOutput != null)
{
lOutput.AddRange(output);
}
return (AggregateList(output));
}
else
{
Debug("Returning null");
return null;
}
}
}
}
}
The reason I am calling it asynchronously is that some of the commands I'm calling aren't guaranteed to work, so this ideally would allow me to try again if it times out.
When running my program, I noticed that one command, "time /t" would always timeout.
To investigate, I tried running the code independently in the main loop of my program, and surprisingly it ran.
I became curious why this exact same command executed in once place while failing to run in another place. I ran another test where I placed the command call into a while loop, and soon found that the command calls stopped working as expected after exactly 4 AsyncCommandCall method calls. Looking back through my code, there were exactly 4 command calls before I called "time /t". I'm wondering if this is a bug in the api or if I'm doing something else wrong
Before anyone suggests it, I should also note that I did write a synchronous command call method that does not contain a "using" statement, but running it causes a hang on "process.WaitForExit()". Any help would be greatly appreciated.
EDIT
I noticed during my testing that if I increase the timeout that I pass as a parameter, more iterations are successfully called. Is there some kind of buffer that can be cleared so that the process time doesn't increase with each call?
As it turns out, this problem was dependent on the /k parameter that the method added to each command call. The /k flag tells the console to keep the output open, causing things like consistent timeouts using this asynchronous method, clogging system memory, and preventing process.WaitForExit() from returning. Instead, I am now using the /c flag before each command call and am successfully reading the output from each command. In calling AsyncCommandCall(command, null, 100) 1000 times in a row looping through three commands (An echo, a dir, and a psexec), there were 0 timeouts and 0 failed reads.
I have 2 applications, A & B.
A calls B within a Process.
B do some stuffs like Console.WriteLine and Console.ReadLine
Thanks to this MSDN Article, I manage somehow to redirect the output of B and to feed its input as well.
What I don't manage to do, is to have the Console.ReadKey function in B work. I made a try catch block arround this function and I got this error message:
Cannot read keys when either application does not have a console, or when console input has been redirected from a file. Try Console.Read
The fact is, I have to use Console.ReadKey, so I need to find a way to make it work...Any idea?
Here are the useful parts of the code of A
In the Main function:
Process P2 = new Process();
P2.StartInfo.FileName = Environment.CurrentDirectory + "\\Main2.exe";
P2.StartInfo.UseShellExecute = false;
P2.StartInfo.RedirectStandardOutput = true;
P2.OutputDataReceived += new DataReceivedEventHandler(WriteOutput);
P2.StartInfo.RedirectStandardInput = true;
P2.Start();
StreamWriter ExeInput = P2.StandardInput;
P2.BeginOutputReadLine();
ConsoleKeyInfo KeyPressed;
do
{
KeyPressed = Console.ReadKey();
if(KeyPressed.Key == ConsoleKey.Enter)
{
Console.WriteLine ();
ExeInput.Write("\n");
}
else
ExeInput.Write(KeyPressed.KeyChar);
} while (!P2.HasExited);
The handler for outputdatareceived:
private static void WriteOutput(object sendingProcess, DataReceivedEventArgs outLine)
{
if (!String.IsNullOrEmpty(outLine.Data))
{
Console.WriteLine(outLine.Data);
}
}
I'm not aware of any way of being able to use ReadKey() when redirecting the StdIn/StdOut of a console program. Furthermore, in order to read and write from the child process, you need to make sure you're using Console.Out.Write()/Console.In.Read() to prevent exceptions from being thrown from the child process, because it is lacking a console window.
You can use Convert.ToChar(ExeOutput.Read()) to convert the input to a valid KeyChar, mimicing the behavior of ReadKey() Also keep in mind Synchronous vs Asynchronous reads/writes. If you use BeginOutputReadLine() and read the stream Asynchronously, your while condition of P2.HasExited may become true before you read all of the input keys when using ExeOutput.Read()
.....
P2.Start();
StreamWriter ExeInput = P2.StandardInput;
StreamReader ExeOutput = P2.StandardOutput;
do
{
var k = P2.StandardOutput.Read();
var key = Convert.ToChar(k);
if (key == Convert.ToChar(ConsoleKey.Enter))
{
Console.WriteLine();
ExeInput.Write("\n");
}
else
ExeInput.Write(key);
} while (!P2.HasExited);
....
Fortunately, the streams will be buffered if the process has exited before you've read every line, so you may consider changing the condition to while(!P2.HasExited && !P2.StandardOutput.EndOfStream) if that fits what you're trying to accomplish.
I want to create a LaTeX editor to produce pdf documents.
Behind the scene, my application uses pdflatex.exe executed through a Process instance.
pdflatex.exe needs an input file, e.g., input.tex as follows
\documentclass{article}
\usepackage[utf8]{inputenc}
\begin{document}
\LaTeX\ is my tool.
\end{document}
For the sake of simplicity, here is the minimal c# codes used in my LaTeX editor:
using System;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Process p = new Process();
p.EnableRaisingEvents = true;
p.Exited += new EventHandler(p_Exited);
p.StartInfo.Arguments = "input.tex";
p.StartInfo.UseShellExecute = false;
p.StartInfo.FileName = "pdflatex.exe";
p.Start();
p.WaitForExit();
}
static void p_Exited(object sender, EventArgs e)
{
// remove all auxiliary files, excluding *.pdf.
}
}
}
The question is
How to detect the pdflatex.exe whether it stops working due to an invalid input?
Edit
This is the final working solution:
using System;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Process p = new Process();
p.EnableRaisingEvents = true;
p.Exited += new EventHandler(p_Exited);
p.StartInfo.Arguments = "-interaction=nonstopmode input.tex";// Edit
p.StartInfo.UseShellExecute = false;
p.StartInfo.FileName = "pdflatex.exe";
p.StartInfo.RedirectStandardError = true;
p.Start();
p.WaitForExit();
//Edit
if (p.ExitCode == 0)
{
Console.WriteLine("Succeeded...");
}
else
{
Console.WriteLine("Failed...");
}
}
static void p_Exited(object sender, EventArgs e)
{
// remove all files excluding *.pdf
//Edit
Console.WriteLine("exited...");
}
}
}
The idea using -interaction=nonstopmode belongs to #Martin here.
Most command-line applications set an exit code to indicate success or failure. You test it thus:
p.WaitForExit();
if (p.ExitCode == 0) {
// Success
} else {
// Failure
}
I suppose that you can understand if pdflatex has stopped working by looking at its output (e.g. matching an error message, seeing that it doesn't output anything for more than 30 seconds, something like that).
To be able to perform such checks, you should redirect the standard output and standard error of pdflatex (you can find many examples just by searching in SO, the key is the ProcessStartInfo.RedirectStandardOutput property) to a stream that you can read/a callback to a function of yours; in this way you should be able to detect the condition whence you deduce that pdflatex is stuck, and then you can kill it with p.Kill().
If you have a means to detect your process has stopped working, you can use
p.Kill();
to terminate the process
One way to go about it is a timeout. If you have a separate thread to launch this proces, you can start the thread and use
if(processThread.Join(waitTime))
{
// worked
}
else
{
// Timeout. need to kill process
}
where waitTime is of type TimeSpan
Time-outs are better suited for a shelled application that performs background processing. The following code sample sets a time-out for the shelled application. The time-out for the example is set to 5 seconds. You may want to adjust this number (which is calculated in milliseconds) for your testing:
//Set a time-out value.
int timeOut = 5000;
//Start the process.
Process p = Process.Start(someProcess);
//Wait for window to finish loading.
p.WaitForInputIdle();
//Wait for the process to exit or time out.
p.WaitForExit(timeOut);
//Check to see if the process is still running.
if (p.HasExited == false)
{
//Process is still running.
//Test to see if the process is hung up.
if (p.Responding)
{
//Process was responding; close the main window.
p.CloseMainWindow();
}
else
{
//Process was not responding; force the process to close.
p.Kill();
}
}
//continue