File: System\Diagnostics\EventLogEntry.cs
Web Access
Project: src\src\runtime\src\libraries\System.Diagnostics.EventLog\src\System.Diagnostics.EventLog.csproj (System.Diagnostics.EventLog)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Text;
using Microsoft.Win32;

namespace System.Diagnostics
{
    [
    ToolboxItem(false),
    DesignTimeVisible(false),
    ]
    public sealed class EventLogEntry : Component, ISerializable
    {
        internal byte[] dataBuf;
        internal int bufOffset;
        private readonly EventLogInternal owner;
        private string? category;
        private string? message;

        internal EventLogEntry(byte[] buf, int offset, EventLogInternal log)
        {
            this.dataBuf = buf;
            this.bufOffset = offset;
            this.owner = log;

            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// The machine on which this event log resides.
        /// </summary>
        public string MachineName
        {
            get
            {
                // first skip over the source name
                int pos = bufOffset + FieldOffsets.RAWDATA;
                while (CharFrom(dataBuf, pos) != '\0')
                    pos += 2;
                pos += 2;
                char ch = CharFrom(dataBuf, pos);
                StringBuilder buf = new StringBuilder();
                while (ch != '\0')
                {
                    buf.Append(ch);
                    pos += 2;
                    ch = CharFrom(dataBuf, pos);
                }

                return buf.ToString();
            }
        }

        /// <summary>
        /// The binary data associated with this entry in the event log.
        /// </summary>
        public byte[] Data
        {
            get
            {
                int dataLen = IntFrom(dataBuf, bufOffset + FieldOffsets.DATALENGTH);
                byte[] data = new byte[dataLen];
                Array.Copy(dataBuf, bufOffset + IntFrom(dataBuf, bufOffset + FieldOffsets.DATAOFFSET),
                           data, 0, dataLen);
                return data;
            }
        }

        /// <summary>
        /// The sequence of this entry in the event log.
        /// </summary>
        public int Index
        {
            get
            {
                return IntFrom(dataBuf, bufOffset + FieldOffsets.RECORDNUMBER);
            }
        }

        /// <summary>
        /// The category for this message.
        /// </summary>
        public string Category
        {
            get
            {
                if (category == null)
                {
                    string? dllName = GetMessageLibraryNames("CategoryMessageFile");
                    string? cat = owner.FormatMessageWrapper(dllName, (uint)CategoryNumber, null);
                    if (cat == null)
                        category = "(" + CategoryNumber.ToString(CultureInfo.CurrentCulture) + ")";
                    else
                        category = cat;
                }

                return category;
            }
        }

        /// <summary>
        /// An application-specific category number assigned to this entry.
        /// </summary>
        public short CategoryNumber
        {
            get
            {
                return ShortFrom(dataBuf, bufOffset + FieldOffsets.EVENTCATEGORY);
            }
        }

        /// <summary>
        /// The number identifying the message for this source.
        /// </summary>
        [Obsolete("EventLogEntry.EventID has been deprecated. Use System.Diagnostics.EventLogEntry.InstanceId instead.")]
        public int EventID
        {
            get
            {
                return IntFrom(dataBuf, bufOffset + FieldOffsets.EVENTID) & 0x3FFFFFFF;
            }
        }

        /// <summary>
        /// The type of entry - Information, Warning, etc.
        /// </summary>
        public EventLogEntryType EntryType
        {
            get
            {
                return (EventLogEntryType)ShortFrom(dataBuf, bufOffset + FieldOffsets.EVENTTYPE);
            }
        }

        /// <summary>
        /// The text of the message for this entry.
        /// </summary>
        [Editor("System.ComponentModel.Design.BinaryEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
                "System.Drawing.Design.UITypeEditor, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
        public string Message
        {
            get
            {
                if (message == null)
                {
                    string? dllNames = GetMessageLibraryNames("EventMessageFile");
                    int msgId = IntFrom(dataBuf, bufOffset + FieldOffsets.EVENTID);
                    string? msg = owner.FormatMessageWrapper(dllNames, (uint)msgId, ReplacementStrings);
                    if (msg == null)
                    {
                        StringBuilder msgBuf = new StringBuilder(SR.Format(SR.MessageNotFormatted, msgId, Source));
                        string[] strings = ReplacementStrings;
                        for (int i = 0; i < strings.Length; i++)
                        {
                            if (i != 0)
                                msgBuf.Append(", ");
                            msgBuf.Append('\'');
                            msgBuf.Append(strings[i]);
                            msgBuf.Append('\'');
                        }

                        msg = msgBuf.ToString();
                    }
                    else
                        msg = ReplaceMessageParameters(msg, ReplacementStrings);

                    message = msg;
                }

                return message;
            }
        }

        /// <summary>
        /// The name of the application that wrote this entry.
        /// </summary>
        public string Source
        {
            get
            {
                StringBuilder buf = new StringBuilder();
                int pos = bufOffset + FieldOffsets.RAWDATA;

                char ch = CharFrom(dataBuf, pos);
                while (ch != '\0')
                {
                    buf.Append(ch);
                    pos += 2;
                    ch = CharFrom(dataBuf, pos);
                }

                return buf.ToString();
            }
        }

        /// <summary>
        /// The application-supplied strings used in the message.
        /// </summary>
        public string[] ReplacementStrings
        {
            get
            {
                string[] strings = new string[ShortFrom(dataBuf, bufOffset + FieldOffsets.NUMSTRINGS)];
                int i = 0;
                int bufpos = bufOffset + IntFrom(dataBuf, bufOffset + FieldOffsets.STRINGOFFSET);
                StringBuilder buf = new StringBuilder();
                while (i < strings.Length)
                {
                    char ch = CharFrom(dataBuf, bufpos);
                    if (ch != '\0')
                        buf.Append(ch);
                    else
                    {
                        strings[i] = buf.ToString();
                        i++;
                        buf.Clear();
                    }

                    bufpos += 2;
                }

                return strings;
            }
        }

        /// <summary>
        /// The full number identifying the message in the event message dll.
        /// </summary>
        public long InstanceId
        {
            get
            {
                return (uint)IntFrom(dataBuf, bufOffset + FieldOffsets.EVENTID);
            }
        }

        /// <summary>
        /// The time at which the application logged this entry.
        /// </summary>
        public DateTime TimeGenerated
        {
            get
            {
                return DateTime.UnixEpoch.AddSeconds(IntFrom(dataBuf, bufOffset + FieldOffsets.TIMEGENERATED)).ToLocalTime();
            }
        }

        /// <summary>
        /// The time at which the system logged this entry to the event log.
        /// </summary>
        public DateTime TimeWritten
        {
            get
            {
                return DateTime.UnixEpoch.AddSeconds(IntFrom(dataBuf, bufOffset + FieldOffsets.TIMEWRITTEN)).ToLocalTime();
            }
        }

        /// <summary>
        /// The username of the account associated with this entry by the writing application.
        /// </summary>
        public string? UserName
        {
            get
            {
                int sidLen = IntFrom(dataBuf, bufOffset + FieldOffsets.USERSIDLENGTH);
                if (sidLen == 0)
                    return null;
                byte[] sid = new byte[sidLen];
                Array.Copy(dataBuf, bufOffset + IntFrom(dataBuf, bufOffset + FieldOffsets.USERSIDOFFSET),
                           sid, 0, sid.Length);

                int userNameLen = 256;
                int domainNameLen = 256;
                unsafe
                {
                    fixed (char* bufUserName = new char[userNameLen])
                    fixed (char* bufDomainName = new char[domainNameLen])
                    {
                        if (Interop.Advapi32.LookupAccountSid(MachineName, sid, bufUserName, ref userNameLen, bufDomainName, ref domainNameLen, out int sidNameUse) != 0)
                        {
                            return new string(bufDomainName) + "\\" + new string(bufUserName);
                        }
                    }
                }

                return string.Empty;
            }
        }

        private static char CharFrom(byte[] buf, int offset)
        {
            return (char)ShortFrom(buf, offset);
        }

        public bool Equals([NotNullWhen(true)] EventLogEntry? otherEntry)
        {
            if (otherEntry == null)
                return false;
            int ourLen = IntFrom(dataBuf, bufOffset + FieldOffsets.LENGTH);
            int theirLen = IntFrom(otherEntry.dataBuf, otherEntry.bufOffset + FieldOffsets.LENGTH);
            if (ourLen != theirLen)
            {
                return false;
            }
            int min = bufOffset;
            int max = bufOffset + ourLen;
            int j = otherEntry.bufOffset;
            for (int i = min; i < max; i++, j++)
                if (dataBuf[i] != otherEntry.dataBuf[j])
                {
                    return false;
                }

            return true;
        }

        private static int IntFrom(byte[] buf, int offset)
        {
            // assumes Little Endian byte order.
            return (unchecked((int)0xFF000000) & (buf[offset + 3] << 24)) | (0xFF0000 & (buf[offset + 2] << 16)) |
            (0xFF00 & (buf[offset + 1] << 8)) | (0xFF & (buf[offset]));
        }

        internal string ReplaceMessageParameters(string msg, string[] insertionStrings)
        {
            int percentIdx = msg.IndexOf('%');
            if (percentIdx < 0)
                return msg;

            int startCopyIdx = 0;
            int msgLength = msg.Length;
            StringBuilder buf = new StringBuilder();
            string? paramDLLNames = GetMessageLibraryNames("ParameterMessageFile");

            while (percentIdx >= 0)
            {
                string? param = null;
                int lasNumIdx = percentIdx + 1;
                while (lasNumIdx < msgLength && char.IsDigit(msg, lasNumIdx))
                    lasNumIdx++;

                uint paramMsgID = 0;

                if (lasNumIdx != percentIdx + 1)
                    uint.TryParse(msg.Substring(percentIdx + 1, lasNumIdx - percentIdx - 1), out paramMsgID);

                if (paramMsgID != 0)
                    param = owner.FormatMessageWrapper(paramDLLNames, paramMsgID, insertionStrings);

                if (param != null)
                {
                    if (percentIdx > startCopyIdx)
                        buf.Append(msg, startCopyIdx, percentIdx - startCopyIdx);    // original chars from msg
                    buf.Append(param);
                    startCopyIdx = lasNumIdx;
                }

                percentIdx = msg.IndexOf('%', percentIdx + 1);
            }

            if (msgLength - startCopyIdx > 0)
                buf.Append(msg, startCopyIdx, msgLength - startCopyIdx);          // last span of original msg
            return buf.ToString();
        }

        private static RegistryKey? GetSourceRegKey(string logName, string source, string machineName)
        {
            RegistryKey? eventKey = null;
            RegistryKey? logKey = null;

            try
            {
                eventKey = EventLog.GetEventLogRegKey(machineName, false);
                logKey = eventKey?.OpenSubKey(logName ?? "Application", /*writable*/false);
                return logKey?.OpenSubKey(source, /*writeable*/false);
            }
            finally
            {
                eventKey?.Close();
                logKey?.Close();
            }
        }

        private string? GetMessageLibraryNames(string libRegKey)
        {
            // get the value stored in the registry
            string? fileName = null;
            RegistryKey? regKey = null;
            try
            {
                regKey = GetSourceRegKey(owner.Log, Source, owner.MachineName);
                if (regKey != null)
                {
                    fileName = (string?)regKey.GetValue(libRegKey);
                }
            }
            finally
            {
                regKey?.Close();
            }

            if (fileName == null)
                return null;
            // convert any absolute paths on a remote machine to use the \\MACHINENAME\DRIVELETTER$ shares
            if (owner.MachineName != ".")
            {
                string[] fileNames = fileName.Split(';');

                StringBuilder result = new StringBuilder();

                for (int i = 0; i < fileNames.Length; i++)
                {
                    if (fileNames[i].Length >= 2 && fileNames[i][1] == ':')
                    {
                        result.Append(@"\\");
                        result.Append(owner.MachineName);
                        result.Append('\\');
                        result.Append(fileNames[i][0]);
                        result.Append('$');
                        result.Append(fileNames[i], 2, fileNames[i].Length - 2);
                        result.Append(';');
                    }
                }

                if (result.Length == 0)
                {
                    return null;
                }
                else
                {
                    return result.ToString(0, result.Length - 1);
                }
            }
            else
            {
                return fileName;
            }
        }

        private static short ShortFrom(byte[] buf, int offset)
        {
            // assumes little Endian byte order.
            return (short)((0xFF00 & (buf[offset + 1] << 8)) | (0xFF & buf[offset]));
        }

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            throw new PlatformNotSupportedException();
        }

        private static class FieldOffsets
        {
            internal const int LENGTH = 0;
            internal const int RESERVED = 4;
            internal const int RECORDNUMBER = 8;
            internal const int TIMEGENERATED = 12;
            internal const int TIMEWRITTEN = 16;
            internal const int EVENTID = 20;
            internal const int EVENTTYPE = 24;
            internal const int NUMSTRINGS = 26;
            internal const int EVENTCATEGORY = 28;
            internal const int RESERVEDFLAGS = 30;
            internal const int CLOSINGRECORDNUMBER = 32;
            internal const int STRINGOFFSET = 36;
            internal const int USERSIDLENGTH = 40;
            internal const int USERSIDOFFSET = 44;
            internal const int DATALENGTH = 48;
            internal const int DATAOFFSET = 52;
            internal const int RAWDATA = 56;
        }
    }
}