2 // System.Diagnostics.Win32EventLog.cs
5 // Gert Driesen <driesen@users.sourceforge.net>
7 // Copyright (C) 2006 Novell, Inc (http://www.novell.com)
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Collections;
32 using System.ComponentModel;
33 using System.Diagnostics;
34 using System.Globalization;
36 using System.Runtime.InteropServices;
39 using Microsoft.Win32;
41 namespace System.Diagnostics
43 internal class Win32EventLog : EventLogImpl
45 private const int MESSAGE_NOT_FOUND = 317;
47 public Win32EventLog (EventLog coreEventLog)
52 public override void BeginInit ()
56 public override void Clear ()
58 IntPtr hEventLog = OpenEventLog ();
60 int ret = PInvoke.ClearEventLog (hEventLog, null);
62 throw new Win32Exception (Marshal.GetLastWin32Error ());
65 CloseEventLog (hEventLog);
69 public override void Close ()
71 // we don't hold any unmanaged resources
74 public override void CreateEventSource (EventSourceCreationData sourceData)
76 using (RegistryKey eventLogKey = GetEventLogKey (sourceData.MachineName, true)) {
77 if (eventLogKey == null)
78 throw new InvalidOperationException ("EventLog registry key is missing.");
80 bool logKeyCreated = false;
81 RegistryKey logKey = null;
83 logKey = eventLogKey.OpenSubKey (sourceData.LogName, true);
85 ValidateCustomerLogName (sourceData.LogName,
86 sourceData.MachineName);
88 logKey = eventLogKey.CreateSubKey (sourceData.LogName);
89 logKey.SetValue ("Sources", new string [] { sourceData.LogName,
91 UpdateLogRegistry (logKey);
93 using (RegistryKey sourceKey = logKey.CreateSubKey (sourceData.LogName)) {
94 UpdateSourceRegistry (sourceKey, sourceData);
100 if (sourceData.LogName != sourceData.Source) {
101 if (!logKeyCreated) {
102 string [] sources = (string []) logKey.GetValue ("Sources");
103 if (sources == null) {
104 logKey.SetValue ("Sources", new string [] { sourceData.LogName,
105 sourceData.Source });
108 for (int i = 0; i < sources.Length; i++) {
109 if (sources [i] == sourceData.Source) {
115 string [] newSources = new string [sources.Length + 1];
116 Array.Copy (sources, 0, newSources, 0, sources.Length);
117 newSources [sources.Length] = sourceData.Source;
118 logKey.SetValue ("Sources", newSources);
122 using (RegistryKey sourceKey = logKey.CreateSubKey (sourceData.Source)) {
123 UpdateSourceRegistry (sourceKey, sourceData);
133 public override void Delete (string logName, string machineName)
135 using (RegistryKey eventLogKey = GetEventLogKey (machineName, true)) {
136 if (eventLogKey == null)
137 throw new InvalidOperationException ("The event log key does not exist.");
139 using (RegistryKey logKey = eventLogKey.OpenSubKey (logName, false)) {
141 throw new InvalidOperationException (string.Format (
142 CultureInfo.InvariantCulture, "Event Log '{0}'"
143 + " does not exist on computer '{1}'.", logName,
146 // remove all eventlog entries for specified log
147 CoreEventLog.Clear ();
149 // remove file holding event log entries
150 string file = (string) logKey.GetValue ("File");
154 } catch (Exception) {
155 // .NET seems to ignore failures here
160 eventLogKey.DeleteSubKeyTree (logName);
164 public override void DeleteEventSource (string source, string machineName)
166 using (RegistryKey logKey = FindLogKeyBySource (source, machineName, true)) {
167 if (logKey == null) {
168 throw new ArgumentException (string.Format (
169 CultureInfo.InvariantCulture, "The source '{0}' is not"
170 + " registered on computer '{1}'.", source, machineName));
173 logKey.DeleteSubKeyTree (source);
175 string [] sources = (string []) logKey.GetValue ("Sources");
176 if (sources != null) {
177 ArrayList temp = new ArrayList ();
178 for (int i = 0; i < sources.Length; i++)
179 if (sources [i] != source)
180 temp.Add (sources [i]);
181 string [] newSources = new string [temp.Count];
182 temp.CopyTo (newSources, 0);
183 logKey.SetValue ("Sources", newSources);
188 public override void Dispose (bool disposing)
193 public override void EndInit ()
197 public override bool Exists (string logName, string machineName)
199 using (RegistryKey logKey = FindLogKeyByName (logName, machineName, false)) {
200 return (logKey != null);
204 [MonoTODO] // ParameterResourceFile ??
205 protected override string FormatMessage (string source, uint messageID, string [] replacementStrings)
207 string formattedMessage = null;
209 string [] msgResDlls = GetMessageResourceDlls (source, "EventMessageFile");
210 for (int i = 0; i < msgResDlls.Length; i++) {
211 formattedMessage = FetchMessage (msgResDlls [i],
212 messageID, replacementStrings);
213 if (formattedMessage != null)
217 return formattedMessage != null ? formattedMessage : string.Join (
218 ", ", replacementStrings);
221 private string FormatCategory (string source, int category)
223 string formattedCategory = null;
225 string [] msgResDlls = GetMessageResourceDlls (source, "CategoryMessageFile");
226 for (int i = 0; i < msgResDlls.Length; i++) {
227 formattedCategory = FetchMessage (msgResDlls [i],
228 (uint) category, new string [0]);
229 if (formattedCategory != null)
233 return formattedCategory != null ? formattedCategory : "(" +
234 category.ToString (CultureInfo.InvariantCulture) + ")";
237 protected override int GetEntryCount ()
239 IntPtr hEventLog = OpenEventLog ();
242 int retVal = PInvoke.GetNumberOfEventLogRecords (hEventLog, ref entryCount);
244 throw new Win32Exception (Marshal.GetLastWin32Error ());
248 CloseEventLog (hEventLog);
252 protected override EventLogEntry GetEntry (int index)
254 // http://msdn.microsoft.com/library/en-us/eventlog/base/readeventlog.asp
255 // http://msdn.microsoft.com/library/en-us/eventlog/base/eventlogrecord_str.asp
256 // http://www.whitehats.ca/main/members/Malik/malik_eventlogs/malik_eventlogs.html
258 index += OldestEventLogEntry;
260 IntPtr hEventLog = OpenEventLog ();
263 int minBufferNeeded = 0;
265 byte [] buffer = new byte [0x7ffff]; // according to MSDN this is the max size of the buffer
266 int length = buffer.Length;
268 int ret = PInvoke.ReadEventLog (hEventLog, ReadFlags.Seek |
269 ReadFlags.ForwardsRead, index, buffer, length,
270 ref bytesRead, ref minBufferNeeded);
272 throw new Win32Exception (Marshal.GetLastWin32Error ());
275 MemoryStream ms = new MemoryStream (buffer);
276 BinaryReader br = new BinaryReader (ms);
278 // skip first 8 bytes
281 int recordNumber = br.ReadInt32 (); // 8
283 int timeGeneratedSeconds = br.ReadInt32 (); // 12
284 int timeWrittenSeconds = br.ReadInt32 (); // 16
285 uint instanceID = br.ReadUInt32 ();
286 int eventID = EventLog.GetEventID (instanceID);
287 short eventType = br.ReadInt16 (); // 24
288 short numStrings = br.ReadInt16 (); ; // 26
289 short categoryNumber = br.ReadInt16 (); ; // 28
290 // skip reservedFlags
291 br.ReadInt16 (); // 30
292 // skip closingRecordNumber
293 br.ReadInt32 (); // 32
294 int stringOffset = br.ReadInt32 (); // 36
295 int userSidLength = br.ReadInt32 (); // 40
296 int userSidOffset = br.ReadInt32 (); // 44
297 int dataLength = br.ReadInt32 (); // 48
298 int dataOffset = br.ReadInt32 (); // 52
300 DateTime timeGenerated = new DateTime (1970, 1, 1).AddSeconds (
301 timeGeneratedSeconds);
303 DateTime timeWritten = new DateTime (1970, 1, 1).AddSeconds (
306 StringBuilder sb = new StringBuilder ();
307 while (br.PeekChar () != '\0')
308 sb.Append (br.ReadChar ());
309 br.ReadChar (); // skip the null-char
311 string sourceName = sb.ToString ();
314 while (br.PeekChar () != '\0')
315 sb.Append (br.ReadChar ());
316 br.ReadChar (); // skip the null-char
317 string machineName = sb.ToString ();
320 while (br.PeekChar () != '\0')
321 sb.Append (br.ReadChar ());
322 br.ReadChar (); // skip the null-char
324 string userName = null;
325 if (userSidLength != 0) {
327 ms.Position = userSidOffset;
328 byte [] sid = br.ReadBytes (userSidLength);
329 userName = LookupAccountSid (machineName, sid);
332 ms.Position = stringOffset;
333 string [] replacementStrings = new string [numStrings];
334 for (int i = 0; i < numStrings; i++) {
336 while (br.PeekChar () != '\0')
337 sb.Append (br.ReadChar ());
338 br.ReadChar (); // skip the null-char
339 replacementStrings [i] = sb.ToString ();
342 byte [] data = new byte [dataLength];
343 ms.Position = dataOffset;
344 br.Read (data, 0, dataLength);
346 // TODO: lazy fetch ??
347 string message = this.FormatMessage (sourceName, instanceID, replacementStrings);
348 string category = FormatCategory (sourceName, categoryNumber);
350 return new EventLogEntry (category, (short) categoryNumber, recordNumber,
351 eventID, sourceName, message, userName, machineName,
352 (EventLogEntryType) eventType, timeGenerated, timeWritten,
353 data, replacementStrings, instanceID);
355 CloseEventLog (hEventLog);
360 protected override string GetLogDisplayName ()
362 return CoreEventLog.Log;
365 protected override string [] GetLogNames (string machineName)
367 using (RegistryKey eventLogKey = GetEventLogKey (machineName, true)) {
368 if (eventLogKey == null)
369 return new string [0];
371 return eventLogKey.GetSubKeyNames ();
375 public override string LogNameFromSourceName (string source, string machineName)
377 using (RegistryKey logKey = FindLogKeyBySource (source, machineName, false)) {
381 return GetLogName (logKey);
385 public override bool SourceExists (string source, string machineName)
387 RegistryKey logKey = FindLogKeyBySource (source, machineName, false);
388 if (logKey != null) {
395 public override void WriteEntry (string [] replacementStrings, EventLogEntryType type, uint instanceID, short category, byte [] rawData)
397 IntPtr hEventLog = RegisterEventSource ();
399 int ret = PInvoke.ReportEvent (hEventLog, (ushort) type,
400 (ushort) category, instanceID, IntPtr.Zero,
401 (ushort) replacementStrings.Length,
402 (uint) rawData.Length, replacementStrings, rawData);
404 throw new Win32Exception (Marshal.GetLastWin32Error ());
407 DeregisterEventSource (hEventLog);
411 private static void UpdateLogRegistry (RegistryKey logKey)
413 // TODO: write other Log values:
416 // - AutoBackupLogFiles
418 if (logKey.GetValue ("File") == null) {
419 string logName = GetLogName (logKey);
421 if (logName.Length > 8) {
422 file = logName.Substring (0, 8) + ".evt";
424 file = logName + ".evt";
426 string configPath = Path.Combine (Environment.GetFolderPath (
427 Environment.SpecialFolder.System), "config");
428 logKey.SetValue ("File", Path.Combine (configPath, file));
433 private static void UpdateSourceRegistry (RegistryKey sourceKey, EventSourceCreationData data)
435 if (data.CategoryCount > 0)
436 sourceKey.SetValue ("CategoryCount", data.CategoryCount);
438 if (data.CategoryResourceFile != null && data.CategoryResourceFile.Length > 0)
439 sourceKey.SetValue ("CategoryMessageFile", data.CategoryResourceFile);
441 if (data.MessageResourceFile != null && data.MessageResourceFile.Length > 0) {
442 sourceKey.SetValue ("EventMessageFile", data.MessageResourceFile);
444 // FIXME: write default once we have approval for shipping EventLogMessages.dll
447 if (data.ParameterResourceFile != null && data.ParameterResourceFile.Length > 0)
448 sourceKey.SetValue ("ParameterMessageFile", data.ParameterResourceFile);
451 private static string GetLogName (RegistryKey logKey)
453 string logName = logKey.Name;
454 return logName.Substring (logName.LastIndexOf ("\\") + 1);
457 [MonoTODO ("Support remote machines")]
458 private static RegistryKey GetEventLogKey (string machineName, bool writable)
460 return Registry.LocalMachine.OpenSubKey (@"SYSTEM\CurrentControlSet\Services\EventLog", writable);
463 private static RegistryKey FindSourceKeyByName (string source, string machineName, bool writable)
465 if (source == null || source.Length == 0)
468 RegistryKey eventLogKey = null;
470 eventLogKey = GetEventLogKey (machineName, writable);
471 if (eventLogKey == null)
474 string [] subKeys = eventLogKey.GetSubKeyNames ();
475 for (int i = 0; i < subKeys.Length; i++) {
476 using (RegistryKey logKey = eventLogKey.OpenSubKey (subKeys [i], writable)) {
480 RegistryKey sourceKey = logKey.OpenSubKey (source, writable);
481 if (sourceKey != null)
487 if (eventLogKey != null)
488 eventLogKey.Close ();
492 private static RegistryKey FindLogKeyByName (string logName, string machineName, bool writable)
494 using (RegistryKey eventLogKey = GetEventLogKey (machineName, writable)) {
495 if (eventLogKey == null) {
499 return eventLogKey.OpenSubKey (logName, writable);
503 private static RegistryKey FindLogKeyBySource (string source, string machineName, bool writable)
505 if (source == null || source.Length == 0)
508 RegistryKey eventLogKey = null;
510 eventLogKey = GetEventLogKey (machineName, writable);
511 if (eventLogKey == null)
514 string [] subKeys = eventLogKey.GetSubKeyNames ();
515 for (int i = 0; i < subKeys.Length; i++) {
516 RegistryKey sourceKey = null;
518 RegistryKey logKey = eventLogKey.OpenSubKey (subKeys [i], writable);
519 if (logKey != null) {
520 sourceKey = logKey.OpenSubKey (source, writable);
521 if (sourceKey != null)
525 if (sourceKey != null)
531 if (eventLogKey != null)
532 eventLogKey.Close ();
536 private int OldestEventLogEntry {
538 IntPtr hEventLog = OpenEventLog ();
540 int oldestEventLogEntry = 0;
541 int ret = PInvoke.GetOldestEventLogRecord (hEventLog, ref oldestEventLogEntry);
543 throw new Win32Exception (Marshal.GetLastWin32Error ());
545 return oldestEventLogEntry;
547 CloseEventLog (hEventLog);
552 private void CloseEventLog (IntPtr hEventLog)
554 int ret = PInvoke.CloseEventLog (hEventLog);
556 throw new Win32Exception (Marshal.GetLastWin32Error ());
560 private void DeregisterEventSource (IntPtr hEventLog)
562 int ret = PInvoke.DeregisterEventSource (hEventLog);
564 throw new Win32Exception (Marshal.GetLastWin32Error ());
568 private static string LookupAccountSid (string machineName, byte [] sid)
570 // http://www.pinvoke.net/default.aspx/advapi32/LookupAccountSid.html
571 // http://msdn.microsoft.com/library/en-us/secauthz/security/lookupaccountsid.asp
573 StringBuilder name = new StringBuilder ();
574 uint cchName = (uint) name.Capacity;
575 StringBuilder referencedDomainName = new StringBuilder ();
576 uint cchReferencedDomainName = (uint) referencedDomainName.Capacity;
579 string accountName = null;
581 while (accountName == null) {
582 bool retOk = PInvoke.LookupAccountSid (machineName, sid, name, ref cchName,
583 referencedDomainName, ref cchReferencedDomainName,
586 int err = Marshal.GetLastWin32Error ();
587 if (err == PInvoke.ERROR_INSUFFICIENT_BUFFER) {
588 name.EnsureCapacity ((int) cchName);
589 referencedDomainName.EnsureCapacity ((int) cchReferencedDomainName);
591 // TODO: write warning ?
592 accountName = string.Empty;
595 accountName = string.Format ("{0}\\{1}", referencedDomainName.ToString (),
602 private static string FetchMessage (string msgDll, uint messageID, string [] replacementStrings)
604 // http://msdn.microsoft.com/library/en-us/debug/base/formatmessage.asp
605 // http://msdn.microsoft.com/msdnmag/issues/02/08/CQA/
606 // http://msdn.microsoft.com/netframework/programming/netcf/cffaq/default.aspx
608 IntPtr msgDllHandle = PInvoke.LoadLibraryEx (msgDll, IntPtr.Zero,
609 LoadFlags.LibraryAsDataFile);
610 if (msgDllHandle == IntPtr.Zero)
611 // TODO: write warning
614 IntPtr lpMsgBuf = IntPtr.Zero;
615 IntPtr [] arguments = new IntPtr [replacementStrings.Length];
618 for (int i = 0; i < replacementStrings.Length; i++) {
619 arguments [i] = Marshal.StringToHGlobalAuto (
620 replacementStrings [i]);
623 int ret = PInvoke.FormatMessage (FormatMessageFlags.ArgumentArray |
624 FormatMessageFlags.FromHModule | FormatMessageFlags.AllocateBuffer,
625 msgDllHandle, messageID, 0, ref lpMsgBuf, 0, arguments);
627 string sRet = Marshal.PtrToStringAuto (lpMsgBuf);
628 lpMsgBuf = PInvoke.LocalFree (lpMsgBuf);
629 // remove trailing whitespace (CRLF)
630 return sRet.TrimEnd (null);
632 int err = Marshal.GetLastWin32Error ();
633 if (err == MESSAGE_NOT_FOUND) {
634 // do not consider this a failure (or even warning) as
635 // multiple message resource DLLs may have been configured
636 // and as such we just need to try the next library if
637 // the current one does not contain a message for this
640 // TODO: report warning
644 // release unmanaged memory allocated for replacement strings
645 for (int i = 0; i < arguments.Length; i++) {
646 IntPtr argument = arguments [i];
647 if (argument != IntPtr.Zero)
648 Marshal.FreeHGlobal (argument);
651 PInvoke.FreeLibrary (msgDllHandle);
656 private string [] GetMessageResourceDlls (string source, string valueName)
658 // Some event sources (such as Userenv) have multiple message
659 // resource DLLs, delimited by a semicolon.
661 RegistryKey sourceKey = FindSourceKeyByName (source,
662 CoreEventLog.MachineName, false);
663 if (sourceKey != null) {
664 string value = sourceKey.GetValue (valueName) as string;
666 string [] msgResDlls = value.Split (';');
670 return new string [0];
673 private IntPtr OpenEventLog ()
675 string logName = CoreEventLog.GetLogName ();
676 IntPtr hEventLog = PInvoke.OpenEventLog (CoreEventLog.MachineName,
678 if (hEventLog == IntPtr.Zero) {
679 throw new InvalidOperationException (string.Format (
680 CultureInfo.InvariantCulture, "Event Log '{0}' on computer"
681 + " '{1}' cannot be opened.", logName, CoreEventLog.MachineName),
682 new Win32Exception ());
687 private IntPtr RegisterEventSource ()
689 IntPtr hEventLog = PInvoke.RegisterEventSource (
690 CoreEventLog.MachineName, CoreEventLog.Source);
691 if (hEventLog == IntPtr.Zero) {
692 throw new InvalidOperationException (string.Format (
693 CultureInfo.InvariantCulture, "Event source '{0}' on computer"
694 + " '{1}' cannot be opened.", CoreEventLog.Source,
695 CoreEventLog.MachineName), new Win32Exception ());
700 private class PInvoke
702 [DllImport ("advapi32.dll", SetLastError=true)]
703 public static extern int ClearEventLog (IntPtr hEventLog, string lpBackupFileName);
705 [DllImport ("advapi32.dll", SetLastError=true)]
706 public static extern int CloseEventLog (IntPtr hEventLog);
708 [DllImport ("advapi32.dll", SetLastError=true)]
709 public static extern int DeregisterEventSource (IntPtr hEventLog);
711 [DllImport ("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
712 public static extern int FormatMessage (FormatMessageFlags dwFlags, IntPtr lpSource, uint dwMessageId, int dwLanguageId, ref IntPtr lpBuffer, int nSize, IntPtr [] arguments);
714 [DllImport ("kernel32.dll", SetLastError=true)]
715 public static extern bool FreeLibrary (IntPtr hModule);
717 [DllImport ("advapi32.dll", SetLastError=true)]
718 public static extern int GetNumberOfEventLogRecords (IntPtr hEventLog, ref int NumberOfRecords);
720 [DllImport ("advapi32.dll", SetLastError=true)]
721 public static extern int GetOldestEventLogRecord (IntPtr hEventLog, ref int OldestRecord);
723 [DllImport ("kernel32.dll", SetLastError=true)]
724 public static extern IntPtr LoadLibraryEx (string lpFileName, IntPtr hFile, LoadFlags dwFlags);
726 [DllImport ("kernel32.dll", SetLastError=true)]
727 public static extern IntPtr LocalFree (IntPtr hMem);
729 [DllImport ("advapi32.dll", SetLastError=true)]
730 public static extern bool LookupAccountSid (
732 [MarshalAs (UnmanagedType.LPArray)] byte [] Sid,
733 StringBuilder lpName,
735 StringBuilder ReferencedDomainName,
736 ref uint cchReferencedDomainName,
737 out SidNameUse peUse);
739 [DllImport ("advapi32.dll", SetLastError=true)]
740 public static extern IntPtr OpenEventLog (string machineName, string logName);
742 [DllImport ("advapi32.dll", SetLastError=true)]
743 public static extern IntPtr RegisterEventSource (string machineName, string sourceName);
745 [DllImport ("Advapi32.dll", SetLastError=true)]
746 public static extern int ReportEvent (IntPtr hHandle, ushort wType,
747 ushort wCategory, uint dwEventID, IntPtr sid, ushort wNumStrings,
748 uint dwDataSize, string [] lpStrings, byte [] lpRawData);
750 [DllImport ("advapi32.dll", SetLastError = true)]
751 public static extern int ReadEventLog (IntPtr hEventLog, ReadFlags dwReadFlags, int dwRecordOffset, byte [] buffer, int nNumberOfBytesToRead, ref int pnBytesRead, ref int pnMinNumberOfBytesNeeded);
753 public const int ERROR_INSUFFICIENT_BUFFER = 122;
756 private enum ReadFlags
760 ForwardsRead = 0x004,
761 BackwardsRead = 0x008
764 private enum LoadFlags: uint
766 LibraryAsDataFile = 0x002
770 private enum FormatMessageFlags
772 AllocateBuffer = 0x100,
773 IgnoreInserts = 0x200,
774 FromHModule = 0x0800,
776 ArgumentArray = 0x2000
779 private enum SidNameUse
794 // http://msdn.microsoft.com/library/en-us/eventlog/base/eventlogrecord_str.asp:
796 // struct EVENTLOGRECORD {
800 // int TimeGenerated;
805 // short EventCategory;
806 // short ReservedFlags;
807 // int ClosingRecordNumber;
809 // int UserSidLength;
810 // int UserSidOffset;
815 // http://www.whitehats.ca/main/members/Malik/malik_eventlogs/malik_eventlogs.html