Decrypt my World

Cryptography, Security, Debugging and more!

How to call CreateProcessWithLogonW & CreateProcessAsUser in .NET

How to call CreateProcessWithLogonW & CreateProcessAsUser in .NET

  • Comments 10

Hi, welcome back,

Sometimes .NET's System.Diagnostics.Process class and its Start method are not enough for our purposes and we need to call Win32 API directly from .NET (P/Invoke mechanism) to be able to create a process the way we need. Here you have a sample you might find useful. It includes P/Invoke declarations and usage samples of some very famous API: LogonUser, CreateProcessAsUser and CreateProcessWithLogonW.

 

<SAMPLE>

using System;
using System.Collections.Generic;
using System.Text;

namespace CreateProcessSample
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("Test with CreateProcessWithLogonW...");
                Win32.LaunchCommand1("\\\\server\\tests\\Batch.cmd", "domain", "user", "password");
                Console.WriteLine("Test with CreateProcessAsUser...");
                Win32.LaunchCommand2("\\\\server\\tests\\Batch.cmd", "domain", "user", "password");
            }
            catch (Exception ex)
            {
                Console.WriteLine("LaunchCommand error: " + ex.Message);
            }
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }
    }
}

//--------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.IO;

namespace CreateProcessSample
{
    class Win32
    {
        #region "CONTS"

        const UInt32 INFINITE = 0xFFFFFFFF;
        const UInt32 WAIT_FAILED = 0xFFFFFFFF;

        #endregion

        #region "ENUMS"

        [Flags]
        public enum LogonType
        {
            LOGON32_LOGON_INTERACTIVE = 2,
            LOGON32_LOGON_NETWORK = 3,
            LOGON32_LOGON_BATCH = 4,
            LOGON32_LOGON_SERVICE = 5,
            LOGON32_LOGON_UNLOCK = 7,
            LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
            LOGON32_LOGON_NEW_CREDENTIALS = 9
        }

        [Flags]
        public enum LogonProvider
        {
            LOGON32_PROVIDER_DEFAULT = 0,
            LOGON32_PROVIDER_WINNT35,
            LOGON32_PROVIDER_WINNT40,
            LOGON32_PROVIDER_WINNT50
        }

        #endregion

        #region "STRUCTS"

        [StructLayout(LayoutKind.Sequential)]
        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;
        }

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

        #endregion

        #region "FUNCTIONS (P/INVOKE)"

        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern Boolean LogonUser 
        (
            String lpszUserName,
            String lpszDomain,
            String lpszPassword,
            LogonType dwLogonType,
            LogonProvider dwLogonProvider,
            out IntPtr phToken
        );

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern Boolean CreateProcessAsUser 
        (
            IntPtr hToken,
            String lpApplicationName,
            String lpCommandLine,
            IntPtr lpProcessAttributes,
            IntPtr lpThreadAttributes,
            Boolean bInheritHandles,
            Int32 dwCreationFlags,
            IntPtr lpEnvironment,
            String lpCurrentDirectory,
            ref STARTUPINFO lpStartupInfo,
            out PROCESS_INFORMATION lpProcessInformation
        );

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern Boolean CreateProcessWithLogonW 
        (
            String lpszUsername, 
            String lpszDomain, 
            String lpszPassword,
            Int32 dwLogonFlags, 
            String applicationName, 
            String commandLine,
            Int32 creationFlags, 
            IntPtr environment,
            String currentDirectory,
            ref STARTUPINFO sui,
            out PROCESS_INFORMATION processInfo
        );

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern UInt32 WaitForSingleObject 
        (
            IntPtr hHandle,
            UInt32 dwMilliseconds
        );

        [DllImport("kernel32", SetLastError=true)]
        public static extern Boolean CloseHandle (IntPtr handle);
 
        #endregion

        #region "FUNCTIONS"

        public static void LaunchCommand1(string strCommand, string strDomain, string strName, string strPassword)
        {
            // Variables
            PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
            STARTUPINFO startInfo = new STARTUPINFO();
            bool bResult = false;
            UInt32 uiResultWait = WAIT_FAILED;

            try 
            {
                // Create process
                startInfo.cb = Marshal.SizeOf(startInfo);

                bResult = CreateProcessWithLogonW(
                    strName, 
                    strDomain, 
                    strPassword, 
                    0, 
                    null,
                    strCommand, 
                    0, 
                    IntPtr.Zero, 
                    null, 
                    ref startInfo, 
                    out processInfo
                );
                if (!bResult) { throw new Exception("CreateProcessWithLogonW error #" + Marshal.GetLastWin32Error().ToString()); }

                // Wait for process to end
                uiResultWait = WaitForSingleObject(processInfo.hProcess, INFINITE);
                if (uiResultWait == WAIT_FAILED) { throw new Exception("WaitForSingleObject error #" + Marshal.GetLastWin32Error()); }

            } 
            finally
            {
                // Close all handles
                CloseHandle(processInfo.hProcess);
                CloseHandle(processInfo.hThread);
            }
        }

