Click here to Skip to main content
16,016,738 members
Articles / Programming Languages / C#
Article

RunAs Class

Rate me:
Please Sign up or sign in to vote.
4.74/5 (27 votes)
14 Feb 20054 min read 333.6K   8.4K   69   71
Class that wraps CreateProcessWithLogonW as well as a simple control that makes use of the RunAs class.

Sample Image - RunAs.jpg

Introduction

There are three projects in this solution: RunAs, UseRunAsControl, and ProcessToStart. RunAs is the focus of this solution; it contains the class that wraps CreateProcessWithLogonW. UseRunAsControl defines and makes use of a simple control implementing the RunAs class and is meant to test and show its functionality. ProcessToStart is simply a form that shows the domain and username of the user whose syntax it is running under. This is merely there to start with the UseRunAsControl to demonstrate its functionality.

Demo

To see the solution in action, grab a second set of credentials (make some dummy ones on your local machine, perhaps). Run UseRunAsControl.exe and provide the credentials. Click on "Command..." and browse to ProcessToStart.exe. Click on "Run Command". Provided the credentials are correct, you will see a MessageBox containing the process ID of the new process. ProcessToStart will display the username that it is running as. If the credentials that UseRunAsControl.exe is running under has enough privileges, when you close ProcessToStart, you will see another MessageBox notifying you that the process has ended. If the user does not have privileges to the new process, you will see a MessageBox notifying you of this, and when ProcessToStart.exe exits, you will not receive any notice.

Class Usage

Using the RunAs class is simple. Add a reference to the assembly and include the namespace VastAbyss. There is an overloaded static method named StartProcess in the class. This simple overload provides standard functionality that starts the executable as the user and loads the profile. A word of caution with using this method is that if the command line is C:\Program Files\Some Directory\Some.exe, if a Program.exe exists in C:\, it will be started and this may be seen as a security flaw. It is due to the way that CreateProcessWithLogonW parses and searches the command line (space-delimited). To avoid this, surround the command line in quotes. All of the overloads return a Process. If the process failed to start, it will be null and a Win32Exception will be thrown. Below is a sample of the simple usage:

C#
string username = "SomeUser";
string domain = "SomeDomain";
string password = "I'll never tell!";
string commandline = "\"C:\\Program Files\\Some Directory\\Some.exe\"";

// Resulting string is
//  "C:\Program Files\Some Directory\Some.exe"
// with the quotes included.
try
{
  Process proc = RunAs.StartProcess(username, domain, password,
                                                  commandline);
  try
  {
    proc.EnableRaisingEvents = true;
    proc.Exited += new EventHandler(processExited);
  }
  catch
  {
    //The process started but you don't have access to it.
  }
}
catch (Win32Exception w32e)
{
  // The process didn't start.
}

To avoid the security risk of using command line, use one of the other overloads of StartProcess() to provide the executable in appname instead of command line (command line must be used to provide parameters to the executable if needed; i.e., c:\myapp.exe /q /t). These overloads provide many more options for creating the new process. Enums are provided for supplying the values of the flags. Additional overloads can easily be added to provide full control over creating the new process. The struct definition for StartUpInfo is public and can be used with the last overload to provide the maximum amount of control.

I have added a default constructor to the RunAs class. This constructor initializes the properties to the following values: UserName (System.Environment.UserName), Domain (System.Environment.UserDomainName), Password (empty string ""), ApplicationName (CurrentProcess.StartInfo.FileName), LogonFlagsInstance (LogonFlags.WithProfile), CommandLine (System.Environment.CommandLine), CreationFlagsInstance (CreationFlags.NewConsole), CurrentDirectory (System.Environment.CurrentDirectory), Environment (IntPtr.Zero), ProcessInfo (new ProcessInformation instance), StartupInfo (new StartUpInfo instance with the following values set: cb is set to the size of the new instance, dwFlags is set to StartUpInfoFlags.UseCountChars, dwYCountChars is set to 50, lpTitle is set to CurrentProcess.MainWindowTitle). After initialization, these values can be changed and the non-static method StartProcess can be called.

Control Usage

I will leave the below code included although the focus of this project is to implement the RunAs class and not this control. This control merely serves as an example of how the RunAs class can be used. I removed the RunAsControl from the RunAs project and placed it in the UseRunAsControl project.

