Do the Chameleon - Windows Impersonation Made Easy

Recently I again stumbled into a situation where I just wanted to test, if a certain portion of my code would also work, if it ran under different Windows user accounts. But instead of switching users by hand, I wanted to impersonate them in my code. How to do this in Managed Code (e.g. C#), is described in quite a few online sources. Here are two examples:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemSecurityPrincipalWindowsIdentityClassImpersonateTopic.asp

http://www.codeproject.com/csharp/lsadotnet.asp

But what I find deplorable is, there are so few (or even no) copy&paste solutions for such small solutions. What I mean is: Authors mostly just explain the problem and a possible solution (with every little step) - but they don´t provide a ready to use component. Why don´t they put everything needed into just a class? Or if it becomes a little more complex, an assembly? It´s this extra level in abstraction or value, that I´m often missing from explanations found online or offline. For understanding I´m interested in every detail - but for my solution I just need a couple of lines explaining, how to use the solution.

(Before anyone get´s anry at me for my limited world view: Sure there are also many authors who not just think "explanation" but also "added value" and "component". But, alas, I can´t help thinking, it´s still too few.)

Never mind.

Since I badly needed a solution for the impersonation problem, I banged together a small "component" myself from what I found online. (Mainly from the Microsoft source.) So whoever is in the need for quick impersonation, here´s a simple VB.NET class to use. Just put it in your project and become a chameleon in just two lines of code:

Dim aa As New AliasAccount("mydomain\myusername", "mypassword")
aa.BeginImpersonation()
...
aa.EndImpersonation()

(See how short an explanation of how to do impersonation can be?)

It worked find for me within WebForms code behind and WinForms applications.

Enjoy!

PS: And here the length details. Read someplace else, what it all means. If you want to use it, just copy the class into your project and you´re set :-)

 

