I'd like to set up a Visual C++ toolchain to be used inside my C# application. For the toolchain it's recommended to call vcvarsall (or some subvariant). My problem is that the calling process - my application - will not get to keep the environment set up by vcvarsall. Can this somehow be achieved?
// First set up the toolchain with vcvarsall
var vcvarsallProc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = vcvarsallPath,
Arguments = "x86",
UseShellExecute = false,
}
};
vcvarsallProc.Start();
vcvarsallProc.WaitForExit();
// Invoke the linker for example
var linkerProc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "LINK.exe",
Arguments = " foo.obj /OUT:a.exe",
UseShellExecute = false,
}
};
linkerProc.Start();
linkerProc.WaitForExit();
// ERROR: 'LINK' is not recognized as an internal or external command
Turns out, this is pretty much impossible without a hassle.
The best one can do is chain multiple commands, which will run in the same environment. It can be done like so:
var linkerProc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/C ({vcvarsallPath} {vcvarsallArgs}) && (LINK.exe {linkArgs})",
UseShellExecute = false,
}
};
Hiding this behind a function like InvokeWithEnvironment, and it's pretty painless to use.
Related
I am writing a software update process on Linux. Application is .NET 5 RC1 (Sept 15 2020 release). When a certain packet is received by my application, it downloads the software update to a sub-folder then spawns off the executable to perform the software update.
Unfortunately, using Process.Start and ProcessStartInfo seems to create a process that is attached to the main process. Since the software update must stop the process in order to update it, it also gets stopped because it is a child of the process, having been spawned via Process.Start.
How do I create a detached process on Linux? On Windows I am using PInvoke and the CreateProcess API with the DETACHED_PROCESS flag, see the following:
var processInformation = new ProcessUtility.PROCESS_INFORMATION();
var startupInfo = new ProcessUtility.STARTUPINFO();
var sa = new ProcessUtility.SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
CreateProcess(null, "\"" + fileName + "\" " + arguments, ref sa, ref sa, false, DETACHED_PROCESS, IntPtr.Zero, Path.GetDirectoryName(fileName), ref startupInfo, out processInformation);
Here is my code for Linux. I had read that appending & to a process on Linux creates it detached, but that does not appear to be the case.
ProcessStartInfo info = new ProcessStartInfo
{
// Linux uses " &" to detach the process
Arguments = arguments + " &",
CreateNoWindow = true,
FileName = fileName,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = Path.GetDirectoryName(fileName)
};
Process.Start(info);
I was unable to get nohup or disown to work from C#. Killing the parent process always resulted in the child process being terminated as well.
I ended up using at, which can be installed via sudo apt install at. The atd service is installed and will stay running even when rebooted.
Here is the C# code that I used:
// the following assumes `sudo apt install at` has been run.
string fileName = "[your process to execute]";
string arguments = "[your command line arguments for fileName]";
string argumentsEscaped = arguments.Replace("\"", "\\\"");
string fullArgs = $"-c \"echo sudo \\\"{fileName}\\\" {argumentsEscaped} | at now\"";
ProcessStartInfo info = new()
{
Arguments = fullArgs,
CreateNoWindow = true,
FileName = "/bin/bash",
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = Path.GetDirectoryName(fileName)
};
using var process = Process.Start(info);
process.WaitForExit();
// make sure to check process.ExitCode == 0
For me, setsid in combination with & makes a spawned child process out-living its parent process when invoked via sh -c.
Example:
var command = $"dotnet \"PathToDll\" param1 param2";
process.StartInfo = new ProcessStartInfo
{
Arguments = $"-c \"setsid {command.Replace("\"", "\\\"")} &\"",
CreateNoWindow = true,
FileName = "/bin/sh",
};
process.Start();
This was tested on Debian and Ubuntu.
Slightly refactored version of #jjxtra solution, so it's easier to understand what's going on in the arguments.
Btw, the echo is not an example, but the way of executing at command.
string command = $"actual command to run";
string atdCommand = $#"echo \""{command}\"" | at now";
string bashCommand = $#"-c ""{atdCommand}"" ";
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = bashCommand,
...
};
I try to execute a .rb file from c#. Process never exits.
var result = new Process
{
StartInfo = new ProcessStartInfo()
{
FileName = "ruby \"someRubyFile.rb\"",
UseShellExecute = false,
CreateNoWindow = true
}
};
result.Start();
result.WaitForExit();
Finally I figured it out. If I send .rb filename as an argument, everthing works fine.
I have a test runner app which runs different tester apps.
Depending on the case if the runner app is started via dotnet CLI command (e.g. >dotnet runner.dll -t tester1) or via simple running of the published .exe file (e.g. >runner.exe -t tester1), I want to build different paths to the tester apps executable files.
How it's better to check this?
That's how I'll use it (it's a POC app, I need only that 2 cases here):
public TesterProcess(bool runViaDotnetCli)
{
TesterInfo = new T();
if (runViaDotnetCli)
{
Process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "dotnet",
// TesterInfo.ExecutableFileName is something like "tester1.dll" here
ArgumentList = {TesterInfo.ExecutableFileName, "--data", "something"},
UseShellExecute = false, CreateNoWindow = false
}
};
}
else
{
Process = new Process
{
StartInfo = new ProcessStartInfo
{
// TesterInfo.ExecutableFileName is something like "tester1.exe" here
FileName = TesterInfo.ExecutableFileName,
ArgumentList = {"--data", "something"},
UseShellExecute = false, CreateNoWindow = false
}
};
}
}
One option for this would be to use Process.GetCurrentProcess to the get the current process and then use its ProcessName property:
if (Process.GetCurrentProcess().ProcessName == "dotnet")
{
...
}
I'm trying to start application "GA.exe", but on start it's taking data from file "acc.txt".
If I start it normallly (via double click :-)) it works, but if I use code below it say "Can't find acc.txt".
My first idea:
Process.Start(pathToGA.exe);
Second idea:
ProcessStartInfo pinfo = new ProcessStartInfo()
{
Arguments = FolderWithGA.exePath,
FileName = pathToGA.exe,
};
And both don't work.
You should set ProcessStartInfo.WorkingDirectory to the directory that holds acc.txt and GA.exe:
ProcessStartInfo pinfo = new ProcessStartInfo()
{
Arguments = FolderWithGA.exePath,
FileName = pathToGA.exe,
WorkingDirectory = FolderWithGA
};
I am developing a C# application.
I need to create and pass variables to a new process and I am doing it using ProcessStartInfo.EnvironmentVariables.
The new process must run elevated so I am using Verb = "runas"
var startInfo = new ProcessStartInfo(command)
{
UseShellExecute = true,
CreateNoWindow = true,
Verb = "runas"
};
foreach (DictionaryEntry entry in enviromentVariables)
{
startInfo.EnvironmentVariables.Add(entry.Key.ToString(), entry.Value.ToString());
}
The problem is that according to the msdn documentation:
You must set the UseShellExecute property to false to start the process after changing the EnvironmentVariables property. If UseShellExecute is true, an InvalidOperationException is thrown when the Start method is called.
but the runas variable requires UseShellExecute=true
Is there a way to do both: run process as elevated and also set the environment variables?
EDIT
I will try to rephrase my question...
Is there a way to pass arguments securly to another process so only the other process will be able to read the arguments.
It works but on the downside is that it also shows a second command prompt, the enviroment vars are only set in the context of the started process so the settings are not propagating to the whole box.
static void Main(string[] args)
{
var command = "cmd.exe";
var environmentVariables = new System.Collections.Hashtable();
environmentVariables.Add("some", "value");
environmentVariables.Add("someother", "value");
var filename = Path.GetTempFileName() + ".cmd";
StreamWriter sw = new StreamWriter(filename);
sw.WriteLine("#echo off");
foreach (DictionaryEntry entry in environmentVariables)
{
sw.WriteLine("set {0}={1}", entry.Key, entry.Value);
}
sw.WriteLine("start /w {0}", command);
sw.Close();
var psi = new ProcessStartInfo(filename) {
UseShellExecute = true,
Verb="runas"
};
var ps = Process.Start(psi);
ps.WaitForExit();
File.Delete(filename);
}
There's a better answer: you can still call Process.Start() with a ProcessStartInfo that has UseShellExecute = true, provided that the method you call it from has been labeled with the [STAThread] attribute.