The RunAsControl can be quickly added to a Windows Form and the four events wired up. That's all there is to it. Below is an example usage:

C#
RunAsControl m_runAsCtl = new RunAsControl();
m_runAsCtl.ProcessStarted += new ProcessStartedEventHandler(m_pStarted);
m_runAsCtl.ProcessFailed += new ProcessFailedEventHandler(m_pFailed);
m_runAsCtl.ProcessEnded += new ProcessEndedEventHandler(m_pEnded);
m_runAsCtl.ProcessAccessFailed +=
    new ProcessAccessFailedEventHandler(m_pAccessFailed);

Comment Disclaimer

I referred to the MSDN documentation for the CreateProcessWithLogonW, PROCESS_INFORMATION, STARTUPINFO, etc... functions, structs, and constants. Most of the comments in the source code are either direct quotes from this documentation or adaptations of information from that documentation.

Thanks

I would like to thank those who provided feedback to this project. I have incorporated the suggestions and fixed the bugs that I found. I hope that this makes the project better, but if there are still things that you think are wrong with it, I welcome more constructive criticism.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralLicense Pin
Stefan Rusek11-Jan-06 3:19
Stefan Rusek11-Jan-06 3:19 
GeneralRe: License Pin
Dewey Vozel13-Jan-06 12:27
Dewey Vozel13-Jan-06 12:27 
GeneralStarting process from Windows Service Pin
mrdance13-Jul-05 7:16
mrdance13-Jul-05 7:16 
AnswerRe: Starting process from Windows Service Pin
davelogie27-Feb-06 7:44
davelogie27-Feb-06 7:44 
QuestionRe: Starting process from Windows Service Pin
Klampfi25-Apr-06 23:23
Klampfi25-Apr-06 23:23 
AnswerRe: Starting process from Windows Service Pin
davelogie26-Apr-06 2:49
davelogie26-Apr-06 2:49 
GeneralRe: Starting process from Windows Service Pin
Klampfi26-Apr-06 7:46
Klampfi26-Apr-06 7:46 
AnswerRe: Starting process from Windows Service Pin
davelogie26-Apr-06 8:41
davelogie26-Apr-06 8:41 
Here's logon stuff. You can add CreateProcessAsUser easy enough. Remember that the lpDesktop member of StartupInfo = "" (not NULL and not "WinSta0\Default") for this application. Good luck.

        <br />
<br />
        #region User Logon<br />
<br />
        /// <summary><br />
        /// The LogonUser function attempts to log a user on to the local computer.<br />
        /// </summary><br />
        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]<br />
        private static extern bool LogonUser(String lpszUsername, String lpszDomain, IntPtr lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr hToken);<br />
<br />
        /// <summary><br />
        /// The DuplicateTokenEx function creates a new access token that duplicates an existing token. This function can create either a primary token or an impersonation token.<br />
        /// </summary><br />
        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]<br />
        private static extern bool DuplicateTokenEx(IntPtr hExistingToken, int dwDesiredAccess, ref SecurityAttributes lpTokenAttributes,<br />
                                                    int impersonationLevel, int tokenType, out IntPtr phNewToken);<br />
<br />
        /// <summary><br />
        /// The LoadUserProfile function loads the specified user's profile<br />
        /// </summary><br />
        [DllImport("userenv.dll", CharSet = CharSet.Auto, SetLastError = true)]<br />
        private static extern bool LoadUserProfile(IntPtr hToken, ref ProfileInfo lpProfileInfo);<br />
<br />
        /// <summary><br />
        /// The UnloadUserProfile function unloads a user's profile that was loaded by the LoadUserProfile function<br />
        /// </summary><br />
        [DllImport("userenv.dll", CharSet = CharSet.Auto, SetLastError = true)]<br />
        private static extern bool UnloadUserProfile(IntPtr hToken, IntPtr hProfile);<br />
<br />
        /// <summary><br />
        /// Closes an open object handle.<br />
        /// </summary><br />
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]<br />
        private static extern bool CloseHandle(IntPtr handle);<br />
