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.
Related
I run into a strange thing and can't find the reason why this is happening.
I have a service.exe where I collect data from configuration registry and then start n processes.
Sample Code:
_mProcess.StartInfo = new ProcessStartInfo
{
FileName = Command,
Arguments = Argument,
WorkingDirectory = WorkDir
};
_mProcess.Start();
Pid = _mProcess.Id;
My Pid contains the process id.
Now I added UseShellExecute = false to get the StandardOutput.
New sample code:
_mProcess.StartInfo = new ProcessStartInfo
{
FileName = Command,
Arguments = Argument,
WorkingDirectory = WorkDir //,
//CreateNoWindow = true,
UseShellExecute = false,
//RedirectStandardOutput = true,
RedirectStandardError = true
//RedirectStandardInput = true
};
_mProcess.Start();
Pid = _mProcess.Id;
using (var reader = _mProcess.StandardError)
{
_logger.ToLog("", Company, reader.ReadToEnd(), "RCluster.log", "service");
}
In this case the process return back any error which I can store to my log file.
Problem: This code is part of a method to start a process which I call many times (depends on my configuration).
So with this code the first process is called, the following processes are not.
Somehow the service wait for the first service now. I thought this happens only with WaitForExit.
So how I can get standard error output but not make the process block my main task to continue?
#Gusman: Add you comment as answer, you brought me to the correct answer. Then I can accept your comment as answer.
To the down-voter: Explanation why would be appreciated.
To all: I added some code to start the new process as a thread. In this case it make sense to start it in another thread to grab `StandardError messages but do not block the main process (which is a service in my case that starts many sub processes).
// start as new thread to prevent blocking
var ths = new ThreadStart(() =>
{
mProcess.Start();
Pid = mProcess.Id;
// write pid file
File.WriteAllText(RubyDir + #"\tmp\pids\" + Port + #".pid", Pid.ToString());
using (var reader = mProcess.StandardError)
{
var errorMsg = reader.ReadToEnd();
if (errorMsg.Length > 0) _logger.ToLog("", Company, errorMsg, "SOLR.log", "service");
}
});
var th = new Thread(ths);
th.Start();
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 have a Console app and a Winforms app that do the same. The common functionality is in a class being reused by both.
The CopyRequiredFile, starts a windows batch file which uses xcopy to copy files from a network folder to a local drive. But, when called from the Windows Forms app it doesn't copy the files.
I am a novice developer trying to develop framework and some internal tools for UI automation.
Why does it work to copy the files when I invoke the functionality from the Console application, but not from the Windows Forms Application?
My Console App:
public class Program
{
private static readonly Action<string> OutputAction = s => Console.WriteLine(s);
private static readonly IProgress<string> Progress = new Progress<string>(OutputAction);
public static void Main(string[] args)
{
HelpersCopy.CreateRequiredDirectories(Progress);
HelpersCopy.CopyRequiredFiles(Progress, true);
HelpersCopy.StartHub(Progress);
HelpersCopy.StartNode(Progress);
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
My Windows Forms app: Only code that is relevant to this question.
private void button1_Click(object sender, EventArgs e)
{
Action<string> outputAction = s => txtOutput.InvokeEx(t => t.Text += s + Environment.NewLine);
IProgress<string> progress = new Progress<string>(outputAction);
txtOutput.Clear();
HelpersCopy.CreateRequiredDirectories(progress);
HelpersCopy.CopyRequiredFiles(progress, true);
HelpersCopy.StartHub(progress);
HelpersCopy.StartNode(progress);
}
InvokeEx is an extension method to invoke the action if required. Help from stackoverflow!
Unfortunately, I cannot post images because I don't have the required points. So, please see the output images here: https://www.flickr.com/photos/61600076#N05/sets/72157649781440604/
Helpers Class Code Please let me know if this code is not required in the question.
public class HelpersCopy
{
public static void CopyRequiredFiles(IProgress<string> progress, bool hideWindow = false)
{
progress.Report(string.Format("Copying latest version of executables...{0}", Environment.NewLine));
var currentDirectory = Directory.GetCurrentDirectory();
ExecuteCommand(String.Format(#"{0}\Copy latest Selenium files.bat", currentDirectory), progress, hideWindow: hideWindow);
progress.Report(string.Format("\r\nLatest version of executables copied successfully{0}", Environment.NewLine));
}
private static void ExecuteCommand(string fileName, IProgress<string> progress, string command = null, bool hideWindow = true)
{
if (hideWindow)
{
var processInfo = new ProcessStartInfo(fileName, command)
{
CreateNoWindow = true,
UseShellExecute = false,
// *** Redirect the output ***
RedirectStandardError = true,
RedirectStandardOutput = true
};
var process = new Process { StartInfo = processInfo, EnableRaisingEvents = true };
process.ErrorDataReceived += (sender, args) => progress.Report(args.Data);
process.OutputDataReceived += (sender, args) => progress.Report(args.Data);
var started = process.Start();
progress.Report(string.Format("process started: {0}", started));
progress.Report(string.Format("process id: {0}", process.Id));
progress.Report(string.Format("process start info: {0} {1}", process.StartInfo.FileName, process.StartInfo.UserName));
process.BeginErrorReadLine();
process.BeginOutputReadLine();
process.WaitForExit();
int ExitCode = process.ExitCode;
progress.Report(string.Format("ExitCode: {0}{1}", ExitCode, Environment.NewLine));
process.Close();
}
else
{
var process = Process.Start(fileName, command);
if (process.HasExited)
{
throw new Exception(string.Format("Process exited. Exit code: {0}", process.ExitCode));
}
}
}
}
My batch file
#echo off
echo Deleting existing mappings...
net use z: /delete /yes
echo Mapping network drives...
net use z: \\company-filestore\Selenium /user:company-filestore\Automation Selen1um
z:
cd "Hub and Node Executables"
echo Copying latest Selenium jars...
xcopy "z:\Hub and Node Executables" "C:\Selenium\" /R /Y /S /Z
echo Finished copying latest Selenium jars...
echo All done
The issue in winforms app (although, there was never any issue in console app using the same code) was being caused by a bug in xcopy in that when you redirect its output you need to redirect its input as well. So, adding this line to the ProcessInfo object in my code, fixed the issue.
RedirectStandardInput = true
More information on the issue is here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/ab3c0cc7-83c2-4a86-9188-40588b7d1a52/processstart-of-xcopy-only-works-under-the-debugger?forum=netfxbcl
Hope this helps someone.
Have you tested placing the batch file in the same folder where the Windows Forms exe is available?
Have you tried running the windows forms app with Admin rights?
Just to knockout the possibility of insufficient permissions.
have you also verified the user context with which the code is executed and if the folder has permissions for the user context?
So what i am doing is calling java.exe and making a call to a jar file with some arguments. If everyting is fine and the command works, then the arguments are printed out to a richtext box.
The issue I am having is when the command isn't correct. So in once instance, say the user types in the wrong password that's passed into txtPassword. Stanadard out is not being redirected. If i run the exact same command in a Dos console, the message "Error: Auth fail", is being presented. How do i redirect that error to the rich text box? I thought redirecting stdout would do it, but apparently not.
Any help would be apprecaited. Please see the code below.
//Declare and instantiate a new process component.
System.Diagnostics.Process process1;
process1 = new System.Diagnostics.Process();
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.RedirectStandardOutput = true;
process1.StartInfo.CreateNoWindow = true;
process1.StartInfo.FileName = "java.exe ";
toLoad = lstBarToLoad.Items[i].Text;
process1.StartInfo.Arguments = "-Xmx512M -jar Deploy.jar" + txtPassword;
process1.StartInfo.Arguments += toLoad;
Console.WriteLine(process1.StartInfo.Arguments);
process1.Start();
process1.OutputDataReceived += (s, a) => myMethod(a);
process1.BeginOutputReadLine();
//myMthod
private void myMethod(DataReceivedEventArgs e)
{
if (e.Data != null)
{
Action action = () => rchsdtOut.Text += "\r\n" + e.Data.ToString();
rchsdtOut.BeginInvoke(action, null);
Console.WriteLine(e.Data.ToString());
}
}//end of private
As well as using
process1.StartInfo.RedirectStandardOutput = true;
You also need to use
process1.StartInfo.RedirectStandardError = true;
Gets or sets a value that indicates whether the error output of an application is written to the Process.StandardError stream
http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.redirectstandarderror.aspx
You can then read out the error and do with it as you please:
string error = process1.StandardError.ReadToEnd();
You can use RedirectStandardError property same way as you use the RedirectStandardOutput property.
I want to run on a C# program a specific running file and during it display the output on the screen and also saving it in the file.
I don't want to save the output in the file and later display it on screen.
I want them both to happen together.
I know a way to do it by "tee" but I failed each time I tried doing so.
Can anyone give me an example (that works) by using "tee"?
The main question is, do you have control over the source code of the program whose output you want logged and displayed?
If so, then you have a couple options. Probably the easiest would be to "hijack" the Console's output stream with a compound TextWriter:
public class CompoundWriter:TextWriter
{
public readonly List<TextWriter> Writers = new List<TextWriter>();
public override void WriteLine(string line)
{
if(Writers.Any())
foreach(var writer in Writers)
writer.WriteLine(line);
}
//override other TextWriter methods as necessary
}
...
//When the program starts, get the default Console output stream
var consoleOut = Console.Out;
//Then replace it with a Compound writer set up with a file writer and the normal Console out.
var compoundWriter = new CompoundWriter();
compoundWriter.Writers.Add(consoleOut);
compoundWriter.Writers.Add(new TextWriter("c:\temp\myLogFile.txt");
Console.SetOut(compoundWriter);
//From now on, any calls to Console's Write methods will go to your CompoundWriter,
//which will send them to the console and the file.
You can also use the Trace listeners to handle any output you want to go to both places:
Trace.Listeners.Clear();
Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
Trace.Listeners.Add(new TextWriterTraceListener(File.Open("C:\temp\myLogFile.txt");
//replace any call to Console.WriteLine() with Trace.WriteLine()
if you do NOT have control over the source code of the console app you want to "tee", and the console app does not require any input in the middle of its execution, then you can use a named pipe to get the output of the app and redirect it.
var appLocation = #"C:\temp\myApp.exe";
var pipeName = "ConsoleNamedPipe";
using(var namedPipe = new NamedPipeServerStream(pipeName, PipeDirection.In))
{
var info = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = String.Format(#"/C {1} >>\\.\pipe\{0}",
pipeName, appLocation),
WindowStyle = ProcessWindowStyle.Hidden
};
ConsoleProcess = Process.Start(info);
pipe.WaitForConnection();
using (var reader = new StreamReader(pipe))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
Console.WriteLine(line);
myFileWriter.WriteLine(line);
}
}
}
This is a very simple example; you can provide more interaction by using a two-way pipe but it will require quite a bit more code on your end.
You can try the following:
C:\you_csharp_program.exe arg1 arg2 arg3 |tee filename
I'm not going to write out specific code examples but I will tell you that you can look at logging frameworks like log4net which has console and file appenders which will do what you want. You cant wrong log statements in your code Log.Debug("some message") setup the log4net config to use any number of appenders you want and have it write the message to all of the sources at once, so for example screen, file, db, and email you all at the same time.
I seem to have missed the last sentence of the question about making it work with Tee so my answer may not be valid.
Elaborating on KeithS's answer, this is a working implementation. It should work for all Write/WriteLine calls without having to override every overload because the current (4.0, and I assume earlier) implementation of TextWriter directs all writes through Write(char).
public class TeeTextWriter : TextWriter
{
readonly TextWriter[] _redirectTo;
public override Encoding Encoding { get { return Encoding.UTF8; } }
public TeeTextWriter(params TextWriter[] redirectTo)
{
_redirectTo = redirectTo ?? new TextWriter[0];
}
public override void Write(char value)
{
foreach (var textWriter in _redirectTo)
{
textWriter.Write(value);
}
}
}
Usage:
var realConsoleStream = Console.Out;
using (var fileOut = new StreamWriter(outFileName, false))
{
Console.SetOut(new TeeTextWriter(fileOut, realConsoleStream));
try
{
Console.WriteLine("Test");
}
finally
{
Console.SetOut(realConsoleStream);
}
}