C# Problem Reading Console Output to string - c#

i want to launch ffmpeg from my app and retrive all console output that ffmpeg produces. Thing seems obvious, i followed many forum threads/articles like this one but i have problem, though i follow all information included there I seem to end up in dead end.
String that should contain output from ffmpeg is always empty. I've tried to see where is the problem so i made simple c# console application that only lists all execution parameters that are passed to ffmpeg, just to check if problem is caused by ffmpeg itself. In that case everything work as expected.
I also did preview console window of my app. When i launch ffmpeg i see all the output in console but the function that should recieve that output for further processing reports that string was empty. When my param-listing app is launched the only thing I see is the expected report from function that gets output.
So my question is what to do to get ffmpeg output as i intended at first place.
Thanks in advance
MTH

This is a long shot, but have you tried redirecting StandardError too?

Here is a part of my ffmpeg wrapper class, in particular showing how to collect the output and errors from ffmpeg.
I have put the Process in the GetVideoDuration() function just so you can see everything in the one place.
Setup:
My ffmpeg is on the desktop, ffPath is used to point to it.
namespace ChildTools.Tools
{
public class FFMpegWrapper
{
//path to ffmpeg (I HATE!!! MS special folders)
string ffPath = System.Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\ffmpeg.exe";
//outputLines receives each line of output, only if they are not zero length
List<string> outputLines = new List<string>();
//In GetVideoDuration I only want the one line of output and in text form.
//To get the whole output just remove the filter I use (my search for 'Duration') and either return the List<>
//Or joint the strings from List<> (you could have used StringBuilder, but I find a List<> handier.
public string GetVideoDuration(FileInfo fi)
{
outputLines.Clear();
//I only use the information flag in this function
string strCommand = string.Concat(" -i \"", fi.FullName, "\"");
//Point ffPath to my ffmpeg
string ffPath = System.Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\ffmpeg.exe";
Process processFfmpeg = new Process();
processFfmpeg.StartInfo.Arguments = strCommand;
processFfmpeg.StartInfo.FileName = ffPath;
//I have to say that I struggled for a while with the order that I setup the process.
//But this order below I know to work
processFfmpeg.StartInfo.UseShellExecute = false;
processFfmpeg.StartInfo.RedirectStandardOutput = true;
processFfmpeg.StartInfo.RedirectStandardError = true;
processFfmpeg.StartInfo.CreateNoWindow = true;
processFfmpeg.ErrorDataReceived += processFfmpeg_OutData;
processFfmpeg.OutputDataReceived += processFfmpeg_OutData;
processFfmpeg.EnableRaisingEvents = true;
processFfmpeg.Start();
processFfmpeg.BeginOutputReadLine();
processFfmpeg.BeginErrorReadLine();
processFfmpeg.WaitForExit();
//I filter the lines because I only want 'Duration' this time
string oStr = "";
foreach (string str in outputLines)
{
if (str.Contains("Duration"))
{
oStr = str;
}
}
//return a single string with the duration line
return oStr;
}
private void processFfmpeg_OutData(object sender, DataReceivedEventArgs e)
{
//The data we want is in e.Data, you must be careful of null strings
string strMessage = e.Data;
if outputLines != null && strMessage != null && strMessage.Length > 0)
{
outputLines.Add(string.Concat( strMessage,"\n"));
//Try a Console output here to see all of the output. Particularly
//useful when you are examining the packets and working out timeframes
//Console.WriteLine(strMessage);
}
}
}
}

Related

Capturing output from powershell script

