//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Diagnostics { using System.Text; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.ComponentModel; using System.Diagnostics; using Microsoft.Win32; using Microsoft.Win32.SafeHandles; using System; using System.Security; using System.Security.Permissions; using System.IO; using System.Globalization; using System.Runtime.Versioning; /// /// /// /// encapsulates a single record in the NT event log. /// /// [ ToolboxItem(false), DesignTimeVisible(false), Serializable, ] public sealed class EventLogEntry : Component, ISerializable { internal byte[] dataBuf; internal int bufOffset; private EventLogInternal owner; private string category; private string message; // make sure only others in this package can create us internal EventLogEntry(byte[] buf, int offset, EventLogInternal log) { this.dataBuf = buf; this.bufOffset = offset; this.owner = log; GC.SuppressFinalize(this); } /// /// /// private EventLogEntry(SerializationInfo info, StreamingContext context) { dataBuf = (byte[])info.GetValue("DataBuffer", typeof(byte[])); string logName = info.GetString("LogName"); string machineName = info.GetString("MachineName"); owner = new EventLogInternal(logName, machineName, ""); GC.SuppressFinalize(this); } /// /// /// Gets the name of the computer on which this entry was generated. /// /// /// [MonitoringDescription(SR.LogEntryMachineName)] 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(); } } /// /// /// Gets the binary data associated with the entry. /// /// /// [ MonitoringDescription(SR.LogEntryData) ] 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; } } /* /// /// /// Copies the binary data in the member into an /// array. /// /// /// /// /// An array of type . /// /// /// public Byte[] getDataBytes() { Byte[] data = new Byte[rec.dataLength]; for (int i = 0; i < data.Length; i++) data[i] = new Byte(rec.buf[i]); return data; } */ /// /// /// Gets the index of this entry in the event /// log. /// /// [MonitoringDescription(SR.LogEntryIndex)] public int Index { get { return IntFrom(dataBuf, bufOffset + FieldOffsets.RECORDNUMBER); } } /// /// /// Gets the text associated with the for this entry. /// /// /// [MonitoringDescription(SR.LogEntryCategory)] 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; } } /// /// /// Gets the application-specific category number for this entry /// /// /// [ MonitoringDescription(SR.LogEntryCategoryNumber) ] public short CategoryNumber { get { return ShortFrom(dataBuf, bufOffset + FieldOffsets.EVENTCATEGORY); } } /// /// /// Gets the application-specific event indentifier of this entry. /// /// /// [ MonitoringDescription(SR.LogEntryEventID), Obsolete("This property has been deprecated. Please use System.Diagnostics.EventLogEntry.InstanceId instead. http://go.microsoft.com/fwlink/?linkid=14202") ] public int EventID { get { // Apparently the top 2 bits of this number are not // always 0. Strip them so the number looks nice to the user. // The problem is, if the user were to want to call FormatMessage(), // they'd need these two bits. return IntFrom(dataBuf, bufOffset + FieldOffsets.EVENTID) & 0x3FFFFFFF; } } /// /// /// /// Gets the type /// of this entry. /// /// /// [MonitoringDescription(SR.LogEntryEntryType)] public EventLogEntryType EntryType { get { return(EventLogEntryType) ShortFrom(dataBuf, bufOffset + FieldOffsets.EVENTTYPE); } } /// /// /// Gets the localized message corresponding to this event entry. /// /// /// [ MonitoringDescription(SR.LogEntryMessage), Editor("System.ComponentModel.Design.BinaryEditor, " + AssemblyRef.SystemDesign, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing) ] 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.GetString(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; } } /// /// /// Gets the name of the application that generated this event. /// /// [MonitoringDescription(SR.LogEntrySource)] 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(); } } /// /// /// Gets the replacement strings /// associated with the entry. /// /// /// [MonitoringDescription(SR.LogEntryReplacementStrings)] 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 = new StringBuilder(); } bufpos += 2; } return strings; } } [ MonitoringDescription(SR.LogEntryResourceId), ComVisible(false) ] public Int64 InstanceId { get { return (UInt32)IntFrom(dataBuf, bufOffset + FieldOffsets.EVENTID); } } #if false internal string StringsBuffer { get { StringBuilder buf = new StringBuilder(); int bufpos = bufOffset + IntFrom(dataBuf, bufOffset + FieldOffsets.STRINGOFFSET); int i = 0; int numStrings = ShortFrom(dataBuf, bufOffset + FieldOffsets.NUMSTRINGS); while (i < numStrings) { char ch = CharFrom(dataBuf, bufpos); buf.Append(ch); bufpos += 2; if (ch == '\0') i++; } return buf.ToString(); } } #endif /// /// /// Gets the time at which this event was generated, in local time. /// /// /// [MonitoringDescription(SR.LogEntryTimeGenerated)] public DateTime TimeGenerated { get { return beginningOfTime.AddSeconds(IntFrom(dataBuf, bufOffset + FieldOffsets.TIMEGENERATED)).ToLocalTime(); } } /// /// /// Gets /// the time at which this event was written to the log, in local time. /// /// /// [MonitoringDescription(SR.LogEntryTimeWritten)] public DateTime TimeWritten { get { return beginningOfTime.AddSeconds(IntFrom(dataBuf, bufOffset + FieldOffsets.TIMEWRITTEN)).ToLocalTime(); } } /// /// /// Gets the name /// of the user responsible for this event. /// /// [MonitoringDescription(SR.LogEntryUserName)] 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; int sidNameUse = 0; StringBuilder bufUserName = new StringBuilder(userNameLen); StringBuilder bufDomainName = new StringBuilder(domainNameLen); StringBuilder retUserName = new StringBuilder(); if(UnsafeNativeMethods.LookupAccountSid(MachineName, sid, bufUserName, ref userNameLen, bufDomainName, ref domainNameLen, ref sidNameUse) != 0) { retUserName.Append(bufDomainName.ToString()); retUserName.Append("\\"); retUserName.Append(bufUserName.ToString()); } return retUserName.ToString(); } } private char CharFrom(byte[] buf, int offset) { return(char) ShortFrom(buf, offset); } /// /// /// Performs a comparison between two event log entries. /// /// /// public bool Equals(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 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])); } // Replacing parameters '%n' in formated message using 'ParameterMessageFile' registry key. internal string ReplaceMessageParameters( String msg, string[] insertionStrings ) { int percentIdx = msg.IndexOf('%'); if ( percentIdx < 0 ) return msg; // no '%' at all int startCopyIdx = 0; // start idx of last [....] msg chars to copy int msgLength = msg.Length; StringBuilder buf = new StringBuilder(); string paramDLLNames = GetMessageLibraryNames("ParameterMessageFile"); while ( percentIdx >= 0 ) { string param = null; // Convert numeric string after '%' to paramMsgID number. int lasNumIdx = percentIdx + 1; while ( lasNumIdx < msgLength && Char.IsDigit(msg, lasNumIdx) ) lasNumIdx++; uint paramMsgID = 0; // If we can't parse it, leave the paramMsgID as zero. We'll skip the replacement and just put // the %xxxx into the final message. if (lasNumIdx != percentIdx + 1 ) UInt32.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(); } [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] private static RegistryKey GetSourceRegKey(string logName, string source, string machineName) { RegistryKey eventKey = null; RegistryKey logKey = null; try { eventKey = EventLog.GetEventLogRegKey(machineName, false); if (eventKey == null) return null; if (logName == null) logKey = eventKey.OpenSubKey("Application", /*writable*/false); else logKey = eventKey.OpenSubKey(logName, /*writable*/false); if (logKey == null) return null; return logKey.OpenSubKey(source, /*writable*/false); } finally { if (eventKey != null) eventKey.Close(); if (logKey != null) logKey.Close(); } } // ------------------------------------------------------------------------------ // Returns DLL names list. // libRegKey can be: "EventMessageFile", "CategoryMessageFile", "ParameterMessageFile" [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] 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 { if (regKey != null) regKey.Close(); } if (fileName == null) return null; // convert any absolute paths on a remote machine to use the \\MACHINENAME\DRIVELETTER$ shares // so we pick up message dlls from the remote machine. 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); // Chop of last ";" } } else { return fileName; } } private short ShortFrom(byte[] buf, int offset) { // assumes little Endian byte order. return(short) ((0xFF00 & (buf[offset+1] << 8)) | (0xFF & buf[offset])); } /// /// /// /// Saves an entry as a stream of data. /// /// void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { int len = IntFrom(dataBuf, bufOffset + FieldOffsets.LENGTH); byte[] buf = new byte[len]; Array.Copy(dataBuf, bufOffset, buf, 0, len); info.AddValue("DataBuffer", buf, typeof(byte[])); info.AddValue("LogName", owner.Log); info.AddValue("MachineName", owner.MachineName); } /// /// Stores the offsets from the beginning of the record to fields within the record. /// private static class FieldOffsets { /** int */ internal const int LENGTH = 0; /** int */ internal const int RESERVED = 4; /** int */ internal const int RECORDNUMBER = 8; /** int */ internal const int TIMEGENERATED = 12; /** int */ internal const int TIMEWRITTEN = 16; /** int */ internal const int EVENTID = 20; /** short */ internal const int EVENTTYPE = 24; /** short */ internal const int NUMSTRINGS = 26; /** short */ internal const int EVENTCATEGORY = 28; /** short */ internal const int RESERVEDFLAGS = 30; /** int */ internal const int CLOSINGRECORDNUMBER = 32; /** int */ internal const int STRINGOFFSET = 36; /** int */ internal const int USERSIDLENGTH = 40; /** int */ internal const int USERSIDOFFSET = 44; /** int */ internal const int DATALENGTH = 48; /** int */ internal const int DATAOFFSET = 52; /** bytes */ internal const int RAWDATA = 56; } // times in the struct are # seconds from midnight 1/1/1970. private static readonly DateTime beginningOfTime = new DateTime(1970, 1, 1, 0, 0, 0); // offsets in the struct are specified from the beginning, but we have to reference // them from the beginning of the array. This is the size of the members before that. private const int OFFSETFIXUP = 4+4+4+4+4+4+2+2+2+2+4+4+4+4+4+4; } }