Starting an executable and passing arguments - c#

I have definitely done something like this before, but something is not working right and I'm not 100% sure what it is.
I have an executable executable.exe that takes a file, does some magic and outputs a second file somewhere else. So when I run this executable via CMD, all I need to do is pass "path1" and "path2". I put the paths in quotations because they may have spaces.
Anyway, so what I'm doing in my c# application is something like:
public void methodToRunExecutable()
{
var exePath = "\""+ "C:\\SomePathToAnExecutable" + "\"";
var firstFilePath = "C:\\PathToFirstFile\\NameOfFile.txt"
var secondFilePath= "C:\\PathToSecondFile\\NameOfFile.txt"
Process.Start(exePath, "\""firstFilePath + "\" \"" + secondFilePath+"\"")
}
However, I notice when I'm debugging is that the "\"" is actually showing up as \", like the backslash isn't escaping the quotation mark.
To be clear, when I run the CMD exe all I have to do is:
"C:\\PathToFirstFile\\NameOfFile.txt" "C:\\PathToSecondFile\\NameOfFile.txt"
and it works great. Any Ideas on what I'm doing wrong? Is it because the " is not being escaped?

Escaping is ugly and error prone. Use # and you won't need to escape:
var firstFilePath = #"C:\PathToFirstFile\NameOfFile.txt"
It might also be easier to use Process this way:
using (Process process = new Process())
{
process.StartInfo.FileName = exePath;
process.StartInfo.Arguments = ""; // args here
process.Start();
}

Related

calling devcon from inside windows forms not working

