I'm porting some C# code to Dart on Windows and just wasted a couple of hours on a frustrating difference in how Dart passes command line arguments through to the process.
Here's a portion of the C# code being ported, which executes just fine:
var startInfo = new ProcessStartInfo("C:\\Program Files\\Inkscape\\inkscape.exe", "--file=\"C:\\Users\\Kent\\test.svg\" --export-png=\"C:\\Users\\Kent\\test.png\" --export-width=100 --export-area-page")
{
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
};
var process = Process.Start(startInfo);
process.WaitForExit();
if (process.ExitCode != 0)
{
var stdout = process.StandardOutput.ReadToEnd();
var stderr = process.StandardError.ReadToEnd();
Console.WriteLine(stdout);
Console.WriteLine(stderr);
}
Running this correctly produces an output PNG file. However, the Dart equivalent does not:
final inkscapeResult = await Process.run(
r'C:\Program Files\Inkscape\inkscape.exe',
[
'--file="C:\\Users\\Kent\\test.svg"',
'--export-png="C:\\Users\\Kent\\test.png"',
'--export-width=100',
'--export-area-page',
],
);
final exitCode = inkscapeResult.exitCode;
if (exitCode != 0) {
writeError(inkscapeResult.stdout);
writeError(inkscapeResult.stderr);
}
Instead, the process exits with code 1 and the program outputs:
** (inkscape.exe:5604): WARNING **: 17:30:09.785: Can't open file: "C:\Users\Kent\test.svg" (doesn't exist)
** (inkscape.exe:5604): WARNING **: 17:30:09.785: Can't open file: "C:\Users\Kent\test.svg" (doesn't exist)
** (inkscape.exe:5604): WARNING **: 17:30:09.785: Specified document "C:\Users\Kent\test.svg" cannot be opened (does not exist or not a valid SVG file)
It is pretty well-known that Inkscape is a bit finicky when it comes to executing on the command line, but the advice I've read is to simply pass in full paths, which I am doing.
I found that if I eschew arguments, it works:
final inkscapeResult = await Process.run(
r'C:\Program Files\Inkscape\inkscape.exe --file="C:\\Users\\Kent\\test.svg" --export-png="C:\\Users\\Kent\\test.png" --export-width=100 --export-area-page',
[],
);
So I inferred that Dart must be doing something with the arguments that Inkscape does not like. I managed to catch it in the act using Process Explorer and it has the following command line:
"C:\Program Files\Inkscape\inkscape.exe" "--file=\"C:\Users\Kent\test.svg\"" "--export-png=\"C:\Users\Kent\test.png\"" --export-width=100 --export-area-page
If I run this directly from the command line, I get no output but the image also does not get generated, so presumably it's failing in the same fashion. Compare this with the command line produced by the C# code:
"C:\Program Files\Inkscape\inkscape.exe" --file="C:\Users\Kent\test.svg" --export-png="C:\Users\Kent\test.png" --export-width=100 --export-area-page
Dart is wrapping each individual argument with quotes, where C# is not (though C#'s API does not even separate individual arguments). This is somehow breaking Inkscape's parser.
Who is in the wrong here? Me, Dart, C#, or Inkscape?
I think the simplest way is to split the argument name and the argument value like that:
final inkscapeResult = await Process.run(r'C:\Program Files\Inkscape\inkscape.exe', [
'--file', 'C:\\Users\\Kent\\test.svg',
'--export-png', 'C:\\Users\\Kent\\test.png',
'--export-width', '100',
'--export-area-page',
]);
That way you don't have to worry about escaping or quoting arguments.
Related
I'm writing an application, and at one point it launches win-acme and needs to pass some parametres to it. I'm successfully opening powershell and launching win-acme, but it doesn't pass arguments to it. So, I have this code:
Process wacsProcess = Process.Start(new ProcessStartInfo
{
FileName = #"C:\Windows\System32\WindowsPowershell\v1.0\powershell.exe",
Arguments = (#"cd C:\inetpub\letsencrypt ; .\wacs.exe ; N"),
RedirectStandardOutput = true,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden
});
File.WriteAllText(".\\OutPutAfterFirstLaunch.txt",
wacsProcess.StandardOutput.ReadToEnd());
It opens command-line utility, but doesn't give it the last parametr "N". I guess that is because I'm passing this parametr to the powershell, but it's still working with win-acme.
It looks like this:
Is there a way to pass an argument to the command line utility using C#?
This is how this application is designed. It is meant to be interactive for new certificates. Please see the documentation with all of the allowed command-line arguments: https://www.win-acme.com/reference/cli
Is there a particular reason that you must launch the process from powershell?
You should be able to read the stdout of the process if you launch it directly the same way as if you were reading the output from your powershell window (the output powershell displays is just the stdout of the process anyways.)
You can also try passing the N parameter with the executable,
Arguments = (#"cd C:\inetpub\letsencrypt ; .\wacs.exe N;"),
There is an executable called jlink.exe that I will like to make of use on my console application. Because jlink.exe is not a .net application it is not possible for me to reference it and call it's methods. As a result I will like to use it reading it's output
When I start that exe from windows explorer this is how it looks:
It then waits for a command. If I type mem 88 2 then I get back:
Now I will like to do the same thing with my .net console application reading it's standard output but for some reason I cannot read the output. This is what I have:
// change working directory
Directory.SetCurrentDirectory(#"C:\Program Files (x86)\IAR Systems\Embedded Workbench 6.4 Evaluation\arm\bin");
Process p = new Process( )
{
StartInfo = new ProcessStartInfo( #"jlink.exe" )
{
UseShellExecute = false ,
RedirectStandardOutput = true ,
RedirectStandardInput = true
}
};
p.Start( );
StreamWriter standardInput = p.StandardInput;
StreamReader standardOutput = p.StandardOutput;
var line = string.Empty;
while ( ( line = standardOutput.ReadLine( ) ) != null )
{
Console.WriteLine( line );
}
why when I run that code I just get a black window... I do not get the same output as when running that executable. When I run that code against other executable it works great.
If I then type mem 88 2 and press ENTER nothing happens. If I press enter one more time then I finally get what I want with extra content. Why am I getting this behavior with that executable?
Edit
I have looked at similar question such as this one:
process.standardoutput.ReadToEnd() always empty?
I don't think I am doing something wrong. I guess there is something weird with that executable. To use that executable you may download it at: http://supp.iar.com/Download/SW/?item=EWARM-EVAL or at http://www.iar.com/en/Products/IAR-Embedded-Workbench/ARM/ . Then go to C:\Program Files (x86)\IAR Systems\Embedded Workbench 6.4 Evaluation\arm\bin and execute jlink.exe (If you do not have a board connected via usb you will get an error but it does not matter. the error message does not show up either).
I want to run git commands from c#. below is the coded I had written and it does execute the git command but I am not able to capture the return value. When I manually run it from command line this is the output I get.
When I run from the program the only thing I get is
Cloning into 'testrep'...
Rest of the info is not capture, but the command is executed successfully.
class Program
{
static void Main(string[] args)
{
ProcessStartInfo startInfo = new ProcessStartInfo("git.exe");
startInfo.UseShellExecute = false;
startInfo.WorkingDirectory = #"D:\testrep";
startInfo.RedirectStandardInput = true;
startInfo.RedirectStandardOutput = true;
startInfo.Arguments = "clone http://tk1:tk1#localhost/testrep.git";
Process process = new Process();
process.StartInfo = startInfo;
process.Start();
List<string> output = new List<string>();
string lineVal = process.StandardOutput.ReadLine();
while (lineVal != null)
{
output.Add(lineVal);
lineVal = process.StandardOutput.ReadLine();
}
int val = output.Count();
process.WaitForExit();
}
}
From the manual page for git clone:
--progress
Progress status is reported on the standard error stream by default when it is attached to
a terminal, unless -q is specified. This flag forces progress status even if the standard
error stream is not directed to a terminal.
The last three lines in the output when running git clone interactively are sent to standard error, not standard output. They won't show up there when you run the command from your program, however, since it's not an interactive terminal. You could force them to appear, but the output isn't going to be anything usable for a program to parse (lots of \rs to update the progress values).
You are better off not parsing the string output at all, but looking at the integer return value of git clone. If it's nonzero, you had an error (and there will probably be something in standard error that you can show to your user).
Have you tried libgit2sharp? The documentation is not complete, but it is pretty easy to use and there's a nuget package for it. You can always look at the test code to see about usage as well. A simple clone would be like this:
string URL = "http://tk1:tk1#localhost/testrep.git";
string PATH = #"D:\testrep";
Repository.Clone(URL, PATH);
Fetching changes is easy as well:
using (Repository r = new Repository(PATH))
{
Remote remote = r.Network.Remotes["origin"];
r.Network.Fetch(remote, new FetchOptions());
}
Once you call process.WaitForExit() and the process has terminated, you can simply use process.ExitCode which will get you the value that you want.
Your code Looks OK.
this is git problem.
git clone git://git.savannah.gnu.org/wget.git 2> stderr.txt 1> stdout.txt
stderr.txt is empty
stdout.txt:
Cloning into 'wget'...
It looks like git not uses standard console.write() like output you can see it when it writes percentage it's all in one line not like:
10%
25%
60%
100%
process.StandardError.ReadToEnd() + "\n" + process.StandardOutput.ReadToEnd();
I found the following snippet of the code:
using System;
using System.Diagnostics;
public class RedirectingProcessOutput
{
public static void Main()
{
Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c dir *.cs";
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
Console.WriteLine("Output:");
Console.WriteLine(output);
}
}
but I can't figure out what this p.StartInfo.Arguments = "/c dir *.cs"; is doing? thanks in advance for any explanation
It's passing command line arguments to the process that will be launched.
In this particular case, the process is the Windows shell (cmd.exe). Passing a command line to it will cause it to execute this command when started; then, because of the /c parameter at the beginning it will terminate itself.
So the output of the process will be exactly what you will get if you open a command prompt and enter the command dir *.cs.
In the beginning was exec(3) and its friends, which accept the path to an executable and a variable length list of pointers to arguments. In sane operating systems, The process that gets started receives a point to the argument list, each word of which contains a pointer to and individual string. Sane shells parse the command line and populate the argument list required by exec(3).
You can see a direct correlation between the argument list accepted by exec(3):
exec ("some.executable.file", "arg1" , "arg2" , "arg3" , ... ) ;
and what gets passed to the entrypoint of the process:
int main ( char *arg[] ) { ... }
where argv[0] is the executable name, and argv[1]—argv[n-2] are the individual arguments, and argv[n-1] is a NULL pointer to indicate the end of the argument list.
Both conceptually simple and simple to implement.
CP/M didn't do it that way (I assume because of limited memory). It passed the started process the address of the raw command line from the shell and left its parsing up the process.
DOS followed in 1982 as clone of CP/M, handing a started process the address of the raw command line as well.
Windows hasn't deviated from that model since its inception. The Win32 CreateProcess() function
BOOL WINAPI CreateProcess(
__in_opt LPCTSTR lpApplicationName,
__inout_opt LPTSTR lpCommandLine,
...
);
still does the same thing, passing the raw command line to be passed to the program. The C runtime library, of course, takes care of the command line parsing for you...more or less.
So...in the CLR/.Net world, because of all this history, and because the CLR was designed to be dependent on the Win32 APIs, you have pass a complete command line to the process to be started. Why they didn't let you pass a params string[], instead and have the CLR build the command line is something that Microsoft's developers kept close to their chest.
Building the command line required by the started program is simple. You just join each argument into a single string with an SP character separating the arguments. Easy!
...until one of your arguments contains whitespace or a double quote character (").
Then you have to quote one or all of the arguments. Should be easy, but due to the bizarre quote rules, there are a lot of edge conditions that can trip you up.
A Windows command-line is broken up into words separated by whitespace, optionally quoted with double-quoted ("). Partly because Windows also got the path separater wrong (\ rather than /), the quoting rules are...byzantine. If you dig into the Windows CRT source code (the file is something like {VisualStudioInstallLocation}\VC\crt\src\stdargv.c), you'll find the command line parsing code.
That line just gives an argument to that proccess.
just downloaded ActivePerl. I want to embed the perl interpreter in a C# application (or at least call the perl interpreter from C#). I need to be able to send send out data to Perl from C#, then receive the output back into C#.
I just installed ActivePerl, and added MS Script Control 1.0 as a reference. I found this code on the internet, but am having trouble getting it to work.
MSScriptControl.ScriptControlClass Interpreter = new MSScriptControl.ScriptControlClass();
Interpreter.Language = #"ActivePerl";
string Program = #"reverse 'abcde'";
string Results = (string)Interpreter.Eval(Program);
return Results;
Originally, it had 'PerlScript' instead of 'ActivePerl', but neither work for me. I'm not entirely sure what Interpreter.Language expects. Does it require the path to the interpreter?
Solved... I'm not sure how, but when I changed it back to PerlScript it works now. Still, I would like to know if MSScript Control is using ActivePerl or another interpreter.
You can run an external program as Maxwell suggests, in which case the external program can be Perl or anything else. It might be easier to use temp files to send the input data and get the output, but that depends on how the external program expects to get its data.
The alternative, which is what I think you're looking for, is to use the PerlNET compiler that comes with ActiveState's Perl Dev Kit. It lets you add a class wrapper around the Perl code so you can expose it to C# just like any C# class. It's fairly simple to use; you add POD comments to your Perl code to specify the method names and signatures to expose, including type information, then you compile your Perl module into a DLL .NET assembly. Once that's done you can reference the assembly from any .NET program, construct an object from your Perl class, and call its methods.
I am not sure about the script control but I have done a similar thing where I had to 'embed' spamassasin (which is a Perl program). I basically used the Process to do the job. Something along the lines of:
var proc = new Process
{
StartInfo =
{
FileName = "perl",
WorkingDirectory = HttpRuntime.AppDomainAppPath,
Arguments = " myscript.pl arg1 arg2",
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
UseShellExecute = false
}
};
proc.Start();
proc.StandardInput.BaseStream.Write... // feed STDIN
proc.StandardOutput.Read... // Read program output
var procStdErr = proc.StandardError.ReadToEnd(); // errors
proc.StandardError.Close();
proc.StandardOutput.Close();
proc.WaitForExit(3000);
int exitCode = proc.ExitCode;
proc.Close();
This obviously not just Perl specific and it has the process creation overhead, so if you are running your script too often probably you need to think of a different solution.