I am writing a C# program that needs to run a script. I want to include the script with the application so that it is available when the user installs the program after I publish it.
I tried adding the script as a resource. In the Solution Explorer, under the Resources directory, I can see the script file.
In the program, I call a function that starts a process and runs the desired command:
runNewProcess("tclsh \\Resources\\make.tcl " + activeProducts);
I get the command prompt with the message "couldn't read file "\Resources\make.tcl": no such file or directory". So I guess it cannot find the file? Am I not referencing the file correctly? Is this the correct way of doing something like this?
Thank you all for your suggestions. Using them and with a bit more research, I was able to come up with a perfect solution for me.
1) Add the TCL script file as a resource to the project and set the Build Action to 'Content' in it's Properties.
2) Get the path to the TCL script (even after installation from a published version):
string makeScriptPath = System.Windows.Forms.Application.StartupPath + "\\Resources\\make.tcl";
3) Construct the run command using all the required variables and pass it to a routine that can execute it.
localCommand = String.Format("tclsh \"{0}\" --librarytype {1} --makeclean {2} --buildcode {3} --copybinary {4} --targetpath \"{5}\" --buildjobs {6} --products {7}",
makeScriptPath, library, makeClean, buildCode, copyBinary, targetPath, buildJobs, activeProducts);
runNewProcess(localCommand);
where:
private void runNewProcess(string command)
{
System.Diagnostics.ProcessStartInfo procStartInfo =
new System.Diagnostics.ProcessStartInfo("cmd", "/k " + command);
procStartInfo.RedirectStandardOutput = false;
procStartInfo.UseShellExecute = true;
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();
}
This gives some added perks. Since the file is included with the application, but remains a separate entity, this allows it to be tweaked and modified without needing to re-build, re-publish and re-install the application.
The script runner is unable to dig into you executable to find the commands, as it most likely only know what to do with files on disk. Shipping as a resource is a good idea, but for make anything useful with it you should extract it into a real file on disk so that other programs can use it.
A good pattern for such things would be to create a temporary file on %TEMP%, make the script runner execute that file, and delete it afterwards.
To expand on Alejandro's answer, The easiest way to handle this is to use the temporary folder and copy your script there first.
var scriptPath = Path.Combine(Path.GetTempPath(), "make.tcl");
// Copy the text of the script to the temp folder. There should be a property
//you can reference associated with the script file if you added the file using
//the resources tab in the project settings. This will have the entire script in
//string form.
File.WrteAllText(scriptPath, Resources.make);
runNewProcess("tclsh \"" + scriptPath + "\"" + activeProducts); //added quotes in case there are spaces in the path to temp.
File.Delete(scriptPath); //Clean up after yourself when you are done.
You need to ensure that the Build Action of the script file is set to Content to keep it as an independent file. By default it will be set to Resource which means you will have to programmatically extract it and then save it to a temporary location before attempting to run it.
Related
I have been looking all over but cannot find a definitive answer/solution, or any solution I try fails. I am making a WinFormApp that calls an embedded .vbs file to script a program I use at work. This is what my Project Solution looks currently:
Project Solution
The test.vbs file is set as an embedded resource in its file properties. Here is the different code I have tried to use to run the script from the form:
private void button1_Click(object sender, EventArgs e)
{
string path = Path.Combine(Path.GetTempPath(), "test.vbs");
File.WriteAllBytes(path, Properties.Resources.Test);
Process.Start(path);
string path2 = Path.Combine(Path.GetTempPath(), "test.vbs");
System.Diagnostics.Process.Start(#"cscript //B //Nologo " + path2 + "");
}
Here is what my test.vbs file is:
dim thing
thing = "It did something!"
Wscript.Echo thing
The test.vbs file is primarily meant as a proof of concept to make sure I can at least run a .vbs file. The test.vbs file compiles fine outside of the WinForm.
Most of the time I receive 'The system cannot find the file specified' as the error message. I have read that it may be easier to just convert my .vbs file to C# but they are GUI scripting for SAP and all of SAP's libraries seem primarily set us for .vbs files.
I am still relatively new to C# so I may be way off with this so please tell me if I am. If there is another question that fixes my issue please link it.
Thank you for your time!
EDIT #1 Code compiles and seems to run.
string path2 = Path.Combine(Path.GetTempPath(), "test.vbs");
var startInfo = new ProcessStartInfo
{
WorkingDirectory = "" + path2 + "",
FileName = #"cscript"
};
However the script does not seem to be outputting to the cmd...
Your process start command is incorrect. Here is the correct version
System.Diagnostics.Process.Start("cscript", #"//B //Nologo " + path2 + "");
I've made a C++ code editor in C# and now am trying to invoke g++ to compile the source files. So I've copied the TDM-GCC installation besides my program and wrote a small batch script to call that.
#echo off
#set PATH="%~dp0TDM-GCC-32\bin\";%PATH%
call "%~dp0TDM-GCC-32\mingwvars.bat"
cd %1
"%~dp0TDM-GCC-32\bin\g++.exe" %2 -o %3
And from C# code, I'm trying to call this script by using CMD like in this script of code.
string fileName = Path.GetFileName(CurrentFile);
string exeName = Path.GetFileNameWithoutExtension(CurrentFile) + ".exe";
string workingDir = Directory.GetParent(CurrentFile) + "";
string compile = Directory.GetParent(Application.ExecutablePath) + "\\compile.cmd";
File.Delete(Path.Combine(workingDir, exeName));
StartProcess(true, "cmd", "/c", "\"" + compile + "\"", workingDir, fileName, exeName);
And here is the StartProcess method:
void StartProcess(bool hidden, string command, params string[] args)
{
ProcessStartInfo pStartInfo = new ProcessStartInfo();
pStartInfo.FileName = command;
pStartInfo.Arguments = string.Join(" ", args);
pStartInfo.UseShellExecute = false;
if (hidden)
{
pStartInfo.RedirectStandardError = true;
pStartInfo.RedirectStandardOutput = true;
pStartInfo.CreateNoWindow = true;
}
Process proc = new Process();
proc.StartInfo = pStartInfo;
proc.Start();
logBox.Clear();
if (hidden)
{
while (!proc.StandardError.EndOfStream)
{
logBox.AppendText(GetTimestamp() + " Error: " + proc.StandardError.ReadLine() + Environment.NewLine);
}
}
}
Update: 26-9-14
I doubted whether the batch file is ever called, because if I call it from cmd, it simply works. So I tried to echo to the standard error from the batch file like this:
echo %PATH% 1>&2
And I can see that the GCC's bin folder is in the path too, but for some reason, the exe is not getting created. But sometimes, the script works, and the exe gets created.
But whenever I execute this, there will be nothing in the LOG and also, no executable is being created. I know that the TDM-GCC's bin folder must be in the PATH, but that is what the second line of the batch script needs to do. Dunno what this is and why this error.
End of update
Any suggestions on how can I get it working?
Thanks.
I don't know if anything written below really helps on this issue. But at least those hints should be useful in any case.
I suggest for the batch file
#echo off
call "%~dp0TDM-GCC-32\mingwvars.bat"
set "PATH=%~dp0TDM-GCC-32\bin;%PATH%"
cd /D %1
"%~dp0TDM-GCC-32\bin\g++.exe" %2 -o %3
PATH is modified after execution of mingwvars.bat. It could be that this batch file also modifies PATH. Or it runs commands like find.exe with the executables expected in %SystemRoot%\System32, but perhaps also existing in directory bin. I have seen already several times not working logon batch scripts because PATH on client computer contained as first folder path the bin directory of a compiler ported from Unix to Windows with executables also found in %SystemRoot%\System32, but working completely different as ported from Unix.
Folder paths should be added to environment variable PATH always without double quotes even if the folder path contains 1 or more spaces. The double quotes used in third line just make sure that a trailing space is not added additionally to PATH and that command set works even with not typical folder paths like an ampersand in path.
And folder paths should be added to environment variable PATH without a trailing backslash.
On command cd the parameter /D is additionally used in case of a change to a different drive must be performed, too. The command cd would not change the current directory if the specified path is on a different drive without parameter /D.
In C# code you have to make sure that workingDir, fileName and exeName are finally on execution of cmd.exe enclosed in double quotes in the arguments string as Erti-Chris Eelmaa already wrote.
And it would be perhaps better to read in your C# application the value of environment variable ComSpec and use this value instead of just cmd for executing the batch file.
This might, or might not be part of the problem, but you don't take care of spaces.
pStartInfo.Arguments = string.Join(" ", args);
you probably would want something like this:
pStartInfo.Arguments = string.Join(" ", args.Select(x => "\"" + x + "\""));
I've been searching and experimenting for a while with this, but I have had no luck.
I am trying to make a console program to automate some tasks that I couldn't quite do with a BAT file. I want to call "signcode.exe" from the Windows SDK, the bin folder with all the tools in my system PATH, and I can call "signcode" from anywhere, but Process.Start is ignoring the path.
Current code:
System.Diagnostics.Process sign = new System.Diagnostics.Process();
sign.StartInfo.FileName = signCommand.Substring(0, signCommand.IndexOf(' ')); // signtool.exe
sign.StartInfo.Arguments = signCommand.Substring(signCommand.IndexOf(' ') + 1); // /sign /a file1 file2
// sign.StartInfo.EnvironmentVariables["Path"] = Environment.GetEnvironmentVariable("PATH"); // This doesn't work either
sign.StartInfo.UseShellExecute = false;
sign.StartInfo.RedirectStandardOutput = true;
sign.StartInfo.RedirectStandardError = true;
sign.Start(); // Throws Win32Exception - The system cannot find the file specified
I've confirmed that StartInfo.EnvironmentVariables["Path"] matches my system path, and contains the Windows SDK folder. Setting it manually doesn't work either.
I've even tried setting TempPath as shown on the MSDN page for EnvironmentVariables Property, but that didn't work either. I wonder why you would be able to set this if it has no effect.
If System.Diagnostics.Process cannot use the path, are there any other functions I could use? I'd like to see the output of the command in my console application as well.
Here is some additional debug values:
Console.WriteLine("Sign Filename = '{0}'", sign.StartInfo.FileName);
Sign Filename = 'signtool.exe'
Console.WriteLine("Sign Arguments = '{0}'", sign.StartInfo.Arguments);
Sign Arguments = '/sign /f C:\Visual Studio\Projects\MGInsight\MGInsight\APPARENTINC.pfx /t http://timestamp.comodoca.com/authenticode "C:\Visual Studio\Projects\MGInsight\MGInsight\Publish\Application Files\\MGInsight_0_9_1_85\MGInsight.exe" "C:\Visual Studio\Projects\MGInsight\MGInsight\Publish\Application Files\\MGInsight_0_9_1_85\XPXScanner.dll" "C:\Visual Studio\Projects\MGInsight\MGInsight\Publish\Application Files\\MGInsight_0_9_1_85\NetworkCalculations.dll"'
Console.WriteLine("Sign Path = '{0}'", sign.StartInfo.EnvironmentVariables["Path"]);
Sign Path = 'C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;"C:\Program Files\Intel\WiFi\bin\";"C:\Program Files\Common Files\Intel\WirelessCommon\";"C:\Program Files (x86)\cwRsync\bin";"C:\Program Files (x86)\Git\cmd";"C:\Program Files (x86)\Git\bin";"C:\Program Files (x86)\Zend\ZendServer\bin";"C:\Program Files (x86)\Zend\ZendServer\share\ZendFramework\bin";"C:\Program Files\Java\jre6\bin";"C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\";"C:\Program Files\Microsoft Windows Performance Toolkit\";C:\MinGW\bin;"C:\Program Files (x86)\Microsoft\ILMerge";"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin";C:\Program Files (x86)\Nmap'
The path "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin" is where signtool.exe is, and I can run it from a command prompt by simply typing signtool, but if I run this application from the same prompt, it doesn't register that path.
Adding to mhutch's answer: It does indeed take PATH into consideration, but I have noticed you actually need to restart Visual Studio to pick up any path changes. It is kind of sneaky.
I'm pretty sure Process.Start does respect PATH.
Are you sure your signCommand value is correct?
Is the directory value in PATH specified using quotes? The docs mention that such values will not be respected.
Note that FileName can also be a full path to the executable.
If you updated PATH recently, be sure to restart Visual Studio. Environment variables are loaded at the launch of Visual Studio. Note that this applies to DEBUG mode execution.
Well, I guess the problem was related to what mhutch said, from the MSDN docs:
If you have a path variable declared in your system using quotes,
you must fully qualify that path when starting any process found
in that location. Otherwise, the system will not find the path. For
example, if c:\mypath is not in your path, and you add it using
quotation marks: path = %path%;"c:\mypath", you must fully qualify
any process in c:\mypath when starting it.
I saw that initially, but it seemed strange so I disregard it. Not sure why that's the case but it seems to be.
I tried copying signtool.exe to C:\sign\tool\bin and added that to my path, and then my code worked, so I guess because I have quotes in that path due to the spaces, I am SOL and will have to manually search the path for the windows SDK path unless there is some way to add something with spaces to the path without using quotes.
I believe you're looking for the ProcessStartInfo.WorkingDirectory property.
The StartInfo filename is actually the full path to the executable
For example, on a wrapper I have for x264, it looks like this:
x264Start.FileName = Directory.GetCurrentDirectory() + #"\Tools\x264\x264x86-r1995.exe";
I'd sense-check the code by adding a try {}, catch {} in there and writing the actual filename you are trying to call into the debug if you have an error.
Otherwise add something like the below:
if (File.exists(sign.FileName))
{
sign.Start();
}
else
{
Console.WriteLine("Can't find {0}", sign.FileName);
throw new Exception("File doesn't exist");
}
EDIT: Added a full example - this searches for CCleaner and then runs it with the "/AUTO" switch. Just tested and works fine.
// detect if 64-bit system
string programFiles = "";
if (Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE").Contains("64"))
{
Console.WriteLine("#info# x64 detected");
programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
}
else
{
Console.WriteLine("#info# x86 detected");
programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
}
// search
string[] dirs = Directory.GetDirectories(programFiles, "CCleaner", SearchOption.AllDirectories);
string[] exes = Directory.GetFiles(programFiles, "CCleaner64.exe", SearchOption.AllDirectories);
//debug only
foreach (string s in dirs)
{
Console.WriteLine(s);
}
foreach (string s in exes)
{
Console.WriteLine(s);
}
// access directly
ProcessStartInfo CCleaner = new ProcessStartInfo(exes[0], "/AUTO");
Process.Start(CCleaner);
Your code does seem to take the path into account for me.
It's hard to say what might be wrong in your case, but you might try running your command through cmd.exe:
sign.StartInfo.FileName = "cmd";
sign.StartInfo.Arguments = "/c signtool.exe ...";
Is it possible to open a file with the default program without invoking the command line? I want to run a unit test and have the unit test open the file (PDF) at completion for visual inspection.
Just call Process.Start(filePath).
This will open the file in the user's default program.
I think this should work:
System.Diagnostics.Process.Start(#"c:\file.pdf"); //i.e provide the full path!
Simply use the following syntax:
System.Diagnostics.Process.Start(#"c:\yourfile.txt");
Process process = new System.Diagnostics.Process();
process.EnableRaisingEvents = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.FileName = filePath;
string arguments = fileArguments;
process.StartInfo.Arguments = fileArguments;
process.Start();
process.WaitForExit();
This way you can invoke and put the file name in the pdf with parameters/arguments. You can also specify different programs and put it in the path, then the pdf name in the fileArguments. It's up to you.
if you use this code:
System.Diagnostics.Process.Start( "C:\...\...\myfile.pdf" );
the pdf should get opened by the default program associated to the .pdf extension.
is this what you wanted? I would be careful in putting this inside the unit test in case you include those tests in an automated build on the server, which runs with no logged in user, this could be an issue if it fails and if it does not fail, who is there to close Acrobat Reader? :D
I am trying to execute a OS command through C#. I have the following code taken from this webpage:
//Execute command on file
ProcessStartInfo procStart =
new ProcessStartInfo(#"C:\Users\Me\Desktop\Test\System_Instructions.txt",
"mkdir testDir");
//Redirects output
procStart.RedirectStandardOutput = true;
procStart.UseShellExecute = false;
//No black window
procStart.CreateNoWindow = true;
//Creates a process
System.Diagnostics.Process proc = new System.Diagnostics.Process();
//Set start info
proc.StartInfo = procStart;
//Start
proc.Start();
but when I attempt to run the code I get the following error:
{"The specified executable is not a valid application for this OS platform."}
What am I doing wrong? I have tried this example as well but got the same issue.
The overload of the ProcessStartInfo constructor you are using expects an executable file name and parameters to pass to it - a .txt file is not executable by itself.
It sounds more like you want to execute a batch file with commands within the file. For that check this SO thread: How do I use ProcessStartInfo to run a batch file?
Try setting the USESHELLEXECUTE member to TRUE instead of FALSE.
It worked for me - but I think this has reprocussions for certain users after publishing.
You are trying to execute a TXT file. That's why you get
{"The specified executable is not a valid application for this OS platform."}
Because, well, the specified executable (TXT) is not a valid application for this OS platform.
You would target an executable or other file that has a specified opening application. You're targeting a text file; what you should do is target Notepad, and then supply the path to your text file as an argument:
ProcessStartInfo info = new ProcessStartInfo
{
FileName = "C:\\Windows\System32\\notepad.exe",
Arguments = "C:\\Users\\Me\\Desktop\\Test\\System_Instructions.txt"
}
new Process.Start(info);
Alternatively, if you mean for your text file to be executed, it needs to be made a .bat file.
You are trying to execute this:
C:\Users\Me\Desktop\Test\System_Instructions.txt mkdir testDir
The shell has no clue how to "execute" a text file so the command fails.
If you want to execute this text file as a batch file, change file extension to .bat so the system understands it's a batch file, and then set UseShellExecute so it does the default action for it (= runs it, in case of a batch file).
If you want to open up the file in Notepad, use:
ProcessStartInfo procStart =
new ProcessStartInfo("notepad.exe", #"C:\Users\Me\Desktop\Test\System_Instructions.txt");
If you want to write into the file :
//In case the directory doesn't exist
Directory.CreateDirectory(#"C:\Users\Me\Desktop\Test\);
using (var file = File.CreateText(#"C:\Users\Me\Desktop\Test\System_Instructions.txt"))
{
file.WriteLine("mkdir testDir");
}
If you have commands in the text file that you want to execute, just rename it to .bat and it should work (and presumably the contents do something with "mkdir testDir" as a parameter?)
What are you trying to accomplish?
Create a directory? Use the "System.IO.Directory.CreateDirectory" method.
Open a .txt file with associated program? Use ProcessStartInfo(#".\filename.txt") with UseShellExecute set to true. This will cause the associated program for that file type to be executed, which might not be notepad.txt.