I do a lot of server programming. I frequently need to be able to run some code as a different user than the currently logged-in user. Unfortunately, the .Net Framework does not make this particularly easy. Sure, there is the WindowsIdentity class and its Impersonate method. But how do you create an instance of a WindowsIdentity object? You need to pass the constructor a logon token. How do you get a logon token? Well, .Net is rather silent on that point.
PInvoke is your friend
The Win32 API does have a LogonUser function in the advapi32.dll DLL. Thanks to the wonderful work of PInvoke (now part of RedGate), you can easily get the signature of the LogonUser function translated into .Net. It looks like this:
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(string lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken);
I found a nice code snippet by Viji RAJKUMAR that showed how to use the LogonUser function, handle the Win32 error codes, and handle the .Net impersonation contexts. But do you really want deal with all of that whenever you have to run some code as someone else? I sure don’t.
Inspiration from SharePoint’s RunWithElevatedPrivileges
One thing that the SharePoint API has is a very handy method on the SPSecurity class called RunWithElevatedPrivileges. RunWithElevatedPrivileges takes a function pointer (called a delegate in .Net) and executes the code in the function in the context of a pre-defined user identity (the identity of the IIS application pool in which the code is running). I really liked the ease of invocation of RunWithElevatedPrivileges method, but I needed to have a method that allowed me to specify the user name and password of any arbitrary user account.
The RunAs method is born
I used Viji’s code snippet as a starting point, and enhanced it to accept a delegate function. The result is that I can easily call RunAs, passing in a username and password, and a block of code I want executed under that user’s identity. Calling RunAs looks like this:
1: BlackBlade.Utilities.SecurityUtilities.RunAs(delegate()
2: {
3: File.Move(“local_file”, “network_file_location”);
4: },
5: “a_username”,
6: “some_password”);
Line 3 in the above code snippet is the code that will be executed with the credentials supplied to the RunAs method. You can have as many lines of code between lines 2 and 4. And those lines of code can interact with variables outside the code block. The RunAs method takes care of the details of interoperating with the Win32 API and handling the impersonation and reversion of .Net security contexts.
RunAs implemented
Here’s the full implementation of the RunAs method. Less than 75 lines of code. You can compile this as an assembly and reference it form other projects. It’s very handy.
1: using System;
2: using System.Collections.Generic;
3: using System.Text;
4:
5: using System.Security.Principal;
6: using System.Runtime.InteropServices;
7:
8: namespace BlackBlade.Utilities
9: {
10: public class SecurityUtilities
11: {
12: [DllImport("advapi32.dll", SetLastError = true)]
13: private static extern bool LogonUser(string lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken);
14: public delegate void RunAsDelegate();
15:
16: public static void RunAs(RunAsDelegate MethodToRunAs, string Username, string Password)
17: {
18: string userName;
19: string domain;
20:
21: if (Username.IndexOf('\') > 0)
22: {
23: //a domain name was supplied
24: string[] usernameArray = Username.Split('\');
25: userName = usernameArray[1];
26: domain = usernameArray[0];
27: }
28: else
29: {
30: //there was no domain name supplied
31: userName = Username;
32: domain = ".";
33: }
34:
35: RunAs(MethodToRunAs, userName, Password, domain);
36: }
37:
38: public static void RunAs(RunAsDelegate MethodToRunAs, string Username, string Password, string Domain)
39: {
40: IntPtr imp_token;
41: WindowsIdentity wid_admin = null;
42: WindowsImpersonationContext wic = null;
43:
44: try
45: {
46: if (LogonUser(Username, string.IsNullOrEmpty(Domain) ? "." : Domain, Password, 9, 0, out imp_token))
47: {
48: //the impersonation suceeded
49: wid_admin = new WindowsIdentity(imp_token);
50: wic = wid_admin.Impersonate();
51:
52: //run the delegate method
53: MethodToRunAs();
54: }
55: else
56: throw new Exception(string.Format("Could not impersonate user {0} in domain {1} with the specified password.", Username, Domain));
57: }
58: catch (Exception se)
59: {
60: int ret = Marshal.GetLastWin32Error();
61: if (wic != null)
62: wic.Undo();
63:
64: throw new Exception("Error code: " + ret.ToString(), se);
65: }
66: finally
67: {
68: //revert to self
69: if (wic != null)
70: wic.Undo();
71: }
72: }
73: }
74: }
Line 53 in the previous code block invokes the caller-supplied code that should be run unsung the new impersonation context.
That is great man thanks it was very helpful to me
Hi, I tried this as well, but I run something like :
RunAs(delegate()
{
Process.Start(@"\ps01appsBatchfilesstaltestDONOTUSEictform.exe");
}, "domainadministrator", "secret");
However I get a Access Denied, is it maybe normal, or is it me ?
Nick, I've seen similar issues when invoking applications remotely with recent Windows service packs. The first thing I would try is signing in to the machine that is running the code as the administrator and attempting to run the application from the remote share. I suspect that the application will not start that way either. I've usually had to copy the executable to the local machine before executing it, even with elevated credentials.
I have another post that shows how to execute SharePoint STSADM commands remotely. You could adapt the approach to execute any command on a remote system.
great,
thanks a lot
Thanks i try the code but went i try to run as a different user and i look in task manager it's still under my credential
Process.Start(notepad); },runasusername,password);
but it will start under my context not runasusername
Thanks
It seems that Process.Start can be a tricky animal when impersonation is involved. There are lots of articles online that talk about what needs to be done in order to ensure that the started process has the correct impersonation context. The instruction vary depending on what type of application is calling the Process.Start method. More info here:
http://www.google.com/search?q=process.start+impersonation+context
Thanks i will take a look.
I have used your code to to open the SQL Server connection as another user as integrated security but it always successful even with wrong credentials. Please help me out there.