Plan
The plan is to disable and subsequently enable a device from inside a windows forms application. To test the first building block of my plan, I open cmd with admin privileges and the following works perfectly:
> devcon hwids =ports
> devcon hwids *VID_10C4*
> devcon disable *VID_10C4*
> devcon enable *VID_10C4*
I can see the device being disabled and enabled again in device manager.
I can also achieve all of this by putting the commands into a batch file and running it from cmd with admin privileges. The above tells me that my plan is essentially good.
Application
However, what I actually want to do is achieve the same thing from inside a windows forms application:
I've set the following in the app manifest:
requestedExecutionLevel level="requireAdministrator" uiAccess="false"
For the sake of baby steps, I have checked this, just to ensure that there are no stupid mistakes in paths and whatnot. And it works just fine. The log file shows me the expected output from the dir command.
// Build String
string strCmdText =
"'/c cd " + prodPath +
" && dir " +
" > logs\\logFileEnablePrt.txt \"'";
// Run command
var p = new System.Diagnostics.Process();
var psi = new ProcessStartInfo("CMD.exe", strCmdText);
psi.Verb = "runas"; // admin rights
p.StartInfo = psi;
p.Start();
p.WaitForExit();
However, this does not work. It always returns an empty log file and does not change the device as expected:
// Build String
string strCmdText =
"'/c cd " + prodPath +
" && devcon hwids =ports " +
" > logs\\logFileEnablePrt.txt \"'";
// Run command
var p = new System.Diagnostics.Process();
var psi = new ProcessStartInfo("CMD.exe", strCmdText);
psi.Verb = "runas"; // admin rights
p.StartInfo = psi;
p.Start();
p.WaitForExit();
Error from cmd window is :
'devcon' is not recognized as an internal or external command,
operable program or batch file.
What's going on?
The above has me stumped. I've proved the commands work. I've proved my C# code works. But when I join the 2 together, it doesn't work...
NB: My C# program is running on my D: drive, if that makes any difference...
Updates Based on Comments
#Compo
Using your code, it does exactly the same as with mine. I see an empty log file & no changes made to the device. I've altered the /c to /k so I can see what going on the cmd terminal and I see this:
I've even tried your code C:\\Windows\\System32\\devcon hwids =usb pointing directly at devcon. Also tried \devcon.exe for completeness. The inexplicable error is :
I can see the flipping devcon.exe file sitting right there in the folder! Is there any reason it would not recognise it?
Also, with the command as you wrote it, the log file name is actually named logFileEnablePrt.txt'. I agree that your command looks right, so don't ask me why this happens!
#Panagiotis Kanavos
using your code, I get the following error:
This is at the line p.Start();. I tried putting in devcon.exe, and even the whole path (I checked the folder was in my PATH, and it is). Can't get past this. I actually stumbled on that answer you shared and arrived at this brick wall already.
Here is the code works for me, I don't have ports devices so I change it to usb.
public static void Main()
{
string prodPath = #"c:\devcon\x64";
// Build String
string strCmdText =
"/c \"cd /d " + prodPath +
" && devcon hwids =usb " +
" > log.txt \"";
// Run command
var p = new Process();
var psi = new ProcessStartInfo("CMD.exe", strCmdText);
psi.Verb = "runas"; // admin rights
p.StartInfo = psi;
p.Start();
p.WaitForExit();
}
Worked through a few steps and think I may have an answer...
Just specifying devcon fails as expected...windows cant find the exe as the folder it is in is not in the %PATH% variable in windows..
IF I specify the full path however it works...
It wasnt clear from your original code but if your copy of devcon is sitting in either System32 or Syswow directories you may be hitting an emulation issue as well...see here....
EDIT1:: A way to prove this would be to do Direcory.GetFiles(directory containing devcon) and see if the results line up with what you expect
As for passing arguments through to devcon I'd try something like this as opposed to trying to concatenate one giant cmd line..
A similar example but with netstat:
EDIT 2::Another example but with devcon:
The target platform here for the build was x64
EDIT3::
With my application build set to x86:
After working through the answers and comments above, I seem to have something that reliably works, which obviously I'd like to share back for scrutiny and future use.
So, my function ended up looking like this:
private int enablePort(string action)
{
while (true)
{
// Command Arg
string devconPath = #"c:\Windows\SysNative";
string strCmdText =
"'/c \"cd /d \"" +
devconPath +
"\" && c:\\Windows\\SysNative\\devcon " + action + " *VID_10C4* " +
"> \"" + prodPath + "\\logs\\logFileEnablePrt.txt\"\"";
// Process
var p = new Process();
var psi = new ProcessStartInfo()
{
Arguments = strCmdText,
Verb = "runas",
FileName = "CMD.exe",
UseShellExecute = true
};
p.StartInfo = psi;
p.Start();
p.WaitForExit();
// Grab log output
string logPath = prodPath + "\\logs\\logFileEnablePrt.txt";
Console.WriteLine("logPath = " + logPath);
string tempFile = System.IO.File.ReadAllText(logPath);
System.Console.WriteLine("Contents of WriteText.txt = \n{0}", tempFile);
// Check if it worked
var success = false;
if (tempFile.Contains(action))
{
success = true;
return 0;
}
// Error -> Allow user to try again!
if (MessageBox.Show("Was unable to " + action + " Test Jig COM port. Unlug & Replug USB. Check COM port is enabled if not working.", "COM Port Problem", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
{
return -1;
}
}
}
And the calling code was:
this.enablePort("disable");
int milliseconds = 3000;
await Task.Delay(milliseconds);
this.enablePort("enable");
As you can see in the code above, I've logged everything to see what was going on... Stepping through with the debugger, I can now see after the disable:
USB\VID_10C4&PID_EA60\0001 : Disabled
1 device(s) disabled.
And then after the enable:
USB\VID_10C4&PID_EA60\0001 : Enabled
1 device(s) are enabled.
The one extra thing I need to stress is that during testing, I thought I could hook a serial peripheral onto the port and determine whether it could disable and enable successfully by checking the connection. THIS DOES NOT WORK. The above code only works when the port is idle. Perhaps someone who understands the underlying software could hazard an explanation of why this is.

Execute multiple commands in same environment from C#

I'm developing a small C# GUI tool which is supposed to fetch some C++ code and compile it after going through some wizard. This works all nice if I run it from a command prompt after running the famous vcvarsall.bat. Now I would like the user not to go to a command prompt first but have the program call vcvars followed by nmake and other tools I need. For that to work the environment variables set by vcvars should obviously be kept.
How can I do that?
The best solution I could find yet was to create a temporary cmd/bat script which will call the other tools, but I wonder if there is a better way.
Update: I meanwhile experimented with batch files and cmd. When using batch files vcvars will terminate the complete batch execution so my second command (i.e. nmake) won't be executed. My current workaround is like this (shortened):
string command = "nmake";
string args = "";
string vcvars = "...vcvarsall.bat";
ProcessStartInfo info = new ProcessStartInfo();
info.WorkingDirectory = workingdir;
info.FileName = "cmd";
info.Arguments = "/c \"" + vcvars + " x86 && " + command + " " + args + "\"";
info.CreateNoWindow = true;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
Process p = Process.Start(info);
This works, but the output from the cmd call is not captured. Still looking for something better
I have a couple of different suggestions
You may want to research using MSBuild instead of NMake
It's more complex, but it can be controlled directly from .Net, and it is the format of VS project files for all projects starting with VS 2010, and for C#/VB/etc. projects earlier than that
You could capture the environment using a small helper program and inject it into your processes
This is probably a bit overkill, but it would work. vsvarsall.bat doesn't do anything more magical than set a few environment variables, so all you have to do is record the result of running it, and then replay that into the environment of processes you create.
The helper program (envcapture.exe) is trivial. It just lists all the variables in its environment and prints them to standard output. This is the entire program code; stick it in Main():
XElement documentElement = new XElement("Environment");
foreach (DictionaryEntry envVariable in Environment.GetEnvironmentVariables())
{
documentElement.Add(new XElement(
"Variable",
new XAttribute("Name", envVariable.Key),
envVariable.Value
));
}
Console.WriteLine(documentElement);
You might be able to get away with just calling set instead of this program and parsing that output, but that would likely break if any environment variables contained newlines.
In your main program:
First, the environment initialized by vcvarsall.bat must be captured. To do that, we'll use a command line that looks like cmd.exe /s /c " "...\vcvarsall.bat" x86 && "...\envcapture.exe" ". vcvarsall.bat modifies the environment, and then envcapture.exe prints it out. Then, the main program captures that output and parses it into a dictionary. (note: vsVersion here would be something like 90 or 100 or 110)
private static Dictionary<string, string> CaptureBuildEnvironment(
int vsVersion,
string architectureName
)
{
// assume the helper is in the same directory as this exe
string myExeDir = Path.GetDirectoryName(
Assembly.GetExecutingAssembly().Location
);
string envCaptureExe = Path.Combine(myExeDir, "envcapture.exe");
string vsToolsVariableName = String.Format("VS{0}COMNTOOLS", vsVersion);
string envSetupScript = Path.Combine(
Environment.GetEnvironmentVariable(vsToolsVariableName),
#"..\..\VC\vcvarsall.bat"
);
using (Process envCaptureProcess = new Process())
{
envCaptureProcess.StartInfo.FileName = "cmd.exe";
// the /s and the extra quotes make sure that paths with
// spaces in the names are handled properly
envCaptureProcess.StartInfo.Arguments = String.Format(
"/s /c \" \"{0}\" {1} && \"{2}\" \"",
envSetupScript,
architectureName,
envCaptureExe
);
envCaptureProcess.StartInfo.RedirectStandardOutput = true;
envCaptureProcess.StartInfo.RedirectStandardError = true;
envCaptureProcess.StartInfo.UseShellExecute = false;
envCaptureProcess.StartInfo.CreateNoWindow = true;
envCaptureProcess.Start();
// read and discard standard error, or else we won't get output from
// envcapture.exe at all
envCaptureProcess.ErrorDataReceived += (sender, e) => { };
envCaptureProcess.BeginErrorReadLine();
string outputString = envCaptureProcess.StandardOutput.ReadToEnd();
// vsVersion < 110 prints out a line in vcvars*.bat. Ignore
// everything before the first '<'.
int xmlStartIndex = outputString.IndexOf('<');
if (xmlStartIndex == -1)
{
throw new Exception("No environment block was captured");
}
XElement documentElement = XElement.Parse(
outputString.Substring(xmlStartIndex)
);
Dictionary<string, string> capturedVars
= new Dictionary<string, string>();
foreach (XElement variable in documentElement.Elements("Variable"))
{
capturedVars.Add(
(string)variable.Attribute("Name"),
(string)variable
);
}
return capturedVars;
}
}
Later, when you want to run a command in the build environment, you just have to replace the environment variables in the new process with the environment variables captured earlier. You should only need to call CaptureBuildEnvironment once per argument combination, each time your program is run. Don't try to save it between runs though or it'll get stale.
static void Main()
{
string command = "nmake";
string args = "";
Dictionary<string, string> buildEnvironment =
CaptureBuildEnvironment(100, "x86");
ProcessStartInfo info = new ProcessStartInfo();
// the search path from the adjusted environment doesn't seem
// to get used in Process.Start, but cmd will use it.
info.FileName = "cmd.exe";
info.Arguments = String.Format(
"/s /c \" \"{0}\" {1} \"",
command,
args
);
info.CreateNoWindow = true;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
foreach (var i in buildEnvironment)
{
info.EnvironmentVariables[(string)i.Key] = (string)i.Value;
}
using (Process p = Process.Start(info))
{
// do something with your process. If you're capturing standard output,
// you'll also need to capture standard error. Be careful to avoid the
// deadlock bug mentioned in the docs for
// ProcessStartInfo.RedirectStandardOutput.
}
}
If you use this, be aware that it will probably die horribly if vcvarsall.bat is missing or fails, and there may be problems with systems with locales other than en-US.
There is probably no better way than collect all the data you need, generate bat file and run it using Process class.
As you wrote, you are redirecting output, which means you must set UseShellExecute = false; so I think there is no way to set your variables other then calling SET from the bat file.
EDIT: adding a specific use case for nmake calling
I've needed to get various "build path stuff" in the past, and this is what I've used - you may need to tweak things here or there to suit, but basically, the only thing that vcvars does is set up a bunch of paths; these helper methods go fetch those path names, you'd just need to pass them into your start info:
public static string GetFrameworkPath()
{
var frameworkVersion = string.Format("v{0}.{1}.{2}", Environment.Version.Major, Environment.Version.Minor, Environment.Version.Build);
var is64BitProcess = Environment.Is64BitProcess;
var windowsPath = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
return Path.Combine(windowsPath, "Microsoft.NET", is64BitProcess ? "Framework64" : "Framework", frameworkVersion);
}
public static string GetPathToVisualStudio(string version)
{
var is64BitProcess = Environment.Is64BitProcess;
var registryKeyName = string.Format(#"Software\{0}Microsoft\VisualStudio\SxS\VC7", is64BitProcess ? #"Wow6432Node\" : string.Empty);
var vsKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(registryKeyName);
var versionExists = vsKey.GetValueNames().Any(valueName => valueName.Equals(version));
if(versionExists)
{
return vsKey.GetValue(version).ToString();
}
else
{
return null;
}
}
And you'd take advantage of this stuff via something like:
var paths = new[]
{
GetFrameworkPath(),
GetPathToVisualStudio("10.0"),
Path.Combine(GetPathToVisualStudio("10.0"), "bin"),
};
var previousPaths = Environment.GetEnvironmentVariable("PATH").ToString();
var newPaths = string.Join(";", previousPaths.Split(';').Concat(paths));
Environment.SetEnvironmentVariable("PATH", newPaths);
var startInfo = new ProcessStartInfo()
{
FileName = "nmake",
Arguments = "whatever you'd pass in here",
};
var process = Process.Start(startInfo);

A method to execute cmd prompt command going wrong

I have this method
private void _executeCommand(string commandStr, int timeout)
{
try
{
System.Diagnostics.ProcessStartInfo procStartInfo =
new System.Diagnostics.ProcessStartInfo("cmd", "/c " + commandStr);
procStartInfo.RedirectStandardOutput = true;
procStartInfo.UseShellExecute = false;
// Do not create the black window.
procStartInfo.CreateNoWindow = true;
// Now we create a process, assign its ProcessStartInfo and start it
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo = procStartInfo;
proc.Start();
Thread.Sleep(timeout);
}
catch (ExecutionEngineException e)
{
throw e;
}}
somehow, if I pass a string called myCmd, _executeCommand(myCmd, timeout), it does nothing. But if I pass the exact string value of myCmd, _executeCommand("copy //data//file \"C://Program Files/myApp\"", timeout), it was able to execute. Could anyone see what the problem is?
If you have a command that has its own arguments you want to execute using /c, you will need to enclose the entire command and its argument after /c in quotes. Further, if you're using a path that contains spaces as one of the arguments to your command you'll have to quote that too, i.e #"/c ""copy ""C:\My Folder\Ny File.txt"" ""C:\My Other Folder"""""
Also, If you have multiple commands that you want it to perform you will either have to put the commands in a batch file and execute that or enclose them in quotes and separate them with &&, i.e. #"/c ""cd \ && dir""".
Note that the doubling of quotes in my examples is how you escape quotes when using a string literal in C#. The # preceding the string tells the compiler to take the string literally and not to interpret \ characters as special.
Taking a look at my answer to Sending commands to cmd prompt in C# is probably a good idea too as it explains a lot about how this stuff works.

merging the content of a directory using cmd shell

I want to merge all the files in a directory into one. However I tried several versions but none of them seem to work. I am getting an error saying that the file was not found. Here is what I was trying:
String outputFile = this.outputTxt.Text;
String inputFolder = this.inputTxt.Text;
String files = "";
String command;
foreach (String f in Directory.GetFiles(inputFolder))
{
files += f+"+";
}
files = files.Substring(0, files.Length - 1);
command = files + " " + outputFile;
Process.Start("copy",command);
sample of what I want to obtain:
copy a.txt+b.txt+c.txt+d.txt output.txt
And the error I get is:
An unhandled exception of type 'System.ComponentModel.Win32Exception' occurred in System.dll
Additional information: The system cannot find the file specified
Try starting cmd rather than "start" with process.
Process.Start("cmd", "copy " + command);
'copy' is a command in the command prompt, aliased to... something, and not an actual file itself that windows knows how to run (outside of the command prompt).
There are properties of the Process class that you can use to suppress the window that the shell pops up if you don't want it on the screen while the program is running.
Should you not be using command instead of files for your second parameter to Process.Start?
Process.Start("copy", command);
UPDATE:
Ok, so it was a typo. How about your inputFolder text? Is it using double back-slashes for the directories (escaping the back-slashes)? As in all \ characters should be \\.
You need to call cmd.exe with the copy command and your arguments (as was mentioned by #Servy). Here is a cleaned up version of your code to do what you need:
String outputFile = this.outputTxt.Text;
String inputFolder = this.inputTxt.Text;
StringBuilder files = new StringBuilder();
foreach (String f in Directory.EnumerateFiles(inputFolder))
{
files.Append(f).Append("+");
}
files = files.Remove(file.Length-1, 1); // Remove trailing plus
files.Append(" ").Append(outputFile);
using (var proc = Process.Start("cmd.exe", "/C copy " + files.ToString()))
{
proc.WaitForExit();
}
You need to dispose of the Process (thus the using statement) and since you are concatenating a lot of strings (potentially a lot of strings anyway), you should use a StringBuilder.

app crashes with ProcessStartInfo arguments

I am trying to run a process called "prog.exe" with the arguments "blah $00" (sort of a code) but whatever I try fails.
string file = "blah $00";
string result = string.Empty;
ProcessStartInfo P = new ProcessStartInfo(#"""" + "prog.exe" + #"""");
P.Arguments = #"""" + file + #"""";
P.CreateNoWindow = true;
P.UseShellExecute = false;
P.RedirectStandardOutput = true;
Process.Start(P);
using (Process process = Process.Start(P))
{
using (StreamReader str = process.StandardOutput)
result = str.ReadToEnd();
}
MessageBox.Show(result);
When this code is executed, my program just crashes and I am forced to close it using the Task Manager.
I am not sure what's wrong with my code (am I not setting the arguments correctly?), so any help would be appreciated.
Run your process with given argument from console and see what happens. If result is something you expect, just remove double quotes and this should resolve your problem.
I don't think your program crashes. It just waits for "prog.exe" to finish! I bet, that your program continues running as soon as you are done working with prog.exe and close it - and make sure in task manager that it really is gone.

Categories

Resources