Execute multiple commands in same environment from C# - 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);

Related

Using Command Line from C# to copy container using AzCopy

I'm trying to copy containers in Azure from one storage location to another. I'm using the AzCopy command for this. First I get a list of all the containers and then run AzCopy based on the container name from the command line, using c# code.
The problem that I was running into is that it does copy the containers from one location to another but after 4 containers, it seems to get stuck. And the test keeps running forever. When I cancel the test I see all the other containers get copied as well.
I was wondering how can I solve this issue of having the test be complete and all the folders get copied over. I tried to wait after each call to make sure there is enough time for the call to complete. Also tried using cmd.WaitForExit(); after each call but that just gets stuck.
Any suggestions on what I could be missing, one thing I wanted to do was get the output after each call, because right now it only outputs the result once all the commands are finished. Also was thinking of how to run the command lines call sequentially so run only after the first has finished.
Any help would be appreciated!
namespace Test2
{
[TestFixture]
class ContainerList
{
[Test]
public void CopyingContainerData()
{
CloudStorageAccount sourceCloudStorageAccount =
CloudStorageAccount.Parse("StorageAccountKey");
CloudBlobClient sourceCloudBlobClient = sourceCloudStorageAccount.CreateCloudBlobClient();
List<string> outputLines = new List<string>();
IEnumerable<CloudBlobContainer> containers = sourceCloudBlobClient.ListContainers();
Process cmd = new Process();
cmd.StartInfo.FileName = "cmd.exe";
cmd.StartInfo.RedirectStandardInput = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.CreateNoWindow = false;
cmd.StartInfo.UseShellExecute = false;
cmd.Start();
int i = 0;
foreach (CloudBlobContainer oneContainer in containers)
{
string outputLine = oneContainer.Name;
outputLines.Add(outputLine);
string container = oneContainer.Name;
string strCmdText = #"AzCopy /Source:https://location1.blob.core.windows.net/" + container + #" /Dest:https://location2.blob.core.windows.net/" + container + #" /SourceKey:abc /DestKey:abc123 /S /NC:8 /XO /Y";
string location = #"cd C:\Program Files (x86)\Microsoft SDKs\Azure\AzCopy";
cmd.StandardInput.WriteLine(location);
cmd.StandardInput.WriteLine(strCmdText);
//System.Threading.Thread.Sleep(20000);
//cmd.WaitForExit();
i++;
if (i == 15)
{
break;
}
}
string[] outputText = outputLines.ToArray();
File.WriteAllLines(#"C:\AzureTests\CopyData.txt", outputText);
cmd.StandardInput.Flush();
cmd.StandardInput.Close();
Console.WriteLine(cmd.StandardOutput.ReadToEnd());
}
}
}
I suggest you use powershell to do this:
$SourceStorageAccount = "sourceStorageAccount"
$SourceStorageKey = "sourceKey"
$DestStorageAccount = "destStorageAccount"
$DestStorageKey = "destKey"
$SourceStorageContext = New-AzureStorageContext –StorageAccountName $SourceStorageAccount -StorageAccountKey $SourceStorageKey
$DestStorageContext = New-AzureStorageContext –StorageAccountName $DestStorageAccount -StorageAccountKey $DestStorageKey
$containers = Get-AzureStorageContainer -Context $SourceStorageContext
foreach($container in $containers) {
New-AzureStorageContainer -Context $DestStorageContext -Name $container.name -Permission Off
$Blobs = Get-AzureStorageBlob -Context $SourceStorageContext -Container $container.name
#Do the copy of everything
foreach ($Blob in $Blobs) {
Write-Output "Moving $Blob.Name"
Start-CopyAzureStorageBlob -Context $SourceStorageContext -SrcContainer $container.name -SrcBlob $Blob.Name `
-DestContext $DestStorageContext -DestContainer $container.name -DestBlob $Blob.Name
}
}
We had similar scenario of AzCopy.exe invocation not returning control to .Net. The reason was parallel execution of AzCopy.exe without specifying the journal files. It share the journal files by default and gets access violation. When we issued different journal files to different instances of AzCopy it stared working.
Running multiple instances of AzCopy.exe from .Net

Visual studio 2013: Difference in annotations between GUI and TFS power tools command line

I'm trying to implement a "blame" mechanism for my team's project. To do this, I've created a program which, through power tools, gets the specific changeset for each line of code in a file:
private readonly Regex changeSetRegex = new Regex(#"^(?<changeset>\d+)(?<codeLine>.*)", RegexOptions.Compiled | RegexOptions.Multiline);
public Changeset GetAnnotations(string filepath, int lineNumber)
{
var versionControlServer = CreateVersionControlServer();
var line = changeSetRegex.Matches(ExecutePowerTools(filepath))[lineNumber];
return versionControlServer.GetChangeset(int.Parse(line.Groups["changeset"].Value), false, false);
}
private static VersionControlServer CreateVersionControlServer()
{
var projectCollection = new TfsTeamProjectCollection(new Uri(myTfsUri));
var versionControlServer = projectCollection.GetService<VersionControlServer>();
return versionControlServer;
}
private static string ExecutePowerTools(string filepath)
{
string TfptLocation = #"..\..\Dependencies\Microsoft Team Foundation Server 2013 Power Tools\TFPT.EXE";
string bla = string.Format(#"annotate /noprompt {0}", filepath);
var startInfo = new ProcessStartInfo();
startInfo.RedirectStandardInput = false;
startInfo.RedirectStandardOutput = true;
startInfo.UseShellExecute = false;
startInfo.FileName = TfptLocation;
startInfo.Arguments = bla;
using (var process = Process.Start(startInfo))
{
StringBuilder sb = new StringBuilder();
while (!process.HasExited)
{
String s = process.StandardOutput.ReadToEnd();
sb.Append(s);
}
return sb.ToString();
}
}
Now, something very strange happens - In some files, when I view the annotations using the GUI I get different changesets for the same line and, accordingly, different blames, from the ones I get using the above code. I can reproduce this difference when I use the "tfpt annotate" command (which is essentially what the code does).
It might be worth mentioning that the changes in this case are minor - changing from spaces to tabs. Perhaps there is a way for VS to recognize this and not display it in the GUI, but this is purely speculative
I couldn't reproduce your issue. Please make sure you are checking the same version when you use GUI and command line. Try to use command looks like:
tfpt annotate myFile.cs;C123 /noprompt

How to redirect STDIN .NET Process before Starting the process

Im trying to make C# application that uses hunpos tagger.
Runing hunpos-tag.exe requires three input arguments: model, inputFile, outputFile
In cmd it would look something like this:
hunpos-tag.exe model <inputFile >outputFile
If I just run it with model it writes something and waits for end command. When i tried using standard redirect I either get an exception (i solved this the code was off by a braceket i just get the or scenario now) or I get the results of running the tagger with just model argument. Here's the code:
string inputFilePath = path + "\\CopyFolder\\rr";
string pathToExe = path + "\\CopyFolder\\hunpos-tag.exe";
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = pathToExe,
UseShellExecute = false,
RedirectStandardInput = true,
WorkingDirectory = Directory.GetDirectoryRoot(pathToExe),
Arguments = path + "\\CopyFolder\\model.hunpos.mte5.defnpout",
};
try
{
Process _proc = new Process();
_proc.StartInfo.FileName = pathToExe;
_proc.StartInfo.UseShellExecute = false;
_proc.StartInfo.RedirectStandardInput = true;
_proc.StartInfo.Arguments = path + "\\CopyFolder\\model.hunpos.mte5.defnpout";
//Magic goes here
_proc.Start();
_proc.WaitForExit();
}
catch (Exception e)
{
Console.WriteLine(e);
}
Any ideas how can I redirect input before starting my process?
It is not only required to set RedirectStandardInput to true but also you need to use the input stream to write the text you want:
_proc.StandardInput.WriteLine("The text you want to write");
There's no need for that ProcessStartInfo if you're setting the info later on. Just get rid of that. And it seems you are already doing what you want. Just creating the process object doesn't start the process, Process.Start does. Just make a new StreamWriter and pass it Process.StandardInput (I think that's right, it may be something else)

.NET Process unpredicably hangs. Need opinions on alternate solutions

I am trying to reach one simple requirement. I would like to create a C# library that talks to the git executable. I am writing a version control tool for my team that will allow access to git commands for non tech-savvy individuals. Unfortunately, I can not use any third party DLL's (I am using Unity and I do not want to push the requirement for Unity pro due to plugins), otherwise I would use GitSharp or something along those lines.
Currently, I have a function called RunGitCommand that is meant to do all my bidding. This snippet is as follows:
private void RunGitCommand(string executablePath, string arguments, int maxCommandDurationMilliseconds)
{
using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
{
try
{
CommandOutput = string.Empty;
CommandError = string.Empty;
ProcessStartInfo processStartInfo = new ProcessStartInfo();
processStartInfo.CreateNoWindow = true;
processStartInfo.FileName = executablePath;
processStartInfo.Arguments = arguments;
processStartInfo.UseShellExecute = false;
processStartInfo.RedirectStandardError = true;
processStartInfo.RedirectStandardOutput = true;
int processedTime = 0;
using (Process process = new Process())
{
StringBuilder outputData = new StringBuilder();
StringBuilder errorData = new StringBuilder();
process.StartInfo = processStartInfo;
process.OutputDataReceived += (sender, e) => {
outputWaitHandle.Set();
if (e.Data == null)
{
//outputWaitHandle.Set();
}
else
{
outputData.AppendLine(e.Data);
}
};
process.ErrorDataReceived += (sender, e) =>
{
errorWaitHandle.Set();
if (e.Data == null)
{
//errorWaitHandle.Set();
}
else
{
errorData.AppendLine(e.Data);
}
};
process.Start();
if(process.Id == 0)
{
Environment.LogError("Process id is 0. Aborting.");
return;
}
RunningProcessDescriptor processDescriptor = new RunningProcessDescriptor(maxCommandDurationMilliseconds, process.Id);
ProcessIds.Add(processDescriptor);
Thread.Sleep(200);
int newMaxTime = maxCommandDurationMilliseconds - 100;
process.BeginOutputReadLine();
process.BeginErrorReadLine();
if (process.WaitForExit(newMaxTime) && outputWaitHandle.WaitOne(newMaxTime) && errorWaitHandle.WaitOne(newMaxTime))
{
process.CancelOutputRead();
process.CancelErrorRead();
CommandOutput = outputData.ToString();
CommandError = errorData.ToString();
string combinedOutput = string.Join(System.Environment.NewLine, new string[]{ CommandError, CommandOutput }).Trim();
BatchOutput = string.Join(System.Environment.NewLine, new string[]{ BatchOutput, string.Format("----------// {0} {1} //----------", executablePath, arguments), CommandOutput }).Trim();
BatchError = string.Join(System.Environment.NewLine, new string[]{ BatchError, string.Format("----------// {0} {1} //----------", executablePath, arguments), CommandError }).Trim();
InterpretErrorsAndAddToLists(combinedOutput);
}
else
{
process.Close();
process.WaitForExit();
}
ProcessIds.Remove(processDescriptor);
}
}
catch(Exception genericException)
{
Environment.LogError(genericException.Message);
Environment.LogError(genericException.StackTrace);
}
}
}
Another thing to note is that I'm using this to run these commands so that they don't execute on the main thread:
ThreadPool.QueueUserWorkItem
As you should be able to identify by my commenting and layout, this function is the result of hours of troubleshooting and I am willing to try anything at this point to make it work. I've already moved to using asynchronous calls for receiving the output/error streams, and even added AutoResetEvent objects to cause my threads to wait for each other (although I am not completely familiar with them and might be doing something wrong).
No matter what I try, it seems to randomly hang and not allow the process to exit. When I manually kill the git process, the output is spit out (and is usually right) and the process exits and everything works as normal.
I'm at the point in troubleshooting and frustration where I need professional input. Here are my questions:
Is there something blatantly or obviously wrong with my code below? If I'm doing it wrong, please advise me how to properly execute this code.
Is there another solution where I do not need to include third party DLL files and can just use raw .NET to grab the git console process and interact with it (on mac and pc)?
Other alternatives to these approaches, such as one i've been considering, that uses a "client/server" architecture. I can use third party dll's and whatnot in a separate downloadable program that communicates to the git plugin via TCP to send and receive output/input to the git process. This one is much more work, but would potentially take less time than troubleshooting the git only version.
Just let me know what your professional opinions are so that I can rest at night :).
I look forward to talking with everyone!
-Zack
I tend to use a very simple piece of code for executing a non-interactive command, and getting the standard output from the result. I would suggest starting from something like this, and checking it doesn't hang. Then build any additional logic from there.
private static string ExecuteCommand(string command, string arguments)
{
command = System.Environment.ExpandEnvironmentVariables(command);
var process = new Process
{
StartInfo =
{
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
FileName = command,
Arguments = arguments
}
};
process.Start();
process.WaitForExit();
return process.StandardOutput.ReadToEnd();
}
It's often hard to isolate the problem when there's so much 'dead wood' in the code. Strip it back to the bare bones, and see if you can isolate the problem from there.

Run Command Prompt Commands

Is there any way to run command prompt commands from within a C# application? If so how would I do the following:
copy /b Image1.jpg + Archive.rar Image2.jpg
This basically embeds an RAR file within JPG image. I was just wondering if there was a way to do this automatically in C#.
this is all you have to do run shell commands from C#
string strCmdText;
strCmdText= "/C copy /b Image1.jpg + Archive.rar Image2.jpg";
System.Diagnostics.Process.Start("CMD.exe",strCmdText);
EDIT:
This is to hide the cmd window.
System.Diagnostics.Process process = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
startInfo.FileName = "cmd.exe";
startInfo.Arguments = "/C copy /b Image1.jpg + Archive.rar Image2.jpg";
process.StartInfo = startInfo;
process.Start();
EDIT 2:
It is important that the argument begins with /C, otherwise it won't work. As #scott-ferguson said: /C carries out the command specified by the string and then terminates.
Tried RameshVel's solution but I could not pass arguments in my console application. If anyone experiences the same problem here is a solution:
using System.Diagnostics;
Process cmd = new Process();
cmd.StartInfo.FileName = "cmd.exe";
cmd.StartInfo.RedirectStandardInput = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.CreateNoWindow = true;
cmd.StartInfo.UseShellExecute = false;
cmd.Start();
cmd.StandardInput.WriteLine("echo Oscar");
cmd.StandardInput.Flush();
cmd.StandardInput.Close();
cmd.WaitForExit();
Console.WriteLine(cmd.StandardOutput.ReadToEnd());
var proc1 = new ProcessStartInfo();
string anyCommand;
proc1.UseShellExecute = true;
proc1.WorkingDirectory = #"C:\Windows\System32";
proc1.FileName = #"C:\Windows\System32\cmd.exe";
proc1.Verb = "runas";
proc1.Arguments = "/c "+anyCommand;
proc1.WindowStyle = ProcessWindowStyle.Hidden;
Process.Start(proc1);
None of the above answers helped for some reason, it seems like they sweep errors under the rug and make troubleshooting one's command difficult. So I ended up going with something like this, maybe it will help someone else:
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = #"C:\Program Files\Microsoft Visual Studio 14.0\Common7\IDE\tf.exe",
Arguments = "checkout AndroidManifest.xml",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true,
WorkingDirectory = #"C:\MyAndroidApp\"
}
};
proc.Start();
Though technically this doesn't directly answer question posed, it does answer the question of how to do what the original poster wanted to do: combine files. If anything, this is a post to help newbies understand what Instance Hunter and Konstantin are talking about.
This is the method I use to combine files (in this case a jpg and a zip). Note that I create a buffer that gets filled with the content of the zip file (in small chunks rather than in one big read operation), and then the buffer gets written to the back of the jpg file until the end of the zip file is reached:
private void CombineFiles(string jpgFileName, string zipFileName)
{
using (Stream original = new FileStream(jpgFileName, FileMode.Append))
{
using (Stream extra = new FileStream(zipFileName, FileMode.Open, FileAccess.Read))
{
var buffer = new byte[32 * 1024];
int blockSize;
while ((blockSize = extra.Read(buffer, 0, buffer.Length)) > 0)
{
original.Write(buffer, 0, blockSize);
}
}
}
}
if you want to run the command in async mode - and print the results. you can you this class:
public static class ExecuteCmd
{
/// <summary>
/// Executes a shell command synchronously.
/// </summary>
/// <param name="command">string command</param>
/// <returns>string, as output of the command.</returns>
public static void ExecuteCommandSync(object command)
{
try
{
// create the ProcessStartInfo using "cmd" as the program to be run, and "/c " as the parameters.
// Incidentally, /c tells cmd that we want it to execute the command that follows, and then exit.
System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command);
// The following commands are needed to redirect the standard output.
//This means that it will be redirected to the Process.StandardOutput StreamReader.
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();
// Get the output into a string
string result = proc.StandardOutput.ReadToEnd();
// Display the command output.
Console.WriteLine(result);
}
catch (Exception objException)
{
// Log the exception
Console.WriteLine("ExecuteCommandSync failed" + objException.Message);
}
}
/// <summary>
/// Execute the command Asynchronously.
/// </summary>
/// <param name="command">string command.</param>
public static void ExecuteCommandAsync(string command)
{
try
{
//Asynchronously start the Thread to process the Execute command request.
Thread objThread = new Thread(new ParameterizedThreadStart(ExecuteCommandSync));
//Make the thread as background thread.
objThread.IsBackground = true;
//Set the Priority of the thread.
objThread.Priority = ThreadPriority.AboveNormal;
//Start the thread.
objThread.Start(command);
}
catch (ThreadStartException )
{
// Log the exception
}
catch (ThreadAbortException )
{
// Log the exception
}
catch (Exception )
{
// Log the exception
}
}
}
if you want to keep the cmd window open or want to use it in winform/wpf then use it like this
string strCmdText;
//For Testing
strCmdText= "/K ipconfig";
System.Diagnostics.Process.Start("CMD.exe",strCmdText);
/K
Will keep the cmd window open
Yes, there is (see link in Matt Hamilton's comment), but it would be easier and better to use .NET's IO classes. You can use File.ReadAllBytes to read the files and then File.WriteAllBytes to write the "embedded" version.
with a reference to Microsoft.VisualBasic
Interaction.Shell("copy /b Image1.jpg + Archive.rar Image2.jpg", AppWinStyle.Hide);
This can also be done by P/Invoking the C standard library's system function.
using System.Runtime.InteropServices;
[DllImport("msvcrt.dll")]
public static extern int system(string format);
system("copy Test.txt Test2.txt");
Output:
1 file(s) copied.
Here is little simple and less code version. It will hide the console window too-
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = "/C copy /b Image1.jpg + Archive.rar Image2.jpg";
process.Start();
I have the following method, which I use to run the command prompt commands from C#
In first parameter pass the command you want to run
public static string RunCommand(string arguments, bool readOutput)
{
var output = string.Empty;
try
{
var startInfo = new ProcessStartInfo
{
Verb = "runas",
FileName = "cmd.exe",
Arguments = "/C "+arguments,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = false
};
var proc = Process.Start(startInfo);
if (readOutput)
{
output = proc.StandardOutput.ReadToEnd();
}
proc.WaitForExit(60000);
return output;
}
catch (Exception)
{
return output;
}
}
You can achieve this by using the following method (as mentioned in other answers):
strCmdText = "'/C some command";
Process.Start("CMD.exe", strCmdText);
When I tried the methods listed above I found that my custom command did not work using the syntax of some of the answers above.
I found out more complex commands need to be encapsulated in quotes to work:
string strCmdText;
strCmdText = "'/C cd " + path + " && composer update && composer install -o'";
Process.Start("CMD.exe", strCmdText);
you can use simply write the code in a .bat format extension ,the code of the batch file :
c:/ copy /b Image1.jpg + Archive.rar Image2.jpg
use this c# code :
Process.Start("file_name.bat")
You can use RunProcessAsTask pacakge and run your process async and easily like this:
var processResults = await ProcessEx.RunAsync("git.exe", "pull");
//get process result
foreach (var output in processResults.StandardOutput)
{
Console.WriteLine("Output line: " + output);
}
This may be a bit of a read so im sorry in advance. And this is my tried and tested way of doing this, there may be a simpler way but this is from me throwing code at a wall and seeing what stuck
If it can be done with a batch file then the maybe over complicated work around is have c# write a .bat file and run it. If you want user input you could place the input into a variable and have c# write it into the file. it will take trial and error with this way because its like controlling a puppet with another puppet.
here is an example, In this case the function is for a push button in windows forum app that clears the print queue.
using System.IO;
using System;
public static void ClearPrintQueue()
{
//this is the path the document or in our case batch file will be placed
string docPath =
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
//this is the path process.start usues
string path1 = docPath + "\\Test.bat";
// these are the batch commands
// remember its "", the comma separates the lines
string[] lines =
{
"#echo off",
"net stop spooler",
"del %systemroot%\\System32\\spool\\Printers\\* /Q",
"net start spooler",
//this deletes the file
"del \"%~f0\"" //do not put a comma on the last line
};
//this writes the string to the file
using (StreamWriter outputFile = new StreamWriter(Path.Combine(docPath, "test.bat")))
{
//This writes the file line by line
foreach (string line in lines)
outputFile.WriteLine(line);
}
System.Diagnostics.Process.Start(path1);
}
IF you want user input then you could try something like this.
This is for setting the computer IP as static but asking the user what the IP, gateway, and dns server is.
you will need this for it to work
public static void SetIPStatic()
{
//These open pop up boxes which ask for user input
string STATIC = Microsoft.VisualBasic.Interaction.InputBox("Whats the static IP?", "", "", 100, 100);
string SUBNET = Microsoft.VisualBasic.Interaction.InputBox("Whats the Subnet?(Press enter for default)", "255.255.255.0", "", 100, 100);
string DEFAULTGATEWAY = Microsoft.VisualBasic.Interaction.InputBox("Whats the Default gateway?", "", "", 100, 100);
string DNS = Microsoft.VisualBasic.Interaction.InputBox("Whats the DNS server IP?(Input required, 8.8.4.4 has already been set as secondary)", "", "", 100, 100);
//this is the path the document or in our case batch file will be placed
string docPath =
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
//this is the path process.start usues
string path1 = docPath + "\\Test.bat";
// these are the batch commands
// remember its "", the comma separates the lines
string[] lines =
{
"SETLOCAL EnableDelayedExpansion",
"SET adapterName=",
"FOR /F \"tokens=* delims=:\" %%a IN ('IPCONFIG ^| FIND /I \"ETHERNET ADAPTER\"') DO (",
"SET adapterName=%%a",
"REM Removes \"Ethernet adapter\" from the front of the adapter name",
"SET adapterName=!adapterName:~17!",
"REM Removes the colon from the end of the adapter name",
"SET adapterName=!adapterName:~0,-1!",
//the variables that were set before are used here
"netsh interface ipv4 set address name=\"!adapterName!\" static " + STATIC + " " + STATIC + " " + DEFAULTGATEWAY,
"netsh interface ipv4 set dns name=\"!adapterName!\" static " + DNS + " primary",
"netsh interface ipv4 add dns name=\"!adapterName!\" 8.8.4.4 index=2",
")",
"ipconfig /flushdns",
"ipconfig /registerdns",
":EOF",
"DEL \"%~f0\"",
""
};
//this writes the string to the file
using (StreamWriter outputFile = new StreamWriter(Path.Combine(docPath, "test.bat")))
{
//This writes the file line by line
foreach (string line in lines)
outputFile.WriteLine(line);
}
System.Diagnostics.Process.Start(path1);
}
Like I said. It may be a little overcomplicated but it never fails unless I write the batch commands wrong.

Categories

Resources