Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Parsing event log(*.evt) file

4.44/5 (21 votes)
23 Aug 2006CPOL3 min read 3   4.8K  
An article on parsing/opening event log files(*.evt) using c#

Sample Image - EventLogParser.jpg

Introduction

Some days back, I was searching for code snippets for opening an event log file. Even though there are a lot of samples available for getting the event log of a local system, there was no help for opening a *.evt file. So, I thought of coming up with this article.

Using the code

The code included uses unsafe methods. So while compiling, use the /unsafe switch.

Reading the event log file

The structure of an event log file is a little complex. The file has a 48 byte header which we can use to validate it. The bytes 5 - 8 will hold the signature of the file, which is a uint (DWORD) value that is always set to ELF_LOG_SIGNATURE (the value is 0x654c664c), which is ASCII for eLfL. Bytes 25 - 28 will store the index for the next event log entry. The uint value from bytes 21 - 24 will have the position where the next entry will be recorded.

Each event log record entered in the log file will have the following structure:

C#
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct EventLogRecord
{
    public uint Length;
    public uint Reserved;
    public uint RecordNumber;
    public uint TimeGenerated;
    public uint TimeWritten;
    public uint EventID;

    public ushort EventType;
    public ushort NumStrings;
    public ushort EventCategory;
    public ushort ReservedFlags;
    public uint ClosingRecordNumber;
    public uint StringOffset;
    public uint UserSidLength;
    public uint UserSidOffset;
    public uint DataLength;
    public uint DataOffset;

    // 
    // Followed by: 
    // 
    // String SourceName 
    // String Computername 
    // SID   UserSid 
    // String[] Description 
    // byte[]  Data 
    // char[]  Pad
    // uint Length 
    // 
}

Length is the size of this event record, in bytes. TimeGenerated is the time at which this entry was submitted. This time is measured in the number of seconds elapsed since 00:00:00 January 1, 1970, Universal Coordinated Time. TimeWritten is the time at which this entry was received by the service to be written to the log. This time is measured in the number of seconds elapsed since 00:00:00 January 1, 1970, Universal Coordinated Time.

We can clearly see how the structure would be saved in the log file, but this structure is different to many others. Since this particular structure doesn't have a fixed size, it has a fixed part and a variable part. The fixed part occupies 56 bytes, and the variable part has a size which is the difference of the 'Length' property and the fixed part.

The property 'SourceName' starts at position 57, but we don't know where it ends. The end of the data is marked by the character '\0'. The next property 'Computername' also works the same way as the previous one, ending with the character '\0'. Now, if we observe how the property "UserSid" is made up, we can see that it starts at the offset defined in the property 'UserSidOffset' and the length is marked by 'UserSidLength', which marks the number of characters that have to be counted from the starting position. The property 'Description' uses the same model as 'UserSid', with the difference that there is nobody to tell it how many characters should be counted from the beginning, and neither would it be valid to count characters until a "\0" is encountered, since there can be as many of these as there are strings found to save in the event. To be able to find the end of this property, we need to subtract 'DataOffset' from the property 'StringOffset'.

The SID obtained has to be converted to UserName using the following function, with the help of the LookupAccountSid API.

C#
private string GetUserInfo(byte[] buff)
{
    StringBuilder name = new StringBuilder();
    uint cchName = (uint)name.Capacity;
    StringBuilder referencedDomainName = new StringBuilder();
    uint cchReferencedDomainName = (uint)referencedDomainName.Capacity;
    SID_NAME_USE sidUse;

    int err = NO_ERROR;
    if (!LookupAccountSid(null, buff, name, ref cchName, 
         referencedDomainName, ref cchReferencedDomainName, out sidUse))
    {
        err = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
        if (err == ERROR_INSUFFICIENT_BUFFER)
        {
            name.EnsureCapacity((int)cchName);
            referencedDomainName.EnsureCapacity((int)cchReferencedDomainName);
            err = NO_ERROR;
            if (!LookupAccountSid(null, buff, name, ref cchName, 
                                  referencedDomainName, 
                                  ref cchReferencedDomainName, 
                                  out sidUse))
                err = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
        }
    }
    if (err == 0)
        return String.Format(@"{0}\{1}", 
               referencedDomainName.ToString(), name.ToString());
    else
        return @"N\A";
}
C#
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool LookupAccountSid(
    string lpSystemName,
    [MarshalAs(UnmanagedType.LPArray)] byte[] Sid,
    System.Text.StringBuilder lpName,
    ref uint cchName,
    System.Text.StringBuilder ReferencedDomainName,
    ref uint cchReferencedDomainName,
    out SID_NAME_USE peUse);

'TimeWritten' has to be converted to a valid DateTime format. With the help of the GetTimeZoneInformation API, we can achieve this.

C#
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern int GetTimeZoneInformation(out TimeZoneInformation 
                                                 lpTimeZoneInformation);
C#
private DateTime GetTime(uint time)
{
    TimeZoneInformation tzi;
    uint offset;
    GetTimeZoneInformation(out tzi);
    offset = (uint)(tzi.bias * 60) - (uint)(tzi.daylightBias * 60);
    DateTime output = new DateTime(1970, 1, 1, 0, 0, 0);
    time = time - offset;
    output = output.AddSeconds(time);
    return output;
}

Points of interest

If you have any suggestions or questions, you are welcome!

TODO: Use the FormatMessage API or suitable methods for formatting the description.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)