Elevating privileges doesn't work with UseShellExecute=false - c#

I want to start a child process (indeed the same, console app) with elevated privileges but with hidden window.
I do next:
var info = new ProcessStartInfo(Assembly.GetEntryAssembly().Location)
{
UseShellExecute = true, // !
Verb = "runas",
};
var process = new Process
{
StartInfo = info
};
process.Start();
and this works:
var identity = new WindowsPrincipal(WindowsIdentity.GetCurrent());
identity.IsInRole(WindowsBuiltInRole.Administrator); // returns true
But UseShellExecute = true creates a new window and I also I can't redirect output.
So when I do next:
var info = new ProcessStartInfo(Assembly.GetEntryAssembly().Location)
{
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false, // !
Verb = "runas"
};
var process = new Process
{
EnableRaisingEvents = true,
StartInfo = info
};
DataReceivedEventHandler actionWrite = (sender, e) =>
{
Console.WriteLine(e.Data);
};
process.ErrorDataReceived += actionWrite;
process.OutputDataReceived += actionWrite;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
This doesn't elevate privileges and code above returns false. Why??

ProcessStartInfo.Verb will only have an effect if the process is started by ShellExecuteEx(). Which requires UseShellExecute = true. Redirecting I/O and hiding the window can only work if the process is started by CreateProcess(). Which requires UseShellExecute = false.
Well, that's why it doesn't work. Not sure if forbidding to start a hidden process that bypasses UAC was intentional. Probably. Very probably.
Check this Q+A for the manifest you need to display the UAC elevation prompt.

In my case, it was ok to get the outputs once the elevated child process is done. Here's the solution I came up. It uses a temporary file :
var output = Path.GetTempFileName();
var process = Process.Start(new ProcessStartInfo
{
FileName = "cmd",
Arguments = "/c echo I'm an admin > " + output, // redirect to temp file
Verb = "runas", // UAC prompt
UseShellExecute = true,
});
process.WaitForExit();
string res = File.ReadAllText(output);
// do something with the output
File.Delete(output);

Check this answer.
This seems to provide a workaround. But I recommend to try other methods like Named Pipes when you have access to source code of the child process.

Related

Is standard output of a process flushed if the process crashes?

I'm starting a Python process using the C# Process class. The standard output is redirected as I want to capture it. I'm able to get the standard output properly except when it crashes. We are aware that it will crash. We need to inspect the standard output when it crashes. We are not getting exact stdout once crashed.
When I run that script in the console manually, I can see the exact output at the time of the crash, but when I redirect the stdout using the > operator, the output is not written to the file, if the process crashed. I suspect that the I/O buffers are not getting flushed, if the process crashes. Is this true? Thanks in advance.
var lastLineOfStdOut = null;
var p = new Process
{
StartInfo =
{
FileName = ... ,
Arguments = ... ,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
p.OutputDataReceived += (sender, e) =>
{
if (e.Data == null) return;
lastLineOfStdOut = e.Data;
Console.WriteLine(e.Data);
};
p.Start();
p.BeginOutputReadLine();
p.WaitForExit();
var exitCode = p.ExitCode; // Use this to figure out if it crashed

Calling a ruby script from c#, process not exit

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.

ProcessStartInfo start "cmd.exe" to run "nvm" commands to install node versions pop up a program association error

I am trying to automate the machine setup using net core 2.0 with a console application, and I need to run some nvm commands to configure node versions.
I am trying to run a .bat file with the nvm commands that I need, but I am getting the following error:
This file does not have a program associated with it for performing this action. Please install a program or, if one is already installed, create an association in the Default Programs control panel.
If I execute the .bat file directly from cmd it works ok, but when my console app run it I get this error.
The 'file.bat' commands are:
nvm version
nvm install 6.11.4
nvm use 6.11.4
nvm list
npm --version
My csharp function to run the command:
public static int ExecuteCommand()
{
int exitCode;
ProcessStartInfo processInfo;
Process process;
processInfo = new ProcessStartInfo("cmd.exe", $"/C file.bat")
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true
};
process = Process.Start(processInfo);
process.OutputDataReceived += (s, e) =>
{
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine("cmd >" + e.Data);
Console.ResetColor();
};
process.BeginOutputReadLine();
process.ErrorDataReceived += (s, e) =>
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(e.Data);
Console.ResetColor();
};
process.BeginErrorReadLine();
process.WaitForExit();
exitCode = process.ExitCode;
Console.WriteLine("ExitCode: " + exitCode.ToString(), "ExecuteCommand");
process.Close();
return exitCode;
}
My expectation is to have this working, because after that I will need to run several other commands, like npm install, gulp install, etc.
Any idea of what could be happening?
Based purely on testing, if you change this section:
processInfo = new ProcessStartInfo("cmd.exe", $"/C file.bat")
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true
};
to not use the constructor arguments and instead manually set parameters like:
processInfo = new ProcessStartInfo()
{
FileName = "cmd.exe",
Arguments = $"/C file.bat",
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true
};
should do the trick. Not sure on why, since from github code on ProcessStartInfo the constructor merely receives arguments and stores them on respective properties (FileName and Arguments).

Start process with IO stream redirection

How to start process and run command like this:
mysql -u root --password="some-password" < "some-file.sql"
Is it possible to do with process.Start()?
I need cross-platform solution (we cannot use cmd.exe).
Yes, this is possible through the System.Diagnostics.Process class. You need to set RedirectStandardInput to true, after which you can write the content of a file redirect the standard input of a process, and write the contents of the file to the Process.StandardInput (which is a StreamWriter)
This should get you started:
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "mysql.exe", // assumes mysql.exe is in PATH
Arguments = "-u root --password=\"some-password\"",
RedirectStandardInput = true,
UseShellExecute = false
},
};
process.Start();
process.StandardInput.Write(File.ReadAllText("some-file.sql"));
Update: this is pretty well documented [here](
https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.redirectstandardinput)

Set ProcessStartInfo.EnvironmentVariables when Verb="runas"

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.

Categories