<br />
        /// <summary><br />
        /// The SECURITY_ATTRIBUTES structure contains the security descriptor for an object and specifies whether the handle retrieved by specifying this structure is inheritable<br />
        /// </summary><br />
        [StructLayout(LayoutKind.Sequential)]<br />
        public struct SecurityAttributes<br />
        {<br />
            public int dwLength;<br />
            public IntPtr lpSecurityDescriptor;<br />
            public bool bInheritHandle;<br />
        }<br />
<br />
        /// <summary><br />
        /// Profile Info<br />
        /// </summary><br />
        [StructLayout(LayoutKind.Sequential)]<br />
        public struct ProfileInfo<br />
        {<br />
            /// <summary><br />
            /// Specifies the size of the structure, in bytes.<br />
            /// </summary><br />
            public int dwSize;<br />
<br />
            /// <summary><br />
            /// This member can be one of the following flags: PI_NOUI or PI_APPLYPOLICY<br />
            /// </summary><br />
            public int dwFlags;<br />
<br />
            /// <summary><br />
            /// Pointer to the name of the user. <br />
            /// This member is used as the base name of the directory in which to store a new profile. <br />
            /// </summary><br />
            public string lpUserName;<br />
<br />
            /// <summary><br />
            /// Pointer to the roaming user profile path. <br />
            /// If the user does not have a roaming profile, this member can be NULL.<br />
            /// </summary><br />
            public string lpProfilePath;<br />
<br />
            /// <summary><br />
            /// Pointer to the default user profile path. This member can be NULL. <br />
            /// </summary><br />
            public string lpDefaultPath;<br />
<br />
            /// <summary><br />
            /// Pointer to the name of the validating domain controller, in NetBIOS format. <br />
            /// If this member is NULL, the Windows NT 4.0-style policy will not be applied. <br />
            /// </summary><br />
            public string lpServerName;<br />
<br />
            /// <summary><br />
            /// Pointer to the path of the Windows NT 4.0-style policy file. This member can be NULL. <br />
            /// </summary><br />
            public string lpPolicyPath;<br />
<br />
            /// <summary><br />
            /// Handle to the HKEY_CURRENT_USER registry key. <br />
            /// </summary><br />
            public IntPtr hProfile;<br />
        } <br />
<br />
        /// <summary><br />
        /// Logon type option. <br />
        /// </summary><br />
        [FlagsAttribute]<br />
        public enum LogonType<br />
        {<br />
            /// <summary><br />
            /// This logon type is intended for users who will be interactively using the computer<br />
            /// </summary><br />
            Interactive = 2,<br />
            /// <summary><br />
            /// This logon type is intended for high performance servers to authenticate plaintext passwords. <br />
            /// </summary><br />
            Network = 3,<br />
            /// <summary><br />
            /// This logon type is intended for batch servers, where processes may be executing on behalf of a user without their direct intervention.<br />
            /// </summary><br />
            Batch = 4,<br />
            /// <summary><br />
            /// Indicates a service-type logon. The account provided must have the service privilege enabled.<br />
            /// </summary><br />
            Service = 5,<br />
            /// <summary><br />
            /// This logon type is for GINA DLLs that log on users who will be interactively using the computer.<br />
            /// </summary><br />
            Unlock = 7<br />
        }<br />
<br />
        /// <summary><br />
        /// Specifies the logon provider. <br />
        /// </summary><br />
        [FlagsAttribute]<br />
        public enum LogonProvider<br />
        {<br />
            /// <summary><br />
            /// Use the standard logon provider for the system.<br />
            /// </summary><br />
            Default = 0,<br />
            /// <summary><br />
            /// Use the negotiate logon provider.  (WINNT50)<br />
            /// </summary><br />
            Negotiate = 3,<br />
            /// <summary><br />
            /// Use the NTLM logon provider (WINNT40)<br />
            /// </summary><br />
            NTLM = 2,<br />
            /// <summary><br />
            /// Use the Windows NT 3.5 logon provider.<br />
            /// </summary><br />
            WinNT35 = 1<br />
        }<br />