I have been working on converting a GUI script from another language to C# in VS2017 for a customer. With help from the folks here I am 95% of the way there, but have run into a couple of snags; just not sure I am doing things in the best way. I'm including just the relevant portions of code below, please let me know if I am not providing enough:
The majority of the code is centered on the wpf form, which collects data for low level technicians to batch deploy a number of Virtual Machines into the VMware environment. This number could easily range into the dozens or even a hundred VMs at once. The information for each VM is specified in the form, then collected in a listview. Once the listview is fully populated it is exported to a csv. Up to this point everything works just fine.
I've next been working on actually launching the powershell/powerCLI script (also working) and capturing output. The log file is opened with a specific reader application the customer uses, which updates in real time, and the captured output is fed to the log. It is important for the technicians to see the output from the code line by line so they can react if there is an issue.
I started with something like this as a test:
string sPSScript = "C:\\Users\\Admin\\Desktop\\TestC#.ps1";
string logFile = "C:\\Users\\Admin\\Desktop\\My.log";
string logReader = "C:\\Users\\Admin\\Documents\\CMTrace.exe";
string standard_output;
System.Diagnostics.Process PSScript = new System.Diagnostics.Process();
PSScript.StartInfo.FileName =
Environment.GetFolderPath(Environment.SpecialFolder.SystemX86) +
"\\WindowsPowerShell\\v1.0\\powershell.exe";
PSScript.StartInfo.Arguments = "-command . '" + sPSScript + "' " +
vCenter.Text;
PSScript.StartInfo.RedirectStandardOutput = true;
PSScript.StartInfo.UseShellExecute = false;
PSScript.Start();
System.Diagnostics.Process LogFile = new System.Diagnostics.Process();
LogFile.StartInfo.FileName = logReader;
LogFile.StartInfo.Arguments = logFile;
LogFile.Start(); while ((standard_output =
PSScript.StandardOutput.ReadLine()) != null)
{
if (standard_output != "")
{
using (StreamWriter file = new StreamWriter(logFile, append: true))
{
file.WriteLine(standard_output);
}
}
}
While this writes to the log file in real time as expected, it creates 100 instances of the logReader application. I understand why, since I am declaring a new StreamWriter object through every pass, but am unsure how better to go about this.
I tried creating the file outside the loop, like this:
StreamWriter file = new StreamWriter(logFile, append: true) { };
System.Diagnostics.Process LogFile = new System.Diagnostics.Process();
LogFile.StartInfo.FileName = logReader;
LogFile.StartInfo.Arguments = logFile;
System.Diagnostics.Process PSScript = new System.Diagnostics.Process();
PSScript.StartInfo.FileName = Environment.GetFolderPath(Environment.SpecialFolder.SystemX86) + "\\WindowsPowerShell\\v1.0\\powershell.exe";
PSScript.StartInfo.Arguments = "-command . '" + sPSScript + "' " + vCenter.Text;
PSScript.StartInfo.RedirectStandardOutput = true;
PSScript.StartInfo.UseShellExecute = false;
LogFile.Start();
PSScript.Start();
System.Threading.Thread.Sleep(1500);
while ((standard_output = PSScript.StandardOutput.ReadLine()) != null)
{
if (standard_output != "")
{
file.WriteLine(standard_output);
}
}
It doesn't create multiple instances, but it also does not update the log file in real time as the previous code does. It only updates once the script runs, and then only partially. The script produces ~1000 lines of output, and I consistently see only about 840 written to the log file.
I thought about doing something like this:
FileStream logFS;
logFS = new FileStream(logFile, FileMode.Append);
but it appears the only options available to me to write to the file are expecting a byte array.
I am sure that I am missing something stupid simple in this, but would appreciate any suggestions on the easiest way to create the log file, open it in the reader, and then update it with the standard output from the powershell script.
why did the previous code writes in real time?
because you are wrapping it with using. And at the end of using block its gonna call dispose which calls .Flush to write to disk
Your second code block calls WriteLine but never called Flush so it writes to the disk whenever the buffer is full. Just add a .Flush call after WriteLine and you will have real time logging

How do I execute and return the results of a python script in c#?

