I've got a dotnet core 6 project where I need to execute SSIS packages via dtexec.
So far I have the following code:
private void ExecutePackage()
{
var processOutput = string.Empty;
var processErrorOutput = string.Empty;
var command = #"/C dtexec /file ""C:\git\star\tests\Star.Shared.UnitTests\test-artifacts\TestPackage.dtsx""";
var process = new Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = command;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
processOutput = process.StandardOutput.ReadToEndAsync().Result;
processErrorOutput = process.StandardError.ReadToEndAsync().Result;
if (processOutput != string.Empty)
{
_logger.LogInformation("{output}", processOutput);
}
if (processErrorOutput != string.Empty)
_logger.LogError("{errors}", processErrorOutput);
}
When running this via my unit test, it just seems to hang and I'm unsure as to why.
On one of my previous attempts to get this file to run I got the following message:
An error occurred trying to start process 'dtexec /file "C:\git\star\tests\Star.Shared.UnitTests\test-artifacts\Test_Package.dtsx' with working directory 'C:\git\star\tests\Star.Shared.UnitTests\bin\Debug\net6.0'. The system cannot find the file specified.
Which is telling me that the last time I ran this, it was looking in my tests bin folder for the package instead of where the package is stored.
Is there a setting that I'm missing / set wrong?
I'm wishing to run a command from C# to a container set up via docker-compose. In Powershell, I run this command and the file is created:
docker-compose exec database sh -c "mysqldump -u((username)) -p((password)) ((databasename)) > /backups/test.sql"
When I run the following code it seems to ignore the environment variables, even though I have them set. It only creates a file named backup.sql and the SQL outputted to the file indicates that no database was selected. I've verified the env variables are set by outputting the last parameters string to the console.
var exportPath = $"/backups/backup.sql {DateTime.Now}";
using (var runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
runspace.SessionStateProxy.Path.SetLocation(Path.GetFullPath(".."));
using (var pipeline = runspace.CreatePipeline())
{
var cmd = new Command("docker-compose");
cmd.Parameters.Add("exec");
cmd.Parameters.Add("database");
cmd.Parameters.Add($"sh -c \"mysqldump - u{GetEnv("MYSQL_USERNAME")} " +
$"-p{GetEnv("MYSQL_PASSWORD")} {GetEnv("MYSQL_DATABASE")} > {exportPath} \"");
pipeline.Commands.Add(cmd);
pipeline.Invoke();
}
}
GetEnv is just a convenience method for Environment.GetEnvironmentVariable
I'm fairly certain that I am not setting the parameters right, but I don't know where to go from here.
I came across to FluentDocker which seems like a nice way to run docker-compose from c#.
Here is an example from the project page:
using (
var container =
new Builder().UseContainer()
.UseImage("kiasaki/alpine-postgres")
.ExposePort(5432)
.WithEnvironment("POSTGRES_PASSWORD=mysecretpassword")
.WaitForPort("5432/tcp", 30000 /*30s*/)
.Build()
.Start())
{
var config = container.GetConfiguration(true);
Assert.AreEqual(ServiceRunningState.Running, config.State.ToServiceState());
}
Disclaimer: I am not affiliated with the project in any way nor have attempted to use it yet.
I gave up and used CliWrap because it is easier to debug. I couldn't figure out how to set the current directory, but fortunately I could modify the rest of the program to look for stuff in the current directory.
using CliWrap;
using CliWrap.Buffered;
var now = DateTime.Now.ToString().Replace("/", "-").Replace(" ", "-");
var exportPath = $"/backups/backup.sql-{now}";
var cmd = $"exec -T database sh -c \"mysqldump -u{GetEnv("MYSQL_USER")} " +
$"-p{GetEnv("MYSQL_PASSWORD")} {GetEnv("MYSQL_DATABASE")} > {exportPath}\"";
Console.WriteLine(cmd);
var result = await Cli.Wrap("docker-compose")
.WithArguments(cmd)
.ExecuteBufferedAsync();
Console.WriteLine(result.StandardOutput);
Console.WriteLine(result.StandardError);
The library can be found here: https://github.com/Tyrrrz/CliWrap
In a Windows application I need to run another one application that's tetpdflib. That tetpdflib runs in command prompt only. When I drag and drop exe to the command prompt it will execute. Here is my code:
Process tetmlProcess = new Process();
tetmlProcess.StartInfo.CreateNoWindow = true;
tetmlProcess.StartInfo.UseShellExecute = false;
tetmlProcess.StartInfo.RedirectStandardError = true;
tetmlProcess.StartInfo.RedirectStandardInput = true;
tetmlProcess.StartInfo.WorkingDirectory = #"C:\Users\sw_chn\Documents\PDFlib\TET 5.0 32-bit\bin";
tetmlProcess.StartInfo.FileName = #"C:\Users\sw_chn\Documents\PDFlib\TET 5.0 32-bit\bin\tet.exe";
string args1 = #"tet -m wordplus D:\DailyWork\March\JOURNAL-ISSUE_6_3924-3930.pdf";
tetmlProcess.StartInfo.Arguments = args1;
tetmlProcess.Start();
StreamReader news = tetmlProcess.StandardError;
string err = news.ReadToEnd();
Console.WriteLine(err);
Console.ReadLine();
I had following error:
could not open PDF file 'tet' for reading
How to recover from this?
Your Start Arguments contains the Program Name again which leads to this error.
Simply change your code
Process tetmlProcess = new Process();
// ...
tetmlProcess.StartInfo.WorkingDirectory = #"C:\Users\sw_chn\Documents\PDFlib\TET 5.0 32-bit\bin";
tetmlProcess.StartInfo.FileName = #"C:\Users\sw_chn\Documents\PDFlib\TET 5.0 32-bit\bin\tet.exe";
// removing "tet" in Arguments
string args1 = #"-m wordplus D:\DailyWork\March\JOURNAL-ISSUE_6_3924-3930.pdf";
tetmlProcess.StartInfo.Arguments = args1;
tetmlProcess.Start();
// ...
Conclusion
The manual contains example like this
tet --format utf16 --outfile file.utf16 file.pdf
Here is tet mapped as environment variable in the system and stands for the full path of the application.
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);
I am trying to export a database from c# using mysqldump.
When I run it i get this message: Unknown database 'mysqldump' when selecting the database.
I can't find the solution.
public static void mysqlBackup()
{
try
{
//string time = DateTime.Now.ToString("dd-MM-yyyy");
Log.Info("Starting MySQL dump");
Process MySqlDump = new Process();
MySqlDump.StartInfo.FileName = #"mysqldump.exe";
MySqlDump.StartInfo.UseShellExecute = false;
MySqlDump.StartInfo.Arguments =
"mysqldump -uroot -p******** b3 >"+
" C:/Users/Administrator/Documents/temp/backups/backup.sql";
MySqlDump.StartInfo.RedirectStandardInput = false;
MySqlDump.StartInfo.RedirectStandardOutput = false;
MySqlDump.Start();
MySqlDump.WaitForExit();
MySqlDump.Close();
Log.Info("Successfull created");
}
catch (IOException ex)
{
Log.Error("Unable to write the database file" + ex.ToString());
}
}
I tried to remove the mysqldump from the arguments kinda the same problem.
The redirection operator > is not an argument to mysqldump. When you execute it on the command line, it's being interpreted by the command line itself, not by mysqldump. You have two choices here:
Use the --result-file option as others have mentioned
Capture the stdout of the process and do what you like with the output by setting the RedirectStandardOutput property of StartInfo to be true. After this, you can read from the StandardOutput stream of the process.
I think you need to specify the name of the database you want to dump as the first argument. Thanks to nathan it goes after --databases at the end.
MySqlDump.StartInfo.Arguments = "-u root -p *** database_name --result-file [path]\backup.sql";
You don't need to specify mysqldump again in the command either (not that it should make much difference).
The Mysql documentation states there are 3 ways to use the mysqldump command:
shell> mysqldump [options] db_name [tbl_name ...]
shell> mysqldump [options] --databases db_name ...
shell> mysqldump [options] --all-databases
Ensure the command works fine via your command line. If it does that execute that command directly within your code. If that works then start extracting your arguments and replacing them with your own parameters within code.
Basically you want to get as basic as possible and work back up from there.
If the file works on the command line, try this:
using (Process p = new Process())
{
p.StartInfo.FileName = #"mysqldump.exe -u root -p *** --database b3 -r test.sql"; <~~~ note the change here
p.Start();
p.WaitForExit();
}
The file will be dumped to your project folders bin/debug or bin/release folder unless you change that code.
Here is your edited method:
public static void mysqlBackup()
{
try
{
//string time = DateTime.Now.ToString("dd-MM-yyyy");
Log.Info("Starting MySQL dump");
using(Process MySqlDump = new Process()
{
MySqlDump.StartInfo.FileName = #"mysqldump.exe";
MySqlDump.StartInfo.UseShellExecute = false;
MySqlDump.StartInfo.Arguments = "-uroot -p******** b3 --result-file=C:/Users/Administrator/Documents/temp/backups/backup.sql";
MySqlDump.StartInfo.RedirectStandardInput = false;
MySqlDump.StartInfo.RedirectStandardOutput = false; //You can redirect this as mention in other answers
MySqlDump.Start();
MySqlDump.WaitForExit();
MySqlDump.Close();
}
Log.Info("Successfully created");
}
catch (IOException ex)
{
Log.Error("Unable to write the database file" + ex.ToString());
}
}