<br />
        /// <summary><br />
        /// Specifies the requested access rights for the new token.<br />
        /// </summary><br />
        [FlagsAttribute]<br />
        public enum DuplicateTokenDesiredAccess<br />
        {<br />
            /// <summary><br />
            /// To request the same access rights as the existing token, specify zero. <br />
            /// </summary><br />
            SameAsExisting = 0,<br />
            /// <summary><br />
            /// To request all access rights that are valid for the caller, specify MAXIMUM_ALLOWED.<br />
            /// </summary><br />
            MaximumAllowed = 0x02000000<br />
        }<br />
        /// <summary><br />
        /// Specifies a value from the SECURITY_IMPERSONATION_LEVEL enumeration that indicates the impersonation level of the new token <br />
        /// </summary><br />
        [FlagsAttribute]<br />
        public enum ImpersonationLevel<br />
        {<br />
            /// <summary><br />
            /// The server process cannot obtain identification information about the client, and it cannot impersonate the client. It is defined with no value given, and thus, by ANSI C rules, defaults to a value of zero.<br />
            /// </summary><br />
            Anonymous = 0,<br />
            /// <summary><br />
            ///  The server process can obtain information about the client, such as security identifiers and privileges, but it cannot impersonate the client. This is useful for servers that export their own objects, for example, database products that export tables and views. Using the retrieved client-security information, the server can make access-validation decisions without being able to use other services that are using the client's security context.,<br />
            /// </summary><br />
            Identification = 1,<br />
            /// <summary><br />
            /// The server process can impersonate the client's security context on its local system. The server cannot impersonate the client on remote systems.,<br />
            /// </summary><br />
            Impersonation = 2,<br />
            /// <summary><br />
            /// The server process can impersonate the client's security context on remote systems. This impersonation level is not supported on WinNT<br />
            /// </summary><br />
            Delegation = 3<br />
        }<br />
        /// <summary><br />
        /// Specifies the requested access rights for the new token.<br />
        /// </summary><br />
        [FlagsAttribute]<br />
        public enum TokenType<br />
        {<br />
            /// <summary><br />
            /// The new token is a primary token that you can use in the CreateProcessAsUser function. <br />
            /// </summary><br />
            Primary = 1,<br />
            /// <summary><br />
            /// The new token is an impersonation token. <br />
            /// </summary><br />
            Impersonation = 2<br />
        }<br />
        <br />
        private void LogonUser(String user, String domain, SecureString password, LogonType type, LogonProvider provider)<br />
        {<br />
            if (password.IsReadOnly() == false)<br />
                throw new InvalidOperationException("SecureString not ReadOnly");<br />
<br />
            if (string.IsNullOrEmpty(user) == true || string.IsNullOrEmpty(domain) == true)<br />
                throw new InvalidOperationException("No user account specified");<br />
<br />
            IntPtr handle;<br />
            IntPtr bstr = Marshal.SecureStringToBSTR(password);<br />
            bool result = LogonUser(user, domain, bstr, (int)type, (int)provider, out handle);<br />
            Marshal.ZeroFreeBSTR(bstr);<br />
<br />
            if (result == false)<br />
                throw new System.ComponentModel.Win32Exception();<br />
<br />
            SecurityAttributes sa = new SecurityAttributes();<br />
            sa.dwLength = Marshal.SizeOf(sa);<br />
            sa.lpSecurityDescriptor = IntPtr.Zero;<br />
            sa.bInheritHandle = true; <br />
<br />
            IntPtr newHandle;<br />
            result = DuplicateTokenEx(handle, (int)DuplicateTokenDesiredAccess.MaximumAllowed, ref sa,<br />
                            (int)ImpersonationLevel.Impersonation, (int)TokenType.Primary, out newHandle);<br />
            if (result == false)<br />
                throw new System.ComponentModel.Win32Exception();<br />
<br />
            CloseHandle(handle);<br />
            handle = newHandle;<br />
<br />
            hToken = handle;<br />
        }<br />
<br />
        public void LoadUserProfile(string username)<br />
        {<br />
            if (hToken == IntPtr.Zero)<br />
                throw new InvalidOperationException("User not logged in");<br />
<br />
            ProfileInfo info = new ProfileInfo();<br />
            info.dwSize = Marshal.SizeOf(info);<br />
            info.lpUserName = username;<br />
            info.dwFlags = 1; // PI_NOUI 0x00000001  // Prevents displaying of messages<br />
<br />
            bool result = LoadUserProfile(hToken, ref info);<br />
            if (result == false)<br />
                throw new System.ComponentModel.Win32Exception();<br />
<br />
            hProfile = info.hProfile;<br />
        }<br />
