Reputation: 185593
I'm trying to start an elevated process from with a non-elevated process, but I also need to supply the username and password for a user with administrative credentials. I've tried both the "runas"
method for elevation as well as using a manifest, but both yield different errors.
For example, if I do this (without using a manifest that requires elevation):
ProcessStartInfo info = new ProcessStartInfo(path);
info.UseShellExecute = false;
info.UserName = username;
info.Password = securePwd;
info.Domain = "MyDomain";
info.Verb = "runas";
var proc = Process.Start(info);
The process launches without displaying the UAC confirmation dialog and fails upon trying to execute the command that requires administrator permissions (I'm just trying to write a test file to the Program Files directory).
If I add a manifest to the target application that indicates that it requires elevation, then I get a Win32Exception
stating that the operation requires elevation.
The issue seems to be setting UseShellExecute
to false
(as both approaches work fine when this is not the case), but I have to set it to false in order to launch the process under a different user account.
How can I launch an elevated process from a non-elevated process and supply the username and password manually?
BOUNTY EDIT: While the user cannot be required to enter administrator credentials, a UAC nag dialog is perfectly acceptable. I'm not looking to bypass UAC here.
Upvotes: 22
Views: 11363
Reputation: 5272
This is still a challenge in Windows 10 in 2022, but using the ideas put forth in Ian Boyd's answer I was able to get it working. Since no other answers here provide a comprehensive accounting of what it takes... I'll include my code below.
Based off Ian's answer, I understood that it is not possible to BOTH open a process under another user AND elevate that process's privileges... two processes are necessary.
One - to run as another user.
Two - to run with elevated permissions.
The first process is responsible for running the 2nd process as another user. The 2nd process tries to elevate itself - the key being, it does so under the context it's already running... i.e. the user it's being run as from the first process.
First process:
static async Task Main(string[] args)
{
try
{
StartupInfo startupInfo = new StartupInfo();
startupInfo.reserved = null;
startupInfo.flags &= Startf_UseStdHandles;
startupInfo.stdOutput = (IntPtr)StdOutputHandle;
startupInfo.stdError = (IntPtr)StdErrorHandle;
UInt32 exitCode = 123456;
ProcessInformation processInfo = new ProcessInformation();
String command = @"RunStep2.exe";
String user = "usernameGoesHere";
String domain = "domainGoesHere";
String password = "passwordGoesHere";
String currentDirectory = System.IO.Directory.GetCurrentDirectory();
try
{
CreateProcessWithLogonW(
user,
domain,
password,
(UInt32)1,
null,
command,
(UInt32)0,
(UInt32)0,
currentDirectory,
ref startupInfo,
out processInfo);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Console.WriteLine("Running ...");
WaitForSingleObject(processInfo.process, Infinite);
GetExitCodeProcess(processInfo.process, ref exitCode);
Console.WriteLine("Exit code: {0}", exitCode);
CloseHandle(processInfo.process);
CloseHandle(processInfo.thread);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.ToString());
Console.ReadLine();
}
}
public const UInt32 Infinite = 0xffffffff;
public const Int32 Startf_UseStdHandles = 0x00000100;
public const Int32 StdOutputHandle = -11;
public const Int32 StdErrorHandle = -12;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct StartupInfo
{
public int cb;
public String reserved;
public String desktop;
public String title;
public int x;
public int y;
public int xSize;
public int ySize;
public int xCountChars;
public int yCountChars;
public int fillAttribute;
public int flags;
public UInt16 showWindow;
public UInt16 reserved2;
public byte reserved3;
public IntPtr stdInput;
public IntPtr stdOutput;
public IntPtr stdError;
}
public struct ProcessInformation
{
public IntPtr process;
public IntPtr thread;
public int processId;
public int threadId;
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CreateProcessWithLogonW(
String userName,
String domain,
String password,
UInt32 logonFlags,
String applicationName,
String commandLine,
UInt32 creationFlags,
UInt32 environment,
String currentDirectory,
ref StartupInfo startupInfo,
out ProcessInformation processInformation);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool GetExitCodeProcess(IntPtr process, ref UInt32 exitCode);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern UInt32 WaitForSingleObject(IntPtr handle, UInt32 milliseconds);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern IntPtr GetStdHandle(IntPtr handle);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr handle);
And then the 2nd process is:
private static bool IsAdministrator()
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
static void Main(string[] args)
{
if (IsAdministrator() == false)
{
// Restart program and run as admin
// Under the context of the same user this is being run under right now
// But this time, with elevated administrator privileges
var exeName = Process.GetCurrentProcess().MainModule.FileName;
ProcessStartInfo startInfo222 = new ProcessStartInfo(exeName);
startInfo222.Verb = "runas";
Process.Start(startInfo222);
Environment.Exit(123);
return;
}
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.UseShellExecute = false;
startInfo.FileName = "cmd.exe";
startInfo.WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFilesX86);
startInfo.Verb = "runas";
startInfo.Arguments = "/C COMMAND-GOES-HERE";
startInfo.ErrorDialog = true;
Process process = new Process();
process.StartInfo = startInfo;
process.Start();
process.WaitForExit();
Console.WriteLine("process.ExitCode: " + process.ExitCode);
Console.ReadLine();
}
Upvotes: 0
Reputation: 256581
i was surprised there's no way to do this, until i found an on blog entry by Chris Jackson:
Why Can’t I Elevate My Application to Run As Administrator While Using CreateProcessWithLogonW?
You need a bootstrapper. Some process which will let you do the transition to the alternate user, which could be responsible for running the requireAdministrator application. So, you could design something like this:
Why don’t we just create the
ShellExecuteWithLogonW
API? I’ll never say never, and we might at some point. But today, the use cases for this APIs have been use cases where there has been an alternate design which is superior.The most common request is for people writing home-grown software deployment software, where they’d like to encode credentials right into the app and elevate their own process. The real problem here is not the lack of the API, it’s that you have admin credentials encoded in your app for the world to read.
So the solution requires ShellExecute
, it's the only one that knows how to trigger a Consent dialog.
It brings up a good point: What are you doing with a person's password already?
There's no UAC on Server Core because there's no windows to show a consent prompt.
Upvotes: 15
Reputation: 3934
The ProcessStartInfo.Verb="runas"
is for only windows Vista and higher, so you should ask for the system level, and not do the elevation for XP.
I think if you choose ProcessStartInfo.Verb="runas"
, you should not specify user name and password.
If UAC is of, then it suppose to succeed anyway, it shouldn't be a problem.
Upvotes: 0
Reputation: 7541
If you're authoring a Windows Installer (MSI) application, and updating it using MSPs, then Windows Installer has built-in support for exactly your scenario: - check out User Account Control (UAC) Patching.
It works basically like this:
Upvotes: 1
Reputation: 2072
According to the MSDN documentation:
When UseShellExecute is false, you can start only executables by using the Process object.
I noticed your declaration var proc = Process.Start(info);
is not using Process
as the class type.
Also make sure parameter path
is the fully qualified path to the executable. For instance, "c:\\directory\\contains\\process_to_be_started\\executable.exe"
According to the MSDN documentation this is important:
The WorkingDirectory property must be set if UserName and Password are provided. If the property is not set, the default working directory is %SYSTEMROOT%\system32.
I would try below code to run target process with elevated privileges (with admin rights).
ProcessStartInfo info = new ProcessStartInfo(path);
info.UseShellExecute = false;
info.UserName = username;
info.Password = securePwd;
info.Domain = "MyDomain";
info.Verb = "runas";
info.WorkingDirectory = "c:\\directory\\contains\\process_to_be_started"
'var proc = Process.Start(info);
Process proc = Process.Start(info);
Upvotes: 0
Reputation: 23886
You cannot elevate an already running process. Thus, you should refactor your app to be separated into admin & non-admin operations - running the default application with normal privileges and starting another elevated process for each administrative operation.
Let's work with that, assuming you request administrator rights from the outset on the processes that require them. Based upon the context you've provided:
The issue seems to be setting
UseShellExecute
tofalse
(as both approaches work fine when this is not the case), but I have to set it to false in order to launch the process under a different user account.
As you mentioned, exactly as noted in the documentation for UseShellExecute
:
UseShellExecute must be false if the UserName property is not Nothing or an empty string, or an InvalidOperationException will be thrown when the Process.Start(ProcessStartInfo) method is called.
We now know you're executing your program directly instead of through the use of a shell. This is valuable information.
Backpathing through the documentation, the docs for ProcessStartInfo
carry the following security note:
This class contains a link demand at the class level that applies to all members. A SecurityException is thrown when the immediate caller does not have full-trust permission. For details about security demands, see Link Demands.
So, you don't have the right Link Demand. While trying to solve your permissions issue, you inadvertently created another permissions issue.
The upshot is you need to decorate your calling method with the right Security Demand, which should be FullTrust
. You can do this declaratively or imperatively within your code.
Upvotes: 3