Introduction
Here is some kind of solution for web or desctop developer's that need to know what kind of errors or events do client and want to store all logs on one computer. .Net framework 2.0 have some unsupported fetures while working with system EventLog. As example you cannot fill in "User" field using methods in EventLog class. Shurely you can use Log4net or other 3-rd party open source libs. But sometimes it will cause DLL-Hell or customer dont accept 3-rd party. So you need to find solution for pretty nice logging that can be easely filtred and so on.
Background
I will use unmanaged code to get needed result. I do not recommened to use such implementations. This is for situations when you cant grade target framework from 2.0 to 3.0 or 3.5 only.
Using the code
My simple application is able to place into event log to type of messages. With User name and without.
When you use standart EventLog methods you should get next message in System Journal.
As you see User field(mark as red) is not planted. Now we will try to add it.
First of all you need to import next functions and declare next structures, variables.
<DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function RegisterEventSource(ByVal machine As String, ByVal source As String) As IntPtr
End Function
<DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function DeregisterEventSource(ByVal handle As IntPtr) As Boolean
End Function
<DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function ReportEvent(ByVal hHandle As IntPtr,
ByVal wType As UShort, ByVal wCategory As UShort, ByVal dwEventID As
UInteger, ByVal uSid As IntPtr, ByVal wStrings As UShort, _
ByVal dwDataSize As UInteger, ByVal lpStrings As String(), ByVal bData As Byte) As Boolean
End Function
<DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function GetTokenInformation(ByVal handle As
IntPtr, ByVal token As TOKEN_INFORMATION_CLASS, ByVal uSid As IntPtr,
ByVal size As UInteger, ByRef tokenSize As UInteger) As Boolean
End Function
<DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function OpenEventLog(ByVal lpUNCServerName As String, ByVal lpSourceName As String) As IntPtr
End Function
Protected Declare Function LogonUserA 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 Integer
Protected Declare Auto Function DuplicateToken Lib "advapi32.dll" ( _
ByVal ExistingTokenHandle As IntPtr, _
ByVal ImpersonationLevel As Integer, _
ByRef DuplicateTokenHandle As IntPtr) As Integer
Protected Declare Auto Function RevertToSelf Lib "advapi32.dll" () As Long
Protected Declare Auto Function CloseHandle Lib "kernel32.dll" (ByVal handle As IntPtr) As Long
#End Region
#Region "API adaptation"
Protected Enum TOKEN_INFORMATION_CLASS
TokenUser = 1
TokenGroups
TokenPrivileges
TokenOwner
TokenPrimaryGroup
TokenDefaultDacl
TokenSource
TokenType
TokenImpersonationLevel
TokenStatistics
TokenRestrictedSids
TokenSessionId
TokenGroupsAndPrivileges
TokenSessionReference
TokenSandBoxInert
TokenAuditPolicy
TokenOrigin
End Enum
Protected Structure TOKEN_USER
Public User As SID_AND_ATTRIBUTES
End Structure
Protected Structure SID_AND_ATTRIBUTES
Public Sid As IntPtr
Public Attributes As Integer
End Structure
Private LOGON32_LOGON_INTERACTIVE As Integer = 2
Private LOGON32_PROVIDER_DEFAULT As Integer = 0
#End Region
Now we can add UserName to that field.
Dim eventSrcHandle As IntPtr = RegisterEventSource(Nothing, iSource)
Dim tokenInfoSize As UInteger = 0
Dim userTokenPtr As IntPtr = WindowsIdentity.GetCurrent().Token
Dim bresult As Boolean = GetTokenInformation(userTokenPtr, TOKEN_INFORMATION_CLASS.TokenUser, IntPtr.Zero, tokenInfoSize, tokenInfoSize)
Dim [error] As Integer = Marshal.GetLastWin32Error()
Dim userTokenInfo As IntPtr = Marshal.AllocHGlobal(CInt(tokenInfoSize))
bresult = GetTokenInformation(userTokenPtr, TOKEN_INFORMATION_CLASS.TokenUser, userTokenInfo, tokenInfoSize, tokenInfoSize)
If bresult Then
Dim tokUser As TOKEN_USER = DirectCast(Marshal.PtrToStructure(userTokenInfo, GetType(TOKEN_USER)), TOKEN_USER)
Dim message As String() = New String(0) {}
message(0) = iMessage
bresult = ReportEvent(eventSrcHandle, iType, iCategory, iEventID, tokUser.User.Sid, 1, _
0, message, New Byte())
End If
DeregisterEventSource(eventSrcHandle)
Marshal.FreeHGlobal(userTokenInfo)
On ASP.net web site if we log error in such manner we should get next in System Journal.
That's look's much more better, than previous one. But what we might do if we want to log user that do some action with our web-application.
Here we must have a deep look on UserTokens.
So the first point that we cannot make event with user 'X' if there is no such user in current domain.
Point second we must know password if our site is running on a muchine with not Server OS(for example Windows XP).
Third point if our Machine is Windows 2003 or something like this. You can get UserToken using UPN user name 'name@domainname.machinename.com'(UPN might look like this). And you don't need to import LogonUserA function to get UserToken. You can use System.Security.Principal namespace to get it
And at last. System call ReportEvent will logon event with owner of process UserName. So to change UserName you need to do impersonation. The next piece of code might help.
If impersonateValidUser("user", "domain", "password") Then
undoImpersonation()
Else
End If
Private Function impersonateValidUser(ByVal userName As String, _
ByVal domain As String, ByVal password As String) As Boolean
Dim tempWindowsIdentity As WindowsIdentity
Dim token As IntPtr = IntPtr.Zero
Dim tokenDuplicate As IntPtr = IntPtr.Zero
impersonateValidUser = False
If RevertToSelf() Then
If LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, token) <> 0 Then
If DuplicateToken(token, 2, tokenDuplicate) <> 0 Then
tempWindowsIdentity = New WindowsIdentity(tokenDuplicate)
impersonationContext = tempWindowsIdentity.Impersonate()
If Not impersonationContext Is Nothing Then
impersonateValidUser = True
End If
End If
End If
End If
If Not tokenDuplicate.Equals(IntPtr.Zero) Then
CloseHandle(tokenDuplicate)
End If
If Not token.Equals(IntPtr.Zero) Then
CloseHandle(token)
End If
End Function
Points of Interest
This can be used not only for logging. I think this piece of code can be used to do any type of secure actions in AD
History
Version 1.0.0