Public Class AliasAccount
    Private _username, _password, _domainname As String

    Private _tokenHandle As New IntPtr(0)
    Private _dupeTokenHandle As New IntPtr(0)
    Private _impersonatedUser As System.Security.Principal.WindowsImpersonationContext


    Public Sub New(ByVal username As String, ByVal password As String)
        Dim nameparts() As String = username.Split("\")
        If nameparts.Length > 1 Then
            _domainname = nameparts(0)
            _username = nameparts(1)
        Else
            _username = username
        End If
        _password = password
    End Sub

    Public Sub New(ByVal username As String, ByVal password As String, ByVal domainname As String)
        _username = username
        _password = password
        _domainname = domainname
    End Sub


    Public Sub BeginImpersonation()
        Const LOGON32_PROVIDER_DEFAULT As Integer = 0
        Const LOGON32_LOGON_INTERACTIVE As Integer = 2
        Const SecurityImpersonation As Integer = 2

        Dim win32ErrorNumber As Integer

        _tokenHandle = IntPtr.Zero
        _dupeTokenHandle = IntPtr.Zero

        If Not LogonUser(_username, _domainname, _password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, _tokenHandle) Then
            win32ErrorNumber = System.Runtime.InteropServices.Marshal.GetLastWin32Error()
            Throw New ImpersonationException(win32ErrorNumber, GetErrorMessage(win32ErrorNumber), _username, _domainname)
        End If

        If Not DuplicateToken(_tokenHandle, SecurityImpersonation, _dupeTokenHandle) Then
            win32ErrorNumber = System.Runtime.InteropServices.Marshal.GetLastWin32Error()

            CloseHandle(_tokenHandle)
            Throw New ImpersonationException(win32ErrorNumber, "Unable to duplicate token!", _username, _domainname)
        End If

        Dim newId As New System.Security.Principal.WindowsIdentity(_dupeTokenHandle)
        _impersonatedUser = newId.Impersonate()
    End Sub


    Public Sub EndImpersonation()
        If Not _impersonatedUser Is Nothing Then
            _impersonatedUser.Undo()
            _impersonatedUser = Nothing

            If Not System.IntPtr.op_Equality(_tokenHandle, IntPtr.Zero) Then
                CloseHandle(_tokenHandle)
            End If
            If Not System.IntPtr.op_Equality(_dupeTokenHandle, IntPtr.Zero) Then
                CloseHandle(_dupeTokenHandle)
            End If
        End If
    End Sub


    Public ReadOnly Property username() As String
        Get
            Return _username
        End Get
    End Property

    Public ReadOnly Property domainname() As String
        Get
            Return _domainname
        End Get
    End Property


    Public ReadOnly Property currentWindowsUsername() As String
        Get
            Return System.Security.Principal.WindowsIdentity.GetCurrent().Name
        End Get
    End Property


#Region "Exception Class"
    Public Class ImpersonationException
        Inherits System.Exception

        Public ReadOnly win32ErrorNumber As Integer

        Public Sub New(ByVal win32ErrorNumber As Integer, ByVal msg As String, ByVal username As String, ByVal domainname As String)
            MyBase.New(String.Format("Impersonation of {1}\{0} failed! [{2}] {3}", username, domainname, win32ErrorNumber, msg))
            Me.win32ErrorNumber = win32ErrorNumber
        End Sub
    End Class
#End Region


#Region "External Declarations and Helpers"
    Private Declare Auto Function LogonUser Lib "advapi32.dll" (ByVal lpszUsername As [String], _
            ByVal lpszDomain As [String], ByVal lpszPassword As [String], _
            ByVal dwLogonType As Integer, ByVal dwLogonProvider As Integer, _
            ByRef phToken As IntPtr) As Boolean


    Private Declare Auto Function DuplicateToken Lib "advapi32.dll" (ByVal ExistingTokenHandle As IntPtr, _
                ByVal SECURITY_IMPERSONATION_LEVEL As Integer, _
                ByRef DuplicateTokenHandle As IntPtr) As Boolean


    Private Declare Auto Function CloseHandle Lib "kernel32.dll" (ByVal handle As IntPtr) As Boolean


    <System.Runtime.InteropServices.DllImport("kernel32.dll")> _
    Private Shared Function FormatMessage(ByVal dwFlags As Integer, ByRef lpSource As IntPtr, _
            ByVal dwMessageId As Integer, ByVal dwLanguageId As Integer, ByRef lpBuffer As [String], _
            ByVal nSize As Integer, ByRef Arguments As IntPtr) As Integer
    End Function


    Private Function GetErrorMessage(ByVal errorCode As Integer) As String
        Dim FORMAT_MESSAGE_ALLOCATE_BUFFER As Integer = &H100
        Dim FORMAT_MESSAGE_IGNORE_INSERTS As Integer = &H200
        Dim FORMAT_MESSAGE_FROM_SYSTEM As Integer = &H1000

        Dim messageSize As Integer = 255
        Dim lpMsgBuf As String
        Dim dwFlags As Integer = FORMAT_MESSAGE_ALLOCATE_BUFFER Or FORMAT_MESSAGE_FROM_SYSTEM Or FORMAT_MESSAGE_IGNORE_INSERTS

        Dim ptrlpSource As IntPtr = IntPtr.Zero
        Dim prtArguments As IntPtr = IntPtr.Zero

        Dim retVal As Integer = FormatMessage(dwFlags, ptrlpSource, errorCode, 0, lpMsgBuf, messageSize, prtArguments)
        If 0 = retVal Then
            Throw New System.Exception("Failed to format message for error code " + errorCode.ToString() + ". ")
        End If

        Return lpMsgBuf
    End Function

#End Region

End Class

 

Comments

# re: Do the Chameleon - Windows Impersonation Made Easy

Monday, November 24, 2003 5:51 PM by Ben Lower

Do I have to know the password of the account I want to impersonate? If so, is there a way to accomplish impersonation without knowing the password? I would like to be able to do this for testing purposes on an application.

# re: Do the Chameleon - Windows Impersonation Made Easy

Tuesday, November 25, 2003 1:39 AM by Ralf Westphal

Sure, you need to know the password of the user you want to impersonate. Impersonation is like logging in on a Windows machine.

# re: Starting Subprocesses

Monday, January 05, 2004 5:13 AM by michael

hello,

if i start a new process with the
impersonated process, e. g. an
unmanaged win32-exe (1.exe), and this exe starts again
an unmanaged win32-exe (2.exe),
is the 2.exe run under the origin Account or under the impersonated account.
Is the accountinformation beeing inherited?

And what is about COM-Services startet by exe2 configured to be run under the currentlogin-User-Context?

# re: Do the Chameleon - Windows Impersonation Made Easy

Thursday, April 01, 2004 9:18 AM by William

Does anybody have experience of using Windows Impersonation directly from the Windows API, I found a good reference to it here:
http://www.xaml.net/articles/Win32-API-DllImport-art9.asp

but there's no code example! grrr.

# re: Do the Chameleon - Windows Impersonation Made Easy

Wednesday, April 14, 2004 9:20 AM by Marc van Orsouw aka /\/\o\/\/

ls,

I used the almost the same code
to do my impersonations

only if I start a new MMC process (code below)

the impersonation is not right (looks like its mixed)
for example if I start AD losers & computers, i can't open any property windows

commandline tools seem to work fine

greetings /\/\o\/\/

Dim objProcess As New Process()
objProcess.StartInfo.UseShellExecute = False
objProcess.StartInfo.RedirectStandardOutput = False
objProcess.StartInfo.CreateNoWindow = False
objProcess.StartInfo.RedirectStandardError = False
objProcess.StartInfo.FileName() = "mmc"
objProcess.StartInfo.Arguments() = "/a """ & strMscPath & """"
objProcess.Start()

# re: Do the Chameleon - Windows Impersonation Made Easy

Thursday, May 13, 2004 6:54 AM by /\/\o\/\/

I also Found a way to start a (External)process as a new user
using CreateProcessWithLogonW, this in stead of impersoniting in a thread, so you can start external tools as a other user


greetings Marc van Orsouw (/\/\o\/\/)


Imports System
Imports System.Runtime.InteropServices
Public Class clsRunAs
Public Structure PROCESS_INFO
Public hProcess As IntPtr
Public hThread As IntPtr
Public dwProcessId As Integer
Public dwThreadId As Integer
End Structure
Public Structure STARTUP_INFO
Public cb As Integer
Public lpReserved As Integer
<MarshalAs(UnmanagedType.LPTStr)> _
Public lpDesktop As String
<MarshalAs(UnmanagedType.LPTStr)> _
Public lpTitle As String
Public dwX As Long
Public dwY As Integer
Public dwXSize As Integer
Public dwYSize As Integer
Public dwXCountChars As Integer
Public dwYCountChars As Integer
Public dwFillAttribute As Integer
Public dwFlags As Integer
Public wShowWindow As Short
Public cbReserved2 As Short
Public lpReserved2 As Integer
Public hStdInput As Integer
Public hStdOutput As Integer
Public hStdError As Integer
End Structure

<DllImport("C:\\Windows\\System32\\advapi32.dll")> _
Public Shared Function CreateProcessWithLogonW( _
<MarshalAs(UnmanagedType.LPWStr)> ByVal lpUsername As String, _
<MarshalAs(UnmanagedType.LPWStr)> ByVal lpDomain As String, _
<MarshalAs(UnmanagedType.LPWStr)> ByVal lpPassword As String, _
ByVal dwLogonFlags As Integer, _
<MarshalAs(UnmanagedType.LPWStr)> ByVal lpApplicationName As String, _
<MarshalAs(UnmanagedType.LPWStr)> ByVal lpCommandLine As String, _
ByVal lpCreationFlags As Integer, _
ByVal lpVoid As Integer, _
<MarshalAs(UnmanagedType.LPWStr)> ByVal lpCurrentDirectory As String, _
ByRef lpStartupInfo As STARTUP_INFO, _
ByRef lpProcessInfo As PROCESS_INFO) As Boolean
End Function

Public Sub start(ByVal strCMD As String, ByVal strUser As String, ByVal strPass As String, Optional ByVal strArguments As String = "")
Dim strDomain As String
Try
strDomain = Split(strUser, "\")(0)
strUser = Split(strUser, "\")(1)
Catch
' no Domain given Try invoking users Domain
strDomain = System.Environment.UserDomainName
End Try

Dim lres As Long
Dim pStartInfo As STARTUP_INFO
Dim pProcessInfo As PROCESS_INFO
Dim LOGON_NETCREDENTIALS_ONLY As Long = &H1&
Dim CREATE_DEFAULT_ERROR_MODE As Long = &H4000000
Dim lpUsername As String = strUser
Dim lpPassword As String = strPass
Dim lpApplicationName As String = strCMD
Dim lpdomainname As String = strDomain
Dim lpcommandline As String
If strArguments = "" Then
lpcommandline = VariantType.Null
Else
lpcommandline = strArguments
End If

Dim lpCurrentDirectory As String = "c:\"
pStartInfo.cb = Len(pStartInfo)
pStartInfo.lpTitle = "MowConsole"
pStartInfo.dwFlags = 0&
Try
lres = CreateProcessWithLogonW(lpUsername, lpdomainname, lpPassword, LOGON_NETCREDENTIALS_ONLY, lpApplicationName, lpcommandline, CREATE_DEFAULT_ERROR_MODE, 0, lpCurrentDirectory, pStartInfo, pProcessInfo)
Catch ec As Exception
MsgBox(ec.ToString)
End Try
End Sub
End Class

# re: Do the Chameleon - Windows Impersonation Made Easy

Thursday, January 04, 2007 8:09 AM by Paul

This worked for me in minutes. Excellent article, well done, and thank you.

# re: Do the Chameleon - Windows Impersonation Made Easy

Thursday, January 11, 2007 9:45 PM by Javier Ralda

Thanks for this page.

Really save my day (or nigth)

Best regards ;)

# re: Do the Chameleon - Windows Impersonation Made Easy

Thursday, April 12, 2007 9:13 AM by Jolly_Dodger

Thanks very much saved me hours of searching/coding  you're a star !

# re: Do the Chameleon - Windows Impersonation Made Easy

Wednesday, July 25, 2007 12:46 PM by BulletProofPoet

Cheers for this mate - you've just saved me hours of research!  I can just about follow what's going on too!

Many thanks!

# re: Do the Chameleon - Windows Impersonation Made Easy

Friday, July 27, 2007 3:11 PM by JD

Hey there & thanks for putting up a nice straightforward solution to this!  I am testing out the solution, but am hitting an issue:  

Impersonation of domain\user failed! [1326] Logon failure: unknown user name or bad password.

Now the user/pass/domain that I am sending in as parameters are valid (I can successfully logon if I use the start-run dialog and have the windows prompt popup).  I don't know if this is an issue, but this is a network share I am trying to access by using the impersonate.  Any thoughts/pointers on this?

# re: Do the Chameleon - Windows Impersonation Made Easy

Friday, July 27, 2007 7:26 PM by Subrata Ghosh

Good Work.... It worked.........for me.

Thanks a ton......

# re: Do the Chameleon - Windows Impersonation Made Easy

Thursday, August 16, 2007 5:19 PM by Corey G

Are any alterations required to make it work for a console application??

# re: Do the Chameleon - Windows Impersonation Made Easy

Wednesday, August 29, 2007 11:17 AM by Ali Khawaja

Excellent Job. I copy/pasted into a vb class library, imported that into my project and worked beautifully. really really appreciate all the great work.

# re: Do the Chameleon - Windows Impersonation Made Easy

Wednesday, November 28, 2007 7:41 AM by Peter (UK)

This code is brill! Thanks a lot its saved me a hell of a lot of work.

# re: Do the Chameleon - Windows Impersonation Made Easy

Wednesday, February 06, 2008 9:57 AM by Ian (UK)

I am using this to try and get a non domain pc to be able to authenticate using a set of domain credentials and access a share on a domain windows 2003 server, i get this error:

Impersonation of domain\user failed! [1326] Logon failure: unknown user name or bad password.

Can anyone shed any light on this, or point me in the direction?

Many thanks.

# re: Do the Chameleon - Windows Impersonation Made Easy

Friday, February 22, 2008 10:31 AM by Kevin Fairchild

Any known issues with querying WMI while impersonating?

If I use System.Management.ObjectQuery("SELECT * FROM Win32_Printer WHERE Network=True") while impersonating, no printers are listed.  Calling EndImpersonation just before it, though, allows me to see the network printer on my machine.

The same network printer is installed locally for both the current user and the impersonated user.

I was looking into impersonation for the WMI object, but it doesn't look like that was really made for local machine access...

Any thoughts?

# re: Do the Chameleon - Windows Impersonation Made Easy

Friday, April 11, 2008 5:50 PM by Juan David

Absolutely spectacular ... works, period.

Thanks a lot !

# re: Do the Chameleon - Windows Impersonation Made Easy

Thursday, April 24, 2008 11:22 PM by Jody

I made a test app with this class and it worked great!  Save me a lot of time and energy.  

Thanks you so much!!!

# re: Do the Chameleon - Windows Impersonation Made Easy

Thursday, May 22, 2008 1:30 PM by Kevin

Worked beautifully.  Thanks.

# re: Do the Chameleon - Windows Impersonation Made Easy

Tuesday, June 03, 2008 12:06 PM by PJB

You saved me a lot of time. Thank you for giving a complete code set. Tired of all the grab and make fixes or add missing pieces code out there.

# re: Do the Chameleon - Windows Impersonation Made Easy

Tuesday, June 24, 2008 10:16 PM by KevinA

I have pretty much the same comment as everyone else: Great code, thanks for saving me lots of time!

# re: Do the Chameleon - Windows Impersonation Made Easy

Thursday, August 28, 2008 11:35 PM by Ander

Cheers mate : ) Just what was needed.

# re: Do the Chameleon - Windows Impersonation Made Easy

Sunday, September 07, 2008 10:22 AM by nickj

Hi, i'm trying to use this method to launch an application from a windows service under, but the process still isn't owned by the impersonated user. Anyone have any success doing this?

# Connect to Sql Server using Windows Authentication with Different Username and Password | it.rss24h.com

Pingback from  Connect to Sql Server using Windows Authentication with Different Username and Password | it.rss24h.com

Leave a Comment

(required) 
(required) 
(optional)
(required)