How do I execute and return the results of a python script in c#?
I am trying to run a python script from my controller.
I have python.exe setup in a virtual environment folder created with the virtualenv command.
So just for testing purposes at the moment I would like to just return resulting string from my phython script:
# myscript.py
print "test"
And display that in a view in my asp.net mvc app.
I got the run_cmd function from a related stackoverflow question.
I've tried adding the -i option to force interactive mode and calling process.WaitForExit() with no luck.
namespace NpApp.Controllers
{
public class HomeController : Controller
{
public ActionResult Index(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
ViewBag.textResult = run_cmd("-i C:/path/to/virtualenv/myscript.py", "Some Input");
return View();
}
private string run_cmd(string cmd, string args)
{
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = #"C:/path/to/virtualenv/Scripts/python.exe";
start.CreateNoWindow = true;
start.Arguments = string.Format("{0} {1}", cmd, args);
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
using (Process process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
//Console.Write(result);
process.WaitForExit();
return result;
}
}
}
}
}
It seems like myscript.py never even runs. But I get no errors, just a blank variable in my view.
Edit:
I had tried to simplify the above stuff because I thought it would be easier to explain and get an answer. Eventually I do need to use a package called "nameparser" and store the result of passed name argument into a database. But if I can just get the run_cmd to return a string I think I can take care of the rest of it. This is why I think the rest api and IronPython mentioned in the comments may not work for me here.
Ok, I figured out what the issue was thanks to some leads from the comments. Mainly it was the spaces in the path to the python.exe and the myscript.py. Turns out I didn't need -i or process.WaitForExit(). I just moved the python virtual environment into a path without spaces and everything started working. Also made sure that the myscript.py file was executable.
This was really helpful:
string stderr = process.StandardError.ReadToEnd();
string stdout = process.StandardOutput.ReadToEnd();
Debug.WriteLine("STDERR: " + stderr);
Debug.WriteLine("STDOUT: " + stdout);
That shows the python errors and output in the Output pane in Visual Studio.

How do I use the system PATH with Process.UseShellExecute=false?

I am developing an application that spawns child processes using the Process API, with UseShellExecute set to false. The problem with this is that I don't want to go looking for the processes I'm spawning, since they are available in my system PATH. For example, I want to run Python scripts by just typing SCRIPT_NAME.py ARGUMENTS. However, when I set Process.FileName to be SCRIPT_NAME.py, I get an error telling me it couldn't find SCRIPT_NAME.py. I want the working directory to be where SCRIPT_NAME.py is, otherwise I'll have to specify the absolute path to SCRIPT_NAME.py and to its arguments, which is ridiculous and excessive.
I can avoid this by using cmd.exe /C SCRIPT_NAME.py ARGUMENTS but there are problems with force halting command prompt that are pushing me in this direction instead.
To fix this you just need to search the set of available paths looking for the one that has python.exe. Once you find that just fully qualify the python executable for launching in process
var path = Environment.GetEnvironmentVariable("PATH");
string pythonPath = null;
foreach (var p in path.Split(new char[] { ';' })) {
var fullPath = Path.Combine(p, "python.exe");
if (File.Exists(fullPath)) {
pythonPath = fullPath;
break;
}
}
if (pythonPath != null) {
// Launch Process
} else {
throw new Exception("Couldn't find python on %PATH%");
}

Got stuck thinking in an idea that i got

