C# Process.StandardError reading without blocking - c#

I am a programming teacher and I am developing an automated checker for the students HWs (All are Console Apps). Basically, I run the student code using some Process.Start() (with some input file) and check if its output is as expected.
If, for some reason, the student code did no complete succesfully, I send them the exception thrown by their code using the process Standart Error redirection.
However, I want to do better then that. I want to find the input line written to the process StandartInput afterwhich their exception took place. How can I do that? My current code is more or less:
ProcessStartInfo psi = new ProcessStartInfo(students_exe_path);
psi.UseShellExecute = false;
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
String randomInputFile = randomInputFilesFolder + "\\" + "test.txt";
psi.WorkingDirectory = randomInputFilesFolder;
Process p = Process.Start(psi);
StreamWriter inputWriter = p.StandardInput;
String[] inputLines = File.ReadAllLines(randomInputFile);
foreach (String line in inputLines)
{
// here I wanted to read the standart error so far (before writing next input line)
String errorTextBefore = p.StandardError.ReadToEnd(); // can not use this because blocks progress.
inputWriter.WriteLine(line); // sending next input line to students app
// here I wanted to read the standart error so far (after writing next input line)
String errorTextAfter = p.StandardError.ReadToEnd(); // can not use because blocks
if (errorTextBefore != errorTextAfter) Console.WriteLine("Line {0} caused the exception",line);
}
Or maybe I can check from p.StandartInput which text was not "consumed" by the process?
Tamir

Related

Execute python from C#

im trying to execute some python code from C#, so i have this code :
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = #"C:\Users\protieax\PycharmProjects\untitled\venv\Scripts\python.exe";
start.Arguments = "request.py 31.12.2016 01.01.2017 datatype"; // give filename, dates from the UI to python and query datatype
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
using (Process process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
Console.Write(result);
}
}
It should work but the C# doesnt find the python file.
What i should put in those 2 variables? Thanks
EDIT :
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = #"U:\Documents\entsoe-py-master\tests\test_data";
start.Arguments = "request.py 31.12.2016 01.01.2017 datatype"; // give filename, dates from the UI to python and query datatype
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
using (Process process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
Console.Write(result);
}
}
Here is my code modified with your answers. I have a new error :
System.ComponentModel.Win32Exception: 'The system cannot find the file specified'
At the line
using (Process process = Process.Stqrt(start))
You specified a U drive, presumably a network drive. I've never used a network drive in a context like this one but I tried it to find out if that's the problem. I mounted a network drive with U being the label. After running the code, it failed and gave me the same error. I advise making a copy of the desired python script into your current working directory instead, it would make things a lot more simple.

Multiple process commands fail to get back any response