        public static void LaunchCommand2(string strCommand, string strDomain, string strName, string strPassword)
        {
            // Variables
            PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
            STARTUPINFO startInfo = new STARTUPINFO();
            Boolean bResult = false;
            IntPtr hToken = IntPtr.Zero;
            UInt32 uiResultWait = WAIT_FAILED;
            
            try 
            {
                // Logon user
                bResult = Win32.LogonUser(
                    strName,
                    strDomain,
                    strPassword,
                    Win32.LogonType.LOGON32_LOGON_INTERACTIVE,
                    Win32.LogonProvider.LOGON32_PROVIDER_DEFAULT,
                    out hToken
                );
                if (!bResult) { throw new Exception("Logon error #" + Marshal.GetLastWin32Error()); }

                // Create process
                startInfo.cb = Marshal.SizeOf(startInfo);
                startInfo.lpDesktop = "winsta0\\default";

                bResult = Win32.CreateProcessAsUser(
                    hToken, 
                    null, 
                    strCommand, 
                    IntPtr.Zero,
                    IntPtr.Zero,
                    false,
                    0,
                    IntPtr.Zero,
                    null,
                    ref startInfo,
                    out processInfo
                );
                if (!bResult) { throw new Exception("CreateProcessAsUser error #" + Marshal.GetLastWin32Error()); }

                // Wait for process to end
                uiResultWait = WaitForSingleObject(processInfo.hProcess, INFINITE);
                if (uiResultWait == WAIT_FAILED) { throw new Exception("WaitForSingleObject error #" + Marshal.GetLastWin32Error()); }
            }
            finally 
            {
                // Close all handles
                CloseHandle(hToken);
                CloseHandle(processInfo.hProcess);
                CloseHandle(processInfo.hThread);
            }
        }

        #endregion
    }
}


</SAMPLE>

 

I hope this helps.

Cheers,

 

Alex (Alejandro Campos Magencio)

  • Hello,

    I tried the code on Vista and I get Error #5 with the two methods... What is Error #5 and where can I found all the error code explanations?

    Nevertheless, thanks for this code, it helps me very well.

    Djo

  • Error 5 means Access Denied. Vista is a bit different regarding these API. You won't be able to use them in a WinForms application. There are some restrictions when creating processes as other users. There is plenty of info about this on the Internet, but I should blog about this situation, too... In Vista we have to use ShellExecute API instead. Or use CreateProcessAsUser in a Windows Service running as Local System (CreateProcessWithLogonW won't work as Local System either)...

    I hope this helps.

    Regards,

    Alex

  • To run in ASP.NET the following line in LaunchCommand2

    should be removed...

         startInfo.lpDesktop = "winsta0\\default";

    And set ASP.NET App Pool run as Network Service.

  • You cannot use the token returned from LogonUser in the call to CreateProcessAsUser since LogonUser returns an impersonate token not a primary token. This is why the second method is failing. First create a duplicate key with DuplicateKeyEx specifying to create a primary key and then pass this new token to CreateProcessAsUser.

    Here's a code sample to duplicate the token.

    <SAMPLE>

               SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();

               sa.bInheritHandle = false;

               sa.Length = Marshal.SizeOf(sa);

               sa.lpSecurityDescriptor = (IntPtr)0;

               const uint GENERIC_ALL = 0x10000000;

               const int TokenPrimary = 1;

               const int SecurityImpersonation = 2;

               IntPtr DupedToken = new IntPtr(0);

               bool bRetVal = DuplicateTokenEx(

                   Token,

                   GENERIC_ALL,

                   ref sa,

                   SecurityImpersonation,

                   TokenPrimary,

                   ref DupedToken);

               // did DuplicateToken fail?

               if (bRetVal == false)

               {

                   int ret = Marshal.GetLastWin32Error();

                   Console.WriteLine("DuplicateToken failed with error code : {0}", ret);

                   Console.WriteLine("\nError: [{0}] {1}\n", ret, GetErrorMessage(ret));

                   CloseHandle(Token);

                   return null;

               }  

    </SAMPLE>

  • Thanks for that note Akash Kava,

    I was in need to run this on an ASP.NEt application. I am using Windows authentication in my application. Do I need to make any changes to web.config file aswell.

  • Hey, is this applicable in Windows Server 2008.

    thanks.

  • Will it Work on Windows Server 2008?

    Thanks and Regards

    Harsh

  • Hi,

    Windows Server 2008 is the same as Vista, so there are some issues reflected in the comments above. Still, it is easy to try if the code fits your needs or works for you.

    Regards,

    Alex

  • use LOGON_NETCREDENTIALS_ONLY flag for windows 7

  • cool, it works,

    old post but needed today :-)

    thnx

Page 1 of 1 (10 items)
Leave a Comment
  • Please add 5 and 1 and type the answer here:
  • Post
Translate This Page