Im encoding a video using a command line app. The app returns a line which says:
% complete : 34%
This is updated as the media encodes. Is there a way using the process class to keep checking the standard output and passing it back to the main execution script? I have a class that starts the process and then writes the standard output to stringbuilder but I want to know how to keep checking it. This is the curent code...
public static Dictionary<string, string> StartProcess(string exePathArg, string argumentsArg, int timeToWaitForProcessToExit)
{
//the dictionary with the
Dictionary<string, string> retDirects = new Dictionary<string, string>();
using (Process p = new Process())
{
p.StartInfo.FileName = exePathArg;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.Arguments = argumentsArg;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
try
{
p.Start();
p.WaitForExit(timeToWaitForProcessToExit);
int exitCode;
try
{
exitCode = p.ExitCode;
StreamReader standardOutput = p.StandardOutput;
StreamReader standardError = p.StandardError;
retDirects.Add("StandardOutput", standardOutput.ReadToEnd());
retDirects.Add("StandardError", standardError.ReadToEnd());
}
catch { }
}
catch { }
finally
{
try
{
p.Kill();
p.CloseMainWindow();
}
catch { }
}
}
return retDirects;
}
You can use the Process.BeginOutputReadLine to initiate the firing of the Process.OutputDataRecieved event. UseShellExecute must be false and Redirect<StreamOfChoice>Output must be true, as in your example code.
There is an example on MSDN which I won't regurgitate here. I've noticed that some programs use the different streams for what I thought were unexpected purposes so it may be appropriate to use the same handler for events from the different streams.
Instead of using "ReadToEnd", use "Read" of a few bytes (even one byte at time) in a loop. Read will block until it reads the number of bytes you specified. Find the correct number of bytes, and you should be able to read the strings from the standard output.
Related
I need to use standard input/output on process, so I created simple app "test":
var line = String.Empty;
do
{
Console.Write($"previous input ==> {line}, type next input> ");
line = Console.ReadLine();
}
while (!String.IsNullOrWhiteSpace(line) && line != "quit");
Console.WriteLine("End");
which receives something on standard input and writes on output. Then I created new app which needs to start that app "test" and use standard iput/output like:
var process = new Process
{
EnableRaisingEvents = false,
StartInfo = new ProcessStartInfo
{
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
Arguments = Arguments,
CreateNoWindow = true,
FileName = Name,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = WorkingDirectory
},
};
process.Start();
String? input;
do
{
Thread.Sleep(10000); // Sleep to be sure that "test" app generated output
var line = String.Empty;
while (process.StandardOutput.Peek() > -1)
line += (char)process.StandardOutput.Read();
Console.Write($"[Standard Output]{line}\t[New Input]");
input = Console.ReadLine();
process.StandardInput.WriteLine(input);
}
while (input != "quit");
The problem is that I get this as output:
[Standard Output]previous input ==> , type next input> [New Input]test
[Standard Output] [New Input]
The "process.StandardOutput.Peek()" second time is returning -1 and there exist output of "test" app. Is it possible to get next what is generated on standard output by "test" app from app that started that process.
I need to get second output generated from "test" app, so I expect to see line:
[Standard Output]previous input ==> test, type next input> [New Input]
If your platform is Windows, try PeekNamedPipe.
static string ReadAvailableString(StreamReader reader)
{
[DllImport("kernel32.dll")]
static extern bool PeekNamedPipe(
SafeFileHandle hNamedPipe,
IntPtr lpBuffer,
int nBufferSize,
IntPtr lpBytesRead,
out int lpTotalBytesAvail,
IntPtr lpBytesLeftThisMessage
);
var stream = (FileStream)reader.BaseStream;
if( !PeekNamedPipe(stream.SafeFileHandle, IntPtr.Zero, 0, IntPtr.Zero, out var totalbytesAvail, IntPtr.Zero) || totalbytesAvail<=0 )
return String.Empty;
Span<byte> buf = stackalloc byte[totalbytesAvail];
stream.Read(buf);
return reader.CurrentEncoding.GetString(buf);
}
Here is an example of getting StandardOutput.
var output = ReadAvailableString(process.StandardOutput);
Problem is Peek is non-blocking call which does not wait for data to become available. You start new process and then immediately proceed checking its standard output with Peek, but it might be nothing there yet. This is what you observe. Instead - you should read until some stopping point, but in this case there is no such point, so you can introduce it - use Console.WriteLine instead of Console.Write here:
Console.WriteLine($"previous input ==> {line}, type next input> ");
Now on receiving end you can read until you meet newline character:
line = process.StandardOutput.ReadLine();
Note that this is blocking read. It will read until newline, and if data is not available yet - it will wait until it's there. Now "messages" in your communication have clear boundaries.
I would even say that you should forget that Peek() exist and never use it. I've never used it in my practice and all usages I ever saw lead to bugs like this.
After trying a lot of things it seams that Process.StandardOutput.Peek is not working. You can use the "PeekNamedPipe" (answer from radian) for Windows OS.
I managed to work it on Windows and Linux using CliWrap (https://github.com/Tyrrrz/CliWrap). The problem here is that for input stream you need Stream that has blocking read() method, so I creted/implemented one for me. I will not put here the implementation of that stream, you can use any stream that satisfy that condition. So, here is the final version of above example using CliWrap library
var stdOutBuffer = new StringBuilder();
var stdErrBuffer = new StringBuilder();
// The CliStream is my own implementation of Stream class.
// The CliWrap library is calling method: int Read(byte[] buffer, int offset, int count)
// with parameters offset = 0 and count = 131072
// it is important that this method is blocking if nothing is in stream (it done it using Semaphores)
// if it is not blocking then you will have some unexpected behaviour
var stream = new CliStream();
var cmd = CliWrap.Cli.Wrap(ExecName)
.WithArguments(Arguments)
.WithStandardErrorPipe(CliWrap.PipeTarget.ToStringBuilder(stdErrBuffer))
.WithStandardOutputPipe(CliWrap.PipeTarget.ToStringBuilder(stdOutBuffer))
.WithStandardInputPipe(CliWrap.PipeSource.FromStream(stream))
.WithWorkingDirectory(WorkingDirectory);
cmd.ExecuteAsync();
String? input;
do
{
Thread.Sleep(10000); // Sleep to be sure that "test" app generated output
Console.Write($"[Standard Output]{stdOutBuffer}\t[New Input]");
stdOutBuffer.Clear();
input = Console.ReadLine();
var buffer = Encoding.UTF8.GetBytes(input + Environment.NewLine);
stream.Write(buffer);
}
while (input != "quit");
Thanks to everyone for contributing.
I'm trying to do a virus scan on uploaded files.
I have no control over the installed virus scanner, the product hosted by multiple parties with different scanners.
I tried the following library but it always returns VirusNotFound on the eicar file.
https://antivirusscanner.codeplex.com/
Do you know any other solutions?
ClamAV has pretty bad detection scores.
VirusTotal is not on premises.
I decided to create CLI wrappers for multiple scanners, nuget packages can be found here: https://www.nuget.org/packages?q=avscan
And its documentation and source code available at https://github.com/yolofy/AvScan
I used this library for .net (It uses the VirusTotal public api):
https://github.com/Genbox/VirusTotal.NET
A little example from github :
static void Main(string[] args)
{
VirusTotal virusTotal = new VirusTotal("INSERT API KEY HERE");
//Use HTTPS instead of HTTP
virusTotal.UseTLS = true;
FileInfo fileInfo = new FileInfo("testfile.txt");
//Create a new file
File.WriteAllText(fileInfo.FullName, "This is a test file!");
//Check if the file has been scanned before.
Report fileReport = virusTotal.GetFileReport(fileInfo).First();
bool hasFileBeenScannedBefore = fileReport.ResponseCode == 1;
if (hasFileBeenScannedBefore)
{
Console.WriteLine(fileReport.ScanId);
}
else
{
ScanResult fileResults = virusTotal.ScanFile(fileInfo);
Console.WriteLine(fileResults.VerboseMsg);
}
}
A full example can be found here :
https://github.com/Genbox/VirusTotal.NET/blob/master/VirusTotal.NET%20Client/Program.cs
Clam AV is pretty good.
https://www.clamav.net/downloads
C# Api here:
https://github.com/michaelhans/Clamson/
I just tried various ways, But some didn't work.
Then I decided to use ESET NOD32 command line tools .
It works fine for me:
public bool Scan(string filename)
{
var result = false;
try
{
Process process = new Process();
var processStartInfo = new ProcessStartInfo(#"C:/Program Files/ESET/ESET Security/ecls.exe")
{
Arguments = $" \"{filename}\"",
CreateNoWindow = true,
ErrorDialog = false,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false
};
process.StartInfo = processStartInfo;
process.Start();
process.WaitForExit();
if (process.ExitCode == 0) //if it doesn't exist virus ,it returns 0 ,if not ,it returns 1
{
result = true;
}
}
catch (Exception)
{ //nothing;
}
return result;
}
I've got a C++ program which uses wprintf_s function to print the results into the command line. But when I uses Process in C# to read the output of the program, I cannot get any words of it. However when I added a fflush(stdout) after the wprintf_s statement, I can finally read the standard output in my C# program.
The code I use to start the progress is:
var proc = new Process {
StartInfo = new ProcessStartInfo {
FileName = "FILENAME",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
proc.Start();
StringCollection values = new StringCollection();
proc.OutputDataReceived += (s, args) => {
lock (values) {
values.Add(args.Data);
}
};
proc.BeginOutputReadLine();
proc.WaitForExit();
Can anybody tell me why a fflush(stdout) would work?
The output is being buffered in the C++ process and will remain so until the buffer is full or it is flushed, e.g. by calling fflush(), by closing the stream, or other OS dependant reasons.
fflush() just causes any data in the output buffer to be written to the stream.
If you don't want to explicitly call fflush(), you can consider setting unbuffered mode on the output stream by calling setbuf() with a NULL pointer for the second argument:
#include <stdio.h>
#include <unistd.h>
int main()
{
setbuf(stdout, (char *)NULL);
while (1)
{
fputs("hi there\n", stdout);
sleep(1);
}
}
Now the output will appear immediately.
Note that if stdout is a terminal, setbuf(f, NULL) is unnecessary as this is the default behaviour for terminal devices. If stdout is a pipe, then setbuf(f, NULL) will make it unbuffered.
I write an application that starts a 3rd party program that downloads a media stream from a tv channel on my program. The program I am using is rtmpdump.exe. They run as independent processes like this:
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = path + rtmpdump;
startInfo.Arguments = rtmpdump_argument;
startInfo.WorkingDirectory = path;
startInfo.WindowStyle = ProcessWindowStyle.Minimized;
Process p = Process.Start(startInfo);
But I would like to name these processes, so if my program crashes or has to be restarted, then I can check the names of the current processes to see if for example there is one called "BBC World - News at 7". Something like that to identify which has already been started and is currently running. Is it possible? I can't find a way to set a friendly name.
Extending what Alexei said - you could create a Dictionary< int, string > to keep track of the process ID / descriptive name that you create. Maybe you should write this out to a file in case your program crashes - but you'd need some special startup handling to deal with processes exiting.
On startup you'd want to read in the file, and check current processes to see if they match with what you have, and remove any processes that no longer exist (wrong process id or exe name). You might want to do that every time you create a new process and write to the file.
You can't change process names, but instead you can:
use Process.Id to identify processes (Id: "...system-generated unique identifier of the process...")
get process command line and see if there anything interesting (may need some PInvoke / WMI for that - How to read command line arguments of another process in C#?).
Here is some code that you could use.
public class ProcessTracker {
public Dictionary<int, string> Processes { get; set; }
public ProcessTracker() {
Processes = new Dictionary<int, string>();
}
public void AddProcess(Process process, string name) {
Processes.Add(process.Id, name);
}
//Check if what processes are still open after crash.
public void UpdateProcesses() {
List<Process> runningProcesses =
Process.GetProcesses().ToList();
Processes = Processes
.Where(pair => runningProcesses
.Any(process => process.Id == pair.Key))
.ToDictionary(pair => pair.Key, pair => pair.Value);
}
//Use this to see if you have to restart a process.
public bool HasProcess(string name) {
return Processes.Any(pair => pair.Value != name);
}
//Write the file on crash.
public void ReadFile(string path) {
if (!(new FileInfo(path).Exists))
return;
using (StreamReader reader = new StreamReader(path)) {
foreach (string line in reader.ReadToEnd()
.Split(new[] {"\n"}, StringSplitOptions.RemoveEmptyEntries)) {
string[] keyPair = line.Split(',');
Processes.Add(int.Parse(keyPair[0]), keyPair[1]);
}
}
}
//Read the file on startup.
public void SaveFile(string path) {
using (StreamWriter writer = new StreamWriter(path, false)) {
foreach (KeyValuePair<int, string> process in Processes) {
writer.WriteLine("{0},{1}",
process.Key, process.Value);
}
}
}
}
Writing the process information to a file (or a memory mapped file) might be the way to go;
However, you could also look into windows messaging. i.e. send a message to each process; and have them reply with their "internal name".
Basically you have everything listen in on that queue; You first send a command that orders the other process to report their internal name, which they then dump into the message queue along with their own process id;
See MSMQ
I had the same question to myself, for my solution created a new class -
MyProcess which inherits from Process and added a property - ProcessId.
In my case I store the ProcessId on a db which is acossiated to other data. Based on the status of the other data I decide to kill the process.
Then I store process on a dictionary which can be passed to other classes if needed where can be access to kill a process.
So when I decide to kill a process, I pull the process from the dictionary by ProcessId and then kill it.
Sample below:
public class Program
{
private static Dictionary<string, MyProcess> _processes;
private static void Main()
{
// can store this to file or db
var processId = Guid.NewGuid().ToString();
var myProcess = new MyProcess
{
StartInfo = { FileName = #"C:\HelloWorld.exe" },
ProcessId = processId
};
_processes = new Dictionary<string, MyProcess> {{processId, myProcess}};
myProcess.Start();
Thread.Sleep(5000);
// read id from file or db or another
var pr = _processes[processId];
pr.Kill();
Console.ReadKey();
}
}
public class MyProcess : Process
{
public string ProcessId { get; set; }
}
I need some advice regarding the use of a command line utility from a C#/ASP.NET web application.
I found a 3rd party utility for converting files to CSV format. The utility works perfectly and it can be used from the command line.
I have been looking on the web for examples on how to execute the command line utility and found this example.
The problem is this is not very good. When I try to us the example code with my utility, I get a prompt asking me to install the utility on the client machine. This is not what I want. I do not want the user to see what is going on in the background.
Is it possible to execute the command server side and processing the file from there?
Any help would be greatly appreciated.
I've done something like this several times in the past, and here's what's worked for me:
Create an IHttpHandler implementation (easiest to do as an .ashx file) to handle a convert. Within the handler, use System.Diagnostics.Process and ProcessStartInfo to run your command line utility. You should be able to redirect the standard output to the output stream of your HTTP response. Here's some code:
public class ConvertHandler : IHttpHandler
{
#region IHttpHandler Members
bool IHttpHandler.IsReusable
{
get { return false; }
}
void IHttpHandler.ProcessRequest(HttpContext context)
{
var jobID = Guid.NewGuid();
// retrieve the posted csv file
var csvFile = context.Request.Files["csv"];
// save the file to disk so the CMD line util can access it
var filePath = Path.Combine("csv", String.Format("{0:n}.csv", jobID));
csvFile.SaveAs(filePath);
var psi = new ProcessStartInfo("mycsvutil.exe", String.Format("-file {0}", filePath))
{
WorkingDirectory = Environment.CurrentDirectory,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (var process = new Process { StartInfo = psi })
{
// delegate for writing the process output to the response output
Action<Object, DataReceivedEventArgs> dataReceived = ((sender, e) =>
{
if (e.Data != null) // sometimes a random event is received with null data, not sure why - I prefer to leave it out
{
context.Response.Write(e.Data);
context.Response.Write(Environment.NewLine);
context.Response.Flush();
}
});
process.OutputDataReceived += new DataReceivedEventHandler(dataReceived);
process.ErrorDataReceived += new DataReceivedEventHandler(dataReceived);
// use text/plain so line breaks and any other whitespace formatting is preserved
context.Response.ContentType = "text/plain";
// start the process and start reading the standard and error outputs
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
// wait for the process to exit
process.WaitForExit();
// an exit code other than 0 generally means an error
if (process.ExitCode != 0)
{
context.Response.StatusCode = 500;
}
}
}
#endregion
}
The command is running server side. Any code is running on the server. The code in the example that you give works. You just need to make sure that the utility is set up properly on the server and that you have permissions to the directory/file.