Question

I know how to launch a process with Admin privileges from a process using:

proc.StartInfo.UseShellExecute = true;
proc.StartInfo.Verb = "runas";

where proc is a System.Diagnostics.Process. But how does one do the opposite?

If the process you're in is already elevated, how do you launch the new process without admin privileges? More accurately, we need to launch the new process with the same permission level as Windows Explorer, so no change if UAC is disabled, but if UAC is enabled, but our process is running elevated, we need to perform a certain operation un-elevated because we're creating a virtual drive and if it's created with elevated permissions and Windows explorer is running unelevated it won't show up.

Feel free to change the title to something better, I couldn't come up with a good description.

Was it helpful?

Solution 2

We ended up using the sample from this Code Project article: High elevation can be bad for your application: How to start a non-elevated process at the end of the installation

It seems to work so far, I gather it injects into RunDll32.exe, my C++/Win32 is fairly weak so I didn't look too much into the actual implementation, just it's use. Confirmed that it works in Vista and Win7 both x86 and x64 (at least for us, x86 and x64 require different dll's which is checked for at install time and the proper one is used).

OTHER TIPS

The solution for you is to use EXPLORER.exe process.

The idea is to run the process in UN-ELEVATED mode, using windows's file explorer process explorer.exe (info). Lets say the process that we want to launch is on $TEMP\MyUnElevatedProcess.exe.

So, for NSIS code, I will just write: (but can be run in ANY language)

 Exec '"$WINDIR\explorer.exe" "$TEMP\MyUnElevatedProcess.exe"'

Example code (using NSIS installer)

Exec '"$WINDIR\explorer.exe" "$TEMP\MyUnElevatedProcess.exe"'

***code taken from http://mdb-blog.blogspot.com/2013/01/nsis-lunch-program-as-user-from-uac.html

If you want to start an unelevated process from an elevated one you could copy the access token of the shell process and use it to start a new process.

public static class UnelevatedProcessStarter
{
    public static void Start(string cmdArgs)
    {
        // 1. Get the shell
        var shell = NativeMethods.GetShellWindow();
        if (shell == IntPtr.Zero)
        {
            throw new Exception("Could not find shell window");
        }

        // 2. Copy the access token of the process
        NativeMethods.GetWindowThreadProcessId(shell, out uint shellProcessId);
        var hShellProcess = NativeMethods.OpenProcess(0x00000400 /* QueryInformation */, false, (int)shellProcessId);
        if (!NativeMethods.OpenProcessToken(hShellProcess, 2 /* TOKEN_DUPLICATE */, out IntPtr hShellToken))
        {
            throw new Win32Exception();
        }

        // 3. Dublicate the acess token
        uint tokenAccess = 8 /*TOKEN_QUERY*/ | 1 /*TOKEN_ASSIGN_PRIMARY*/ | 2 /*TOKEN_DUPLICATE*/ | 0x80 /*TOKEN_ADJUST_DEFAULT*/ | 0x100 /*TOKEN_ADJUST_SESSIONID*/;
        var securityAttributes = new SecurityAttributes();

        NativeMethods.DuplicateTokenEx(
            hShellToken,
            tokenAccess,
            ref securityAttributes,
            2 /* SecurityImpersonation */,
            1 /* TokenPrimary */,
            out IntPtr hToken);

        // 4. Create a new process with the copied token
        var si = new Startupinfo();
        si.cb = Marshal.SizeOf(si);

        if (!NativeMethods.CreateProcessWithTokenW(
            hToken,
            0x00000002 /* LogonNetcredentialsOnly */,
            null,
            cmdArgs,
            0x00000010 /* CreateNewConsole */,
            IntPtr.Zero,
            null,
            ref si,
            out ProcessInformation _))
        {
            throw new Win32Exception();
        }
    }

    public class NativeMethods
    {

        [DllImport("user32.dll")]
        public static extern IntPtr GetShellWindow();
        [DllImport("user32.dll", SetLastError = true)]
        public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr OpenProcess(int processAccess, bool bInheritHandle, int processId);
        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool OpenProcessToken(IntPtr processHandle, UInt32 desiredAccess, out IntPtr tokenHandle);
        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess,
            ref SecurityAttributes lpTokenAttributes,
            int impersonationLevel,
            int tokenType,
            out IntPtr phNewToken);
        [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool CreateProcessWithTokenW(
            IntPtr hToken, int dwLogonFlags,
            string lpApplicationName, string lpCommandLine,
            int dwCreationFlags, IntPtr lpEnvironment,
            string lpCurrentDirectory, [In] ref Startupinfo lpStartupInfo, out ProcessInformation lpProcessInformation);
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct ProcessInformation
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public int dwProcessId;
        public int dwThreadId;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct SecurityAttributes
    {
        public int nLength;
        public IntPtr lpSecurityDescriptor;
        public int bInheritHandle;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct Startupinfo
    {
        public Int32 cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public Int32 dwX;
        public Int32 dwY;
        public Int32 dwXSize;
        public Int32 dwYSize;
        public Int32 dwXCountChars;
        public Int32 dwYCountChars;
        public Int32 dwFillAttribute;
        public Int32 dwFlags;
        public Int16 wShowWindow;
        public Int16 cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }
}

You can use ProcessStartInfo.UserName and ProcessStartInfo.Password to specify the account you want your process to run under.

class Program
{
    static void Main(string[] args)
    {
        var psi = new ProcessStartInfo(@"c:\windows\system32\whoami.exe");
        var password = new SecureString();
        password.AppendChar('s');
        password.AppendChar('e');
        password.AppendChar('c');
        password.AppendChar('r');
        password.AppendChar('e');
        password.AppendChar('t');
        psi.Password = password;
        psi.UserName = "username";
        psi.UseShellExecute = false;
        psi.RedirectStandardOutput = true;

        var p = new Process();
        p.StartInfo = psi;
        p.Start();
        p.WaitForExit();

        Console.WriteLine(p.StandardOutput.ReadToEnd());
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top