First of all, sorry that I was unable to give a proper title.
I got stuck with an idea that's been with me today almost the whole day after searching and searching and searching, till it came to a point that I decided to ask it on Stackoverflow!
So here's where I am stuck:
(I am making an auto-installer currently coded in C# and it is Dutch. It works really awesome but I just need one thing to finish my base. For example:
You have 'multiple' objects selected in a checklistbox, those are read from the checklistbox itself, they get trimmed and they get launched after that.
Now that's all working, I wanted to add a waiting method, for example we got:
Malwarebytes & CCleaner as installation 'example'.
Now when both are checked, and I click start, it starts both of the programs.
What I want to do is: to tell the program to start one program, do your thing, once its finished (closed) it should go to the next.
But... There is a problem, my programs are started in an array, so it basically works if there are multiple objects checked, than it will start all of the checked objects. And I really have no idea how to reach the same thing which is basically :
If there are multiple objects selected, start the object(s), do your thing(auto-clicking etc.),once its closed and confirmed its closed, move on to the next object and do the same thing until its been completed. I would like to make it work with a progressbar, but never really looked into a progress bar as they seem confusing.
I have a piece of code that finds the Process ID so maybe I can do something with that, but the Process ID is never the same on the applications that I start, so when they start in an array I got kinda of an issue.
Could someone help me please figuring out what & how to code / do this?
here's the code i use to make this work :
string pad = Application.StartupPath;
foreach (string checkedItem in checkedListBox1.CheckedItems)
{
if (checkedItem.Contains("."))
{
string str = checkedItem;
if (str.Contains("."))
{
int index = str.IndexOf('.');
string folder = str.Substring(0, index);
try
{
bool started = false;
var process = new Process();
process.StartInfo.FileName = pad + "/data/" + folder + "/" + checkedItem;
started = process.Start();
var processID = process.Id;
var processNAAM = process.ProcessName;
textBox1.Text += "Gevonden - ID: " + processID + " NAAM: " + processNAAM + Environment.NewLine;
textBox1.Text += DateTime.Now.ToString("HH:mm:ss", System.Globalization.DateTimeFormatInfo.InvariantInfo) + " - " + "Installatie Keuze wordt opgestart." + Environment.NewLine;
process.WaitForExit();
}
catch (Exception)
{
}
}
}
}
First of all, you can make the code simpler and shorter by using the CheckedListBox's CheckedItems property. Secondly, there's no point to all your copying of strings from one to another. Strings are immutable in .NET - they never change. You can keep just one copy and cut from there.
Next, you can use the methods in System.IO.Path to cut the filename without the extension, or to build a full path without worrying about having too many or too few "/"'s.
Third, for your original question - just call WaitForExit on your Process object to make it wait before moving on with the list of processes.
Thirdly
foreach (string checkedItem in checkedListBox1.CheckedItems)
{
if (checkedItem.Contains("."))
{
string baseName = Path.GetFileNameWithoutExtension(checkedItem);
string processPath = Path.Combine(pad, "data", baseName, checkedItem);
Process process = Process.Start(processPath);
process.WaitForExit();
}
}
After the line where you start the process (process.Start()), simply add the following:
process.WaitForExit()
This will pause the containing thread until the target process has exited.

Redirect standard output isn't flushed during process execution

I have a console application and I want to process the std out in a c# application.
Basically I already managed to do this with this code:
Process ProcessObj = new Process();
ProcessObj.StartInfo.WorkingDirectory = WorkingPath;
ProcessObj.StartInfo.FileName = ApplicationPath;
ProcessObj.StartInfo.Arguments = ApplicationArguments;
ProcessObj.StartInfo.UseShellExecute = false;
ProcessObj.StartInfo.CreateNoWindow = true;
ProcessObj.StartInfo.RedirectStandardOutput = true;
// Start the process
ProcessObj.Start();
// loop through until the job is done
bool stopper = false;
while (!stopper)
{
stopper = ProcessObj.WaitForExit(100);
string line = null;
// handle normal outputs (loop through the lines)
while (true)
{
line = ProcessObj.StandardOutput.ReadLine();
if (line == null)
break;
Logger.Trace("Out: \"" + line + "\"");
}
}
When the process runs only a few seconds it looks like the whole thing is working without any problem. When I change the configuration of the console application to calculate more, it comes that the process is running for hours. In this time my C# application gets no response from the console app. Since the console app is hidden it looks like the app stucked but that's not true. It is already running in the background and it seems that all std outputs are only piped through to my c# app when the console app was finished the execution.
So the problem is, I don't see the std out lines live in my c# app. It will be refreshed after hours when the console app has finished.
Is there any way to flush this std out redirection?
Anybody knows why this isn't working like I want?
PS: When I execute the console app as standalone in a normal cmd window the outputs are shown live without any problem.
Please help.
Try and read the output while the application is running? And save it to a buffer? Then process the output when the application exits.
pseudo stuff
string buffer = string.Empty;
while(!process.HasExited)
{
string line = process.Stream.ReadLine();
if (line != null)
buffer += Enviorment.Newline + line
}
// Do stuff without put here
Console.Write(buffer);

Categories