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();
}