Having a challenge being able to send commands to cmd.exe from via the C# Process class.
Basically I want to call R.exe and then send it several commands to stage the data before I run some R functions and then pull out the result.
But I can't get the result back from a simple 'dir' statemenet :(
Process p = new Process();
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "cmd.exe";
info.RedirectStandardInput = true;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
string pathToR = #"C:\Program Files\R\R-3.3.1\bin";
p.StartInfo = info;
p.Start();
List<string> output = new List<string>();
using (StreamWriter sw = p.StandardInput)
{
if (sw.BaseStream.CanWrite)
{
sw.WriteLine("cd " + pathToR);
sw.WriteLine("dir");
while (p.StandardOutput.Peek() > -1)
{
var peekVal = p.StandardOutput.Peek();
output.Add(p.StandardOutput.ReadLine());
}
}
}
foreach (var line in output)
{
Console.WriteLine(line);
}
p.StandardInput.Close();
p.StandardOutput.Close();
p.WaitForExit();
p.Close();
Console.ReadLine();
Output:
Microsoft Windows [Version 10.0.10586]
(c) 2015 Microsoft Corporation. All rights reserved.
c:\users\micah_000\documents\visual studio 2015\Projects\RStagingTestApp\RStagingTestApp\bin\Debug>cd C:\Program Files\R\R-3.3.1\bin
The process tried to write to a nonexistent pipe.
The process tried to write to a nonexistent pipe.
The process tried to write to a nonexistent pipe.
The process tried to write to a nonexistent pipe.
The process tried to write to a nonexistent pipe.
I've seen several variations on this result, but I've never seen any kind of response from my commands :(
A lot of places say the async reads work best. I've seen some persuasive, comprehensive explanations for this, but I haven't been able to get it to work for me.
This one seemed to work. It might have been setting AutoFlush to true. Or just reading once at the end.

Calling python script from C# doesn't work, without throwing error

I am writing a C# program to execute a python script with some arguments. The program is not executed(it is supposed to not only print out a message, but also to write to a file), even though there is no error and the Process ExitCode is 0(checked via the debugger). Where am I going wrong?
static private string ExecutePython(string sentence) {
// full path of python interpreter
string python = #"C:\Python27\python.exe";
// python app to call
string myPythonApp = #"C:\Users\user_name\Documents\pos_edit.py";
// Create new process start info
ProcessStartInfo myProcessStartInfo = new ProcessStartInfo(python);
// make sure we can read the output from stdout
myProcessStartInfo.UseShellExecute = false;
myProcessStartInfo.RedirectStandardOutput = true;
myProcessStartInfo.Arguments = string.Format("{0} {1}", myPythonApp, sentence);
Process myProcess = new Process();
// assign start information to the process
myProcess.StartInfo = myProcessStartInfo;
// start process
myProcess.Start();
// Read the standard output of the app we called.
StreamReader myStreamReader = myProcess.StandardOutput;
string myString = myStreamReader.ReadToEnd();
// wait exit signal from the app we called
myProcess.WaitForExit();
// close the process
myProcess.Close();
return myString;
}
You've put myProcess.WaitForExit(); in the wrong place; wait till Python has executed the script:
...
myProcess.Start();
StreamReader myStreamReader = myProcess.StandardOutput;
// first, wait to complete
myProcess.WaitForExit();
// only then read the results (stdout)
string myString = myStreamReader.ReadToEnd();
...
Works perfectly fine with me. I think there's something with your user_name containing spaces.

C# Redirecting process results to a text file

I've been racking my brain trying to figure out why I can not use >> (append) as a part of my p.StartInfo.Argument. When I remove ">" it works perfectly, but the moment I try to use ">" or ">>" I get a "Incorrect Parameter" error. I've gone ahead and wrote my program without > or >> and just stored the output into a string and write it to a file later. Could someone explain why ">" or ">>" would not work in this situation?
// 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 = "attrib.exe";
startInfo.Arguments = "/S *.jpg > mypics.txt";
p.Start();
The output redirection operator > is a feature of cmd.exe, not the operating system. The naive way to use it is hence to call cmd as so:
p.StartInfo.FileName = "cmd.exe";
startInfo.Arguments = "/C \"attrib.exe /S *.jpg > mypics.txt\"";
The proper way to redirect the output, however, is to first set StartInfo.RedirectStandardOutput to true (which you did), and then pipe the output from Process.StandardOutput to the file, as so:
using(StreamWriter file = new StreamWriter("mypics.txt")) {
p.StandardOutput.CopyTo(file);
}
Or, async version:
using(StreamWriter file = new StreamWriter("mypics.txt")) {
await p.StandardOutput.CopyToAsync(file);
}
That's because > and >> are not arguments interpreted by attrib.exe, they're instructions to cmd.exe.
You could try instead something like:
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c attrib.exe /S *.jpg > mypics.txt";
Also, this is a red herring:
p.StartInfo.RedirectStandardOutput = true;
If you wanted to read the output in C# code and write the file yourself, you'd use this option. It's not helpful for using the > output redirector.
SlugFiller's solution is good but doesn't work reliably with large amounts of output. The problem is that if the process outputs too much text it hangs because the stream reaches the maximum buffer size. In other words the stream needs to be spooled out as the process runs, preventing the buffer from ever filling up.
However, there is a way to do this using a task to receive data from the output stream and spool it out in real-time.
// 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 = "attrib.exe";
startInfo.Arguments = "/S *.jpg > mypics.txt";
p.Start();
Thread stdoutThread = new Thread(new ThreadStart(WriteStandardOutput));
stdoutThread.IsBackground = true;
stdoutThread.Name = "stdout_writer";
stdoutThread.Start();
private void WriteStandardOutput()
{
using (StreamWriter sw = File.CreateText(ConsoleLogFileFullPath))
using (StreamReader sr = p.StandardOutput)
{
for (;;)
{
string line = sr.ReadLine();
if (line == null)
break;
sw.WriteLine(textLine);
}
sw.Flush();
}
}
At some point be sure to call stdoutThread.Join() to make sure that the thread completes. I have tested this with some code that generates large amounts of output and it's still possible to overflow the buffer, or to break ReadLine() with extremely long lines, but it does work for very fast output in the order of a few thousand 80 character lines per second.

Running Phantomjs using C# to grab snapshot of webpage

I'm trying to grab snapshots of my own website using phantomjs - basically, this is to create a "preview image" of user-submitted content.
I've installed phantomjs on the server and have confirmed that running it from the command line against the appropriate pages works fine. However, when I try running it from the website, it does not appear to do anything. I have confirmed that the code is being called, that phantom is actually running (I've monitored the processes, and can see it appear in the process list when I call it) - however, no image is being generated.
I'm not sure where I should be looking to figure out why it won't create the images - any suggestions? The relevant code block is below:
string arguments = "/c rasterize.js http://www.mysite.com/viewcontent.aspx?id=123";
string imagefilename = #"C:\inetpub\vhosts\mysite.com\httpdocs\Uploads\img123.png";
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.CreateNoWindow = false;
p.StartInfo.FileName = #"C:\phantomjs.exe";
p.StartInfo.Arguments = arguments + " " + imagefilename;
p.Start();
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
I check the errors that phantomjs throws during its process.
You can read them from Process.StandardError.
var startInfo = new ProcessStartInfo();
//some other parameters here
...
startInfo.RedirectStandardError = true;
var p = new Process();
p.StartInfo = startInfo;
p.Start();
p.WaitForExit(timeToExit);
//Read the Error:
string error = p.StandardError.ReadToEnd();
It will give you an idea of what happened
The easiest way for executing phantomjs from C# code is using wrapper like NReco.PhantomJS. The following example illustrates how to use it for rasterize.js:
var phantomJS = new PhantomJS();
phantomJS.Run( "rasterize.js", new[] { "https://www.google.com", outFile} );
Wrapper API has events for stdout and stderr; also it can provide input from C# Stream and read stdout result into C# stream.

Categories