In an application I need to execute other programs with another user's credentials. Currently I use System.Diagnostics.Process.Start to execute the program:
public static Process Start(
string fileName,
string arguments,
string userName,
SecureString password,
string domain
)
However this function does not load the roaming profile from the net - which is required.
I could use "runas /profile ..." to load the profile and execute the command, but that would ask for a password. There must be an more elegant way...
But where?
My solution (based on leppie's hint):
Process p = new Process();
p.StartInfo.FileName = textFilename.Text;
p.StartInfo.Arguments = textArgument.Text;
p.StartInfo.UserName = textUsername.Text;
p.StartInfo.Domain = textDomain.Text;
p.StartInfo.Password = securePassword.SecureText;
p.StartInfo.LoadUserProfile = true;
p.StartInfo.UseShellExecute = false;
try {
p.Start();
} catch (Win32Exception ex) {
MessageBox.Show("Error:\r\n" + ex.Message);
}
System.Diagnostics.ProcessStartInfo.LoadUserProfile
Related
Were using octopus for deployment, the tentacle is running as "local system account" I would like the tentacle to add credentials for a diffrent account. However I have no luck i doing so.
So far i tried creating a c# program which starts a new process as the other user, and the calls the cmdkey.exe
private static void CallCmdKey(string runAsDomain, string runsAsUser, string runAsPass, string target, string user, string pass)
{
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo.Arguments = $"/generic:{target} /user:{user} /pass:{pass}";
proc.StartInfo.FileName = Environment.GetEnvironmentVariable("WINDIR") + "\\system32\\cmdkey.exe";
Console.Out.WriteLine(proc.StartInfo.Arguments);
proc.StartInfo.Domain = runAsDomain;
proc.StartInfo.UserName = runsAsUser;
proc.StartInfo.LoadUserProfile = true;
SecureString sec = new SecureString();
runAsPass.ToCharArray().ToList().ForEach(sec.AppendChar);
proc.StartInfo.Password = sec;
proc.StartInfo.WorkingDirectory = ".";
proc.StartInfo.UseShellExecute = false;
proc.Start();
proc.WaitForExit();
Console.Out.WriteLine("done");
}
But it fails with access denied.
Then i tried power shell and psexec like this:
$psexec = "C:\temp\psexec.exe"
Invoke-Command -ScriptBlock{&$psexec -accepteula -u $WEB02AP2User -p $GISWEB02AP2Pass cmd /c cmdkey /generic:ffff /user:mufasa /pass:yoyo}
but it fails with
Access is denied.
PsExec could not start cmd:
The remote script failed with exit code 5
For security reasons Im not allowed to change account for the tentacle service
How can i sovle this issue
I Was unable to find a solutions to this issue. Only workaround was to let the octopusservice run as a specific user account
I want to start another program which runs as user from a program running as administrator.
The problem is that the second program needs to use outlook, which is not possible if the program runs as admin. The main program needs to run as admin.
I did already come up with this two solutions:
Process.Start("cmd.exe", #"/C runas.exe /savecred /user:" + Environment.UserDomainName + "\\" + Environment.UserName + " " + "\"SomeProgram.exe" + "\"");
or
Process.Start("explorer.exe", "SomeProgram.exe");
But i have a problem with both solutions.
The first one asks the user for the password (only the first time after windows was restarted).
The second one probalby won`t work in the future, because as far as i found out it is considered as a bug and probably fixed with an future update.
So I would like to know is there any other solution, where the user does not need to enter his password?
This seems to work for me:
Process.Start("cmd.exe", #"/C runas.exe /TrustLevel:0x20000 " + "\"SomeProgram.exe" + "\"");
Process class has StartInfo property that is an instance of ProcessStartInfo class. This class exposes UserName, Domain and Password members to specify the user you want to run the process.
Process myProcess = new Process();
myProcess.StartInfo.FileName = fileName;
myProcess.StartInfo.UserName = userName;
myProcess.StartInfo.Domain = domain;
myProcess.StartInfo.Password = password;
myProcess.Start();
I was having the same issue and was not able to get the current logged user. NB: querying wmi is not a solution as many users may be logged in at that time
so my solution is to do the reverse. Launch my app as current user and if the current user is not admin, I request to run as admin.
if (IsAdministrator())
{
// run whatever you want as elevated user
}
else
{
//launch the same app as admin
ExecuteAsAdmin(PATHH_TO_THE_SAME_APP.EXE);
//execute whatever you want as current user.
}
public static void ExecuteAsAdmin(string fileName)
{
Process proc = new Process();
proc.StartInfo.FileName = fileName;
proc.StartInfo.UseShellExecute = true;
proc.StartInfo.Verb = "runas";
proc.Start();
proc.WaitForExit();
}
public static bool IsAdministrator()
{
var identity = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
I want to allow users to run a command line utility as administrator from within my non-admin program and for my program to get the output. The utility is third-party but is distributed with my programme.
I can redirect the output of a program and I can run a program as administrator but I can't do both at the same time.
The only thing that I can get to work at the moment is using cmd.exe to redirect the output to a file, e.g.:
using System.Windows.Forms;
using System.Diagnostics;
using System.IO;
using System.Reflection;
string appDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string utilityPath = Path.Combine(appDirectory, "tools", "utility.exe");
string tempFile = Path.GetTempFileName();
Process p = new Process();
// hide the command window
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
p.StartInfo.FileName = "cmd.exe";
// run the tool, redirect the output to the temp file and then close.
p.StartInfo.Arguments = " /C \"\"" + utilityPath + "\" > \"" + tempFile + "\"\"";
p.StartInfo.Verb = "runas"; // run as administrator
p.Start();
p.WaitForExit();
// get the output, delete the file and show the output to the user
string output = File.ReadAllText(tempFile);
File.Delete(tempFile);
MessageBox.Show(output);
This has two problems: 1) it uses a temporary file and 2) the UAC is for cmd.exe rather then utility.exe. There must surely be a better way to do this?
Instead of executing through a new cmd, try executing the utility directly. And instead of redirecting to a file, redirect the standard output to read it from your program.
In order to run as admin, you'll need to use the admin username and password (taken from here). You'll need to set your method as unsafe:
unsafe public static void Main(string[] args){
Process p = new Process();
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
// set admin user and password
p.StartInfo.UserName = "adminusername";
char[] chArray = "adminpassword".ToCharArray();
System.Security.SecureString str;
fixed (char* chRef = chArray) {
str = new System.Security.SecureString(chRef, chArray.Length);
}
p.StartInfo.Password = str;
// run and redirect as usual
p.StartInfo.FileName = utilityPath;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
string output = p.StandardOutput.ReadToEnd();
Console.WriteLine(output);
p.WaitForExit();
}
This does the magic, although I haven't tested it.
It's written in C++, but a wrapper API can easily be created to be called from C# by using DllImport.
I'm writing a tool that can be used to manage the virtual host a web server on Ubuntu. As for many of the features I need privileged rights, I look for ways to achieve this.
Currently I realize that with "gksu" or "gksudo". This also works. The problem is that the user is prompted for any activity that requires privileged rights to enter his password.
Is it possible to retrieve this password only once and remember for the rest of the duration of the program? Is there another way to implement this?
public void OnToogledVirtualHost(object o, ToggledArgs args)
{
VirtualHost host = (VirtualHost)store.GetNode(new TreePath(args.Path));
host.Enabled = !host.Enabled;
Process process = new Process();
process.StartInfo.UseShellExecute = false;
if (host.Enabled)
{
process.StartInfo.FileName = "gksu";
process.StartInfo.Arguments = "a2ensite " + System.IO.Path.GetFileName(host.FilePath);
}
else
{
process.StartInfo.FileName = "gksu";
process.StartInfo.Arguments = "a2dissite " + System.IO.Path.GetFileName(host.FilePath);
}
process.Start();
}
AFAIK, it is a security feature of 'su' not to cache the password (more properly the authentication ticket) for more than a few seconds, and thus this is designed not to be bypassed.
You can always gksu an intermediary process and try to make the sub-processes inherit its authorization, but you'll need to secure the IPC (the communication channel between your tool frontend and the intermediary process).
So my advice is to not try to lessen the security of the overall solution, so let the user be asked as many times as needed...
There are several (at least 3) "secure" solutions to this problem:
Use "sudo", that allows for password caching. This is my preferred solution if I can install and configure sudo on the machine. Pros: sudo will cache the password. Cons: it depends on having an external dependency (sudo) correctly configured.
Write different helper executables (for example a "modify configuration and restart apache" program) and when needed ask the user to authenticated using gksu, then launch them. Pros: user gets asked for the password only once for every group of actions. Cons: The user still get asked for the password multiple times AND you have to split the program in multiple pieces.
Write a separate service that runs with root privileges and use polkit/DBUS to authenticate the user and connect to it to require services (like, "restart apache please"). Pros: credential caching and authentication dialog is managed by dekstop/polkit. Cons: more code to write and you need to run a DBUS service.
"secure" is quoted because running code (and especially managed code that depends on a large application such as Mono) as root always has security implications.
Thanks for the useful approaches. Yesterday I arrived at a solution of the problem, which I have enclosed in a static class. First, a distinction is made between normal and privilligierten processes.
Whenever a process needs to be run with elevated privileges, I check if I know the user's password already. If not, I'll get it (gksudo -p) and store it in memory.
Now I can execute commands with privilligierten rights. The stored password is then transferred via the standard input (sudo -S).
What do you think? Do you have any safety concerns?
public static class SystemProcess
{
private static string output;
private static string error;
private static string password;
public static void Start (string filename, string arguments)
{
ProcessStartInfo startInfo = SystemProcess.Prepare(filename, arguments);
using (Process process = Process.Start(startInfo)) {
SystemProcess.output = process.StandardOutput.ReadToEnd();
SystemProcess.error = process.StandardError.ReadToEnd();
process.WaitForExit();
}
}
public static void StartPrivileged (string filename, string arguments)
{
ProcessStartInfo startInfo;
if (SystemProcess.password == default(string))
{
startInfo = SystemProcess.Prepare("gksudo", "-p true -D 'MyApplication'");
using (Process process = Process.Start(startInfo)) {
SystemProcess.password = process.StandardOutput.ReadToEnd();
process.WaitForExit();
}
}
startInfo = SystemProcess.Prepare("sudo", "-S " + filename + " " + arguments);
using (Process process = Process.Start(startInfo)) {
process.StandardInput.WriteLine(SystemProcess.password);
SystemProcess.output = process.StandardOutput.ReadToEnd();
SystemProcess.error = process.StandardError.ReadToEnd();
process.WaitForExit();
}
}
private static ProcessStartInfo Prepare (string filename, string arguments)
{
ProcessStartInfo startInfo = new ProcessStartInfo (filename, arguments);
startInfo.RedirectStandardError = true;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardInput = true;
startInfo.UseShellExecute = false;
return startInfo;
}
public static string Output {
get {
return SystemProcess.output;
}
}
public static string Error {
get {
return SystemProcess.error;
}
}
}
You know, I'd not use gksu or sudo for any of this, sounds like you want to look at userv instead.
You can basically permit different users to run different programs,
I have a Windows Service which goes and update some files on the Network which is mapped as drive "Z:\". When i run the code from VS as administrator I am able to copy the file on the mapped drive but same thing fails when it is run from the Service which is running under administrator account.
The Mapped drive is created from same account under which service is running. Bit puzzling why its working when run from the VS but not from service.
Is it better to use UNC than network drive.
There is a workaround at below forum
http://support.microsoft.com/default.aspx?scid=kb;en-us;827421#appliesto
but it has used UNC not mapped drive.
We experienced this as well, and while I can't tell you WHY our resolution worked, i can tell you WHAT worked.
Map the drive in the code. Don't rely on the drive being mapped just because you're using the same account.
Based on the behavior we saw, this is what i would GUESS was happening in our situation and what's happening in yours.
The service we had issues with used a drive that was mapped in a login script. If we had the machine logged in as the same user the service was using, it worked, but if not it wouldn't work. Based on that, I surmised that the drive simply isn't mapped because the service doesn't really "log on".
Mapping the drive in code fixed it.
As a side note, you can also reference the UNC path directly, but we had permissions issues with that as well. Mapping the drive, passing in a username and password worked much better for us.
Our code for doing this:
public static class NetworkDrives
{
public static bool MapDrive(string DriveLetter, string Path, string Username, string Password)
{
bool ReturnValue = false;
if(System.IO.Directory.Exists(DriveLetter + ":\\"))
{
DisconnectDrive(DriveLetter);
}
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "net.exe";
p.StartInfo.Arguments = " use " + DriveLetter + ": " + '"' + Path + '"' + " " + Password + " /user:" + Username;
p.Start();
p.WaitForExit();
string ErrorMessage = p.StandardError.ReadToEnd();
string OuputMessage = p.StandardOutput.ReadToEnd();
if (ErrorMessage.Length > 0)
{
throw new Exception("Error:" + ErrorMessage);
}
else
{
ReturnValue = true;
}
return ReturnValue;
}
public static bool DisconnectDrive(string DriveLetter)
{
bool ReturnValue = false;
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "net.exe";
p.StartInfo.Arguments = " use " + DriveLetter + ": /DELETE";
p.Start();
p.WaitForExit();
string ErrorMessage = p.StandardError.ReadToEnd();
string OuputMessage = p.StandardOutput.ReadToEnd();
if (ErrorMessage.Length > 0)
{
throw new Exception("Error:" + ErrorMessage);
}
else
{
ReturnValue = true;
}
return ReturnValue;
}
}
Using the above class, you can map and disconnect the drive at will. If this is a service, I would recommend mapping the drive just before you need it and disconnecting the drive immediately after you need it.
I would suggest UNC is a better idea.
What if the mapped drive is mapped on login? A service may run without a user even logging in so the mapping might never be created.
I struggled a lot (~3 days) to solve the same issue. Seems it is more related to NTFS and file level permissions. It is better if you use shared location rather than a drive.