<br />
        internal void LogOffUser()<br />
        {<br />
#if false<br />
            string identity = WindowsIdentity.GetCurrent().Name;<br />
            string threadIdentity = Thread.CurrentPrincipal.Identity.Name;<br />
            scriptTask.State.AddMessage(DateTime.Now, string.Format("Logging off user {0} ({1})", identity, threadIdentity));<br />
#endif<br />
<br />
            WindowsIdentity.Impersonate(IntPtr.Zero);<br />
<br />
#if false<br />
            identity = WindowsIdentity.GetCurrent().Name;<br />
            Thread.CurrentPrincipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());<br />
            threadIdentity = Thread.CurrentPrincipal.Identity.Name;<br />
            scriptTask.State.AddMessage(DateTime.Now, string.Format("Identity now {0} ({1})", identity, threadIdentity));<br />
#endif<br />
<br />
            if (hToken != IntPtr.Zero && hProfile != IntPtr.Zero)<br />
            {<br />
                bool result = UnloadUserProfile(hToken, hProfile);<br />
                hProfile = IntPtr.Zero;<br />
<br />
                if (result == false)<br />
                    throw new System.ComponentModel.Win32Exception();<br />
            }<br />
<br />
            if (hToken != IntPtr.Zero)<br />
            {<br />
                bool result = CloseHandle(hToken);<br />
                hToken = IntPtr.Zero;<br />
<br />
                if (result == false)<br />
                    throw new System.ComponentModel.Win32Exception();<br />
            }<br />
<br />
        }<br />
<br />
        #endregion // User Logon<br />
<br />
<br />

QuestionRe: Starting process from Windows Service Pin
Klampfi27-Apr-06 3:36
Klampfi27-Apr-06 3:36 
AnswerRe: Starting process from Windows Service Pin
davelogie28-Apr-06 2:40
davelogie28-Apr-06 2:40 
GeneralRe: Starting process from Windows Service Pin
Klampfi1-May-06 20:55
Klampfi1-May-06 20:55 
GeneralRe: Starting process from Windows Service Pin
davelogie2-May-06 3:47
davelogie2-May-06 3:47 
GeneralRe: Starting process from Windows Service Pin
srgibson18-Jul-06 17:52
srgibson18-Jul-06 17:52 
GeneralImprovements in Whidbey Pin
Judah Gabriel Himango15-Feb-05 6:59
sponsorJudah Gabriel Himango15-Feb-05 6:59 
GeneralRe: Improvements in Whidbey Pin
Igor Velikorossov15-Feb-05 11:01
Igor Velikorossov15-Feb-05 11:01 
GeneralRe: Improvements in Whidbey Pin
Dewey Vozel15-Feb-05 13:54
Dewey Vozel15-Feb-05 13:54 
GeneralRe: Improvements in Whidbey Pin
Judah Gabriel Himango16-Feb-05 5:29
sponsorJudah Gabriel Himango16-Feb-05 5:29 
GeneralUpdate Incoming Pin
Dewey Vozel12-Feb-05 11:19
Dewey Vozel12-Feb-05 11:19 
GeneralNo access to new process Pin
QASR3-Nov-04 11:11
QASR3-Nov-04 11:11 
GeneralRe: No access to new process Pin
Dewey Vozel12-Feb-05 11:28
Dewey Vozel12-Feb-05 11:28 
GeneralImprove your code ... Pin
GProssliner9-Sep-04 2:44
GProssliner9-Sep-04 2:44 
GeneralRe: Improve your code ... Pin
Dewey Vozel9-Sep-04 3:49
Dewey Vozel9-Sep-04 3:49 
GeneralRe: Improve your code ... Pin
rramirezg17-Sep-04 6:43
rramirezg17-Sep-04 6:43 
GeneralRe: Improve your code ... Pin
GProssliner19-Sep-04 0:31
GProssliner19-Sep-04 0:31 
GeneralRe: Improve your code ... Pin
rramirezg30-Sep-04 11:48
rramirezg30-Sep-04 11:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.