File: services\monitoring\system\diagnosticts\EventLog.cs
Project: ndp\fx\src\System.csproj (System)
//------------------------------------------------------------------------------
// <copyright file="EventLog.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
//#define RETRY_ON_ALL_ERRORS
 
namespace System.Diagnostics {
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading;
    using System.Runtime.InteropServices;
    using System.ComponentModel;
    using System.Diagnostics;
    using System;
    using Microsoft.Win32;
    using Microsoft.Win32.SafeHandles;
    using System.IO;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Globalization;
    using System.ComponentModel.Design;
    using System.Security;
    using System.Security.Permissions;
    using System.Reflection;
    using System.Runtime.Versioning;
    using System.Runtime.CompilerServices;
    using System.Diagnostics.CodeAnalysis;
 
    /// <devdoc>
    ///    <para>
    ///       Provides interaction with Windows 2000 event logs.
    ///    </para>
    /// </devdoc>
    [
    DefaultEvent("EntryWritten"),
    InstallerType("System.Diagnostics.EventLogInstaller, " + AssemblyRef.SystemConfigurationInstall),
    MonitoringDescription(SR.EventLogDesc)
    ]
    public class EventLog : Component, ISupportInitialize {
 
        private const string EventLogKey = "SYSTEM\\CurrentControlSet\\Services\\EventLog";
        internal const string DllName = "EventLogMessages.dll";
        private const string eventLogMutexName = "netfxeventlog.1.0";
 
        private const int DefaultMaxSize = 512 * 1024;
        private const int DefaultRetention = 7 * SecondsPerDay;
        private const int SecondsPerDay = 60 * 60 * 24;
 
        private EventLogInternal m_underlyingEventLog;
        
        // Whether we need backward compatible OS patch work or not
        private static volatile bool s_CheckedOsVersion; 
        private static volatile bool s_SkipRegPatch;
 
        private static bool SkipRegPatch {
            get {
                if (!s_CheckedOsVersion) {
                    OperatingSystem os = Environment.OSVersion;
                    s_SkipRegPatch = (os.Platform == PlatformID.Win32NT) && (os.Version.Major > 5);
                    s_CheckedOsVersion = true;
                }
                return s_SkipRegPatch;
            }
        }
 
        private static readonly bool s_dontFilterRegKeys = !IsWindowsRS5OrUp() || LocalAppContextSwitches.DisableEventLogRegistryKeysFiltering;
 
        internal static PermissionSet _UnsafeGetAssertPermSet() {
            // SEC_NOTE: All callers should already be guarded by EventLogPermission demand.
            PermissionSet permissionSet = new PermissionSet(PermissionState.None);
 
            // We need RegistryPermission 
            RegistryPermission registryPermission = new RegistryPermission(PermissionState.Unrestricted);
            permissionSet.AddPermission(registryPermission);
 
            // It is not enough to just assert RegistryPermission, for some regkeys
            // we need to assert EnvironmentPermission too
            EnvironmentPermission environmentPermission = new EnvironmentPermission(PermissionState.Unrestricted);
            permissionSet.AddPermission(environmentPermission);
 
            // For remote machine registry access UnmanagdCodePermission is required.
            SecurityPermission securityPermission = new SecurityPermission(SecurityPermissionFlag.UnmanagedCode);
            permissionSet.AddPermission(securityPermission);
 
            return permissionSet;
        }
 
        /// <devdoc>
        ///    <para>
        ///       Initializes a new instance of the <see cref='System.Diagnostics.EventLog'/>
        ///       class.
        ///    </para>
        /// </devdoc>
        public EventLog() : this("", ".", "") {
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public EventLog(string logName) : this(logName, ".", "") {
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public EventLog(string logName, string machineName) : this(logName, machineName, "") {
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public EventLog(string logName, string machineName, string source) {
            m_underlyingEventLog = new EventLogInternal(logName, machineName, source, this);
        }
 
        /// <devdoc>
        ///    <para>
        ///       Gets the contents of the event log.
        ///    </para>
        /// </devdoc>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [MonitoringDescription(SR.LogEntries)]
        public EventLogEntryCollection Entries {
            get {
                return m_underlyingEventLog.Entries;
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///    </para>
        /// </devdoc>
        [Browsable(false)]
        public string LogDisplayName {
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
            get {
                return m_underlyingEventLog.LogDisplayName;
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Gets or sets the name of the log to read from and write to.
        ///    </para>
        /// </devdoc>
        [TypeConverter("System.Diagnostics.Design.LogConverter, " + AssemblyRef.SystemDesign)]
        [ReadOnly(true)]
        [MonitoringDescription(SR.LogLog)]
        [DefaultValue("")]
        [SettingsBindable(true)]
        [SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity", Justification = "Microsoft: Safe, oldLog.machineName doesn't change")]
        [SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Microsoft: By design, see justification above assert")]
        public string Log {
            get {
                return m_underlyingEventLog.Log;
            }
            set {
                EventLogInternal newLog = new EventLogInternal(value, m_underlyingEventLog.MachineName, m_underlyingEventLog.Source, this);
                EventLogInternal oldLog = m_underlyingEventLog;
 
                // EnableRaisingEvents and Close demand Write permission but that permission might be removed upstack
                // previously we didn't call Close() since we were reusing the same object.  We assert the permission here.
                new EventLogPermission(EventLogPermissionAccess.Write, oldLog.machineName).Assert();
                if (oldLog.EnableRaisingEvents) {
                    newLog.onEntryWrittenHandler = oldLog.onEntryWrittenHandler;
                    newLog.EnableRaisingEvents = true;
                }
                m_underlyingEventLog = newLog;
                oldLog.Close();
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Gets or sets the name of the computer on which to read or write events.
        ///    </para>
        /// </devdoc>
        [ReadOnly(true)]
        [MonitoringDescription(SR.LogMachineName)]
        [DefaultValue(".")]
        [SettingsBindable(true)]
        [SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Microsoft: By design, see justification above assert")]
        public string MachineName {
            get {
                return m_underlyingEventLog.MachineName;
            }
            set {
                EventLogInternal newLog = new EventLogInternal(m_underlyingEventLog.logName, value, m_underlyingEventLog.sourceName, this);
                EventLogInternal oldLog = m_underlyingEventLog;
 
                // EnableRaisingEvents and Close demand Write permission but that permission might be removed upstack
                // previously we didn't call Close() since we were reusing the same object.  We assert the permission here.
                new EventLogPermission(EventLogPermissionAccess.Write, oldLog.machineName).Assert();
                if (oldLog.EnableRaisingEvents) {
                    newLog.onEntryWrittenHandler = oldLog.onEntryWrittenHandler;
                    newLog.EnableRaisingEvents = true;
                }
                m_underlyingEventLog = newLog;
                oldLog.Close();
            }
        }
 
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Browsable(false)]
        [ComVisible(false)]
        public long MaximumKilobytes {
            get {
                return m_underlyingEventLog.MaximumKilobytes;
            }
 
            [ResourceExposure(ResourceScope.None)]
            [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
            set {
                m_underlyingEventLog.MaximumKilobytes = value;
            }
        }
 
        [Browsable(false)]
        [ComVisible(false)]
        public OverflowAction OverflowAction {
            get {
                return m_underlyingEventLog.OverflowAction;
            }
        }
 
        [Browsable(false)]
        [ComVisible(false)]
        public int MinimumRetentionDays {
            get {
                return m_underlyingEventLog.MinimumRetentionDays;
            }
        }
 
        // EventLogInternal needs to know if the component is in design mode but
        // the DesignMode property is protected.
        internal bool ComponentDesignMode {
            get {
                return this.DesignMode;
            }
        }
 
        // Expose for EventLogInternal
        internal object ComponentGetService(Type service) {
            return GetService(service);
        }
 
        /// <devdoc>
        /// </devdoc>
        [Browsable(false)]
        [MonitoringDescription(SR.LogMonitoring)]
        [DefaultValue(false)]
        public bool EnableRaisingEvents {
            get {
                return m_underlyingEventLog.EnableRaisingEvents;
            }
            set {
                m_underlyingEventLog.EnableRaisingEvents = value;
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Represents the object used to marshal the event handler
        ///       calls issued as a result of an <see cref='System.Diagnostics.EventLog'/>
        ///       change.
        ///    </para>
        /// </devdoc>
        [Browsable(false)]
        [DefaultValue(null)]
        [MonitoringDescription(SR.LogSynchronizingObject)]
        public ISynchronizeInvoke SynchronizingObject {
        [HostProtection(Synchronization=true)]
            get {
                return m_underlyingEventLog.SynchronizingObject;
            }
 
            set {
                m_underlyingEventLog.SynchronizingObject = value;
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Gets or
        ///       sets the application name (source name) to register and use when writing to the event log.
        ///    </para>
        /// </devdoc>
        [ReadOnly(true)]
        [TypeConverter("System.Diagnostics.Design.StringValueConverter, " + AssemblyRef.SystemDesign)]
        [MonitoringDescription(SR.LogSource)]
        [DefaultValue("")]
        [SettingsBindable(true)]
        [SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity", Justification = "Microsoft: Safe, oldLog.machineName doesn't change")]
        [SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Microsoft: By design, see justification above assert")]
        public string Source {
            get {
                return m_underlyingEventLog.Source;
            }
            set {
                EventLogInternal newLog = new EventLogInternal(m_underlyingEventLog.Log, m_underlyingEventLog.MachineName, CheckAndNormalizeSourceName(value), this);
                EventLogInternal oldLog = m_underlyingEventLog;
 
                // EnableRaisingEvents and Close demand Write permission but that permission might be removed upstack
                // previously we didn't call Close() since we were reusing the same object.  We assert the permission here.
                new EventLogPermission(EventLogPermissionAccess.Write, oldLog.machineName).Assert();
                if (oldLog.EnableRaisingEvents) {
                    newLog.onEntryWrittenHandler = oldLog.onEntryWrittenHandler;
                    newLog.EnableRaisingEvents = true;
                }
                m_underlyingEventLog = newLog;
                oldLog.Close();
            }
        }
 
 
        /// <devdoc>
        ///    <para>
        ///       Occurs when an entry is written to the event log.
        ///    </para>
        /// </devdoc>
        [MonitoringDescription(SR.LogEntryWritten)]
        public event EntryWrittenEventHandler EntryWritten {
            add {
                m_underlyingEventLog.EntryWritten += value;
            }
            remove {
                m_underlyingEventLog.EntryWritten -= value;
            }
        }
 
        /// <devdoc>
        /// </devdoc>
        public void BeginInit() {
            m_underlyingEventLog.BeginInit();
        }
 
        /// <devdoc>
        ///    <para>
        ///       Clears
        ///       the event log by removing all entries from it.
        ///    </para>
        /// </devdoc>
        [ResourceExposure(ResourceScope.Machine)]  // Should anyone ever call this, other than an event log viewer?
        [ResourceConsumption(ResourceScope.Machine)]
        public void Clear() {
            m_underlyingEventLog.Clear();
        }
 
        /// <devdoc>
        ///    <para>
        ///       Closes the event log and releases read and write handles.
        ///    </para>
        /// </devdoc>
        [ResourceExposure(ResourceScope.None)]
        public void Close() {
            m_underlyingEventLog.Close();
        }
 
        /// <devdoc>
        ///    <para> Establishes an application, using the
        ///       specified <see cref='System.Diagnostics.EventLog.Source'/> , as a valid event source for
        ///       writing entries
        ///       to a log on the local computer. This method
        ///       can also be used to create
        ///       a new custom log on the local computer.</para>
        /// </devdoc>
        public static void CreateEventSource(string source, string logName) {
            CreateEventSource(new EventSourceCreationData(source, logName, "."));
        }
 
        /// <devdoc>
        ///    <para>Establishes an application, using the specified
        ///    <see cref='System.Diagnostics.EventLog.Source'/> as a valid event source for writing
        ///       entries to a log on the computer
        ///       specified by <paramref name="machineName"/>. This method can also be used to create a new
        ///       custom log on the given computer.</para>
        /// </devdoc>
        [Obsolete("This method has been deprecated.  Please use System.Diagnostics.EventLog.CreateEventSource(EventSourceCreationData sourceData) instead.  http://go.microsoft.com/fwlink/?linkid=14202")]
        public static void CreateEventSource(string source, string logName, string machineName) {
            CreateEventSource(new EventSourceCreationData(source, logName, machineName));
        }
 
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        public static void CreateEventSource(EventSourceCreationData sourceData) {
            if (sourceData == null)
                throw new ArgumentNullException("sourceData");
 
            string logName = sourceData.LogName;
            string source = sourceData.Source;
            string machineName = sourceData.MachineName;
 
            // verify parameters
            Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "CreateEventSource: Checking arguments");
            if (!SyntaxCheck.CheckMachineName(machineName)) {
                throw new ArgumentException(SR.GetString(SR.InvalidParameter, "machineName", machineName));
            }
            if (logName == null || logName.Length==0)
                logName = "Application";
            if (!ValidLogName(logName, false))
                throw new ArgumentException(SR.GetString(SR.BadLogName));
            if (source == null || source.Length==0)
                throw new ArgumentException(SR.GetString(SR.MissingParameter, "source"));
            if (source.Length + EventLogKey.Length > 254)
                throw new ArgumentException(SR.GetString(SR.ParameterTooLong, "source", 254 - EventLogKey.Length));
 
            EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, machineName);
            permission.Demand();
 
            Mutex mutex = null;
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                SharedUtils.EnterMutex(eventLogMutexName, ref mutex);
                Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "CreateEventSource: Calling SourceExists");
                if (SourceExists(source, machineName, true)) {
                    Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "CreateEventSource: SourceExists returned true");
                    // don't let them register a source if it already exists
                    // this makes more sense than just doing it anyway, because the source might
                    // be registered under a different log name, and we don't want to create
                    // duplicates.
                    if (".".Equals(machineName))
                        throw new ArgumentException(SR.GetString(SR.LocalSourceAlreadyExists, source));
                    else
                        throw new ArgumentException(SR.GetString(SR.SourceAlreadyExists, source, machineName));
                }
 
                Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "CreateEventSource: Getting DllPath");
 
                //SECREVIEW: Note that EventLog permission is demanded above.
                PermissionSet permissionSet = _UnsafeGetAssertPermSet();
                permissionSet.Assert();
                
                RegistryKey baseKey = null;
                RegistryKey eventKey = null;
                RegistryKey logKey = null;
                RegistryKey sourceLogKey = null;
                RegistryKey sourceKey = null;
                try {
                    Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "CreateEventSource: Getting local machine regkey");
                    if (machineName == ".")
                        baseKey = Registry.LocalMachine;
                    else
                        baseKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, machineName);
 
                    eventKey = baseKey.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\EventLog", true);
                    if (eventKey == null) {
                        if (!".".Equals(machineName))
                            throw new InvalidOperationException(SR.GetString(SR.RegKeyMissing, "SYSTEM\\CurrentControlSet\\Services\\EventLog", logName, source, machineName));
                        else
                            throw new InvalidOperationException(SR.GetString(SR.LocalRegKeyMissing, "SYSTEM\\CurrentControlSet\\Services\\EventLog", logName, source));
                    }
 
                    // The event log system only treats the first 8 characters of the log name as
                    // significant. If they're creating a new log, but that new log has the same
                    // first 8 characters as another log, the system will think they're the same.
                    // Throw an exception to let them know.
                    logKey = eventKey.OpenSubKey(logName, true);
                    if (logKey == null && logName.Length >= 8) {
 
                        // check for Windows embedded logs file names
                        string logNameFirst8 = logName.Substring(0,8);
                        if ( string.Compare(logNameFirst8,"AppEvent",StringComparison.OrdinalIgnoreCase) ==0  ||
                             string.Compare(logNameFirst8,"SecEvent",StringComparison.OrdinalIgnoreCase) ==0  ||
                             string.Compare(logNameFirst8,"SysEvent",StringComparison.OrdinalIgnoreCase) ==0 )
                            throw new ArgumentException(SR.GetString(SR.InvalidCustomerLogName, logName));
 
                        string sameLogName = FindSame8FirstCharsLog(eventKey, logName);
                        if ( sameLogName != null )
                            throw new ArgumentException(SR.GetString(SR.DuplicateLogName, logName, sameLogName));
                    }
 
                    bool createLogKey = (logKey == null);
                    if (createLogKey) {
                        if (SourceExists(logName, machineName, true)) {
                            // don't let them register a log name that already
                            // exists as source name, a source with the same
                            // name as the log will have to be created by default
                            if (".".Equals(machineName))
                                throw new ArgumentException(SR.GetString(SR.LocalLogAlreadyExistsAsSource, logName));
                            else
                                throw new ArgumentException(SR.GetString(SR.LogAlreadyExistsAsSource, logName, machineName));
                        }
 
                        logKey = eventKey.CreateSubKey(logName);
                                                                        
                        // NOTE: We shouldn't set "Sources" explicitly, the OS will automatically set it.
                        // The EventLog service doesn't use it for anything it is just an helping hand for event viewer filters.
                        // Writing this value explicitly might confuse the service as it might perceive it as a change and 
                        // start initializing again
 
                        if (!SkipRegPatch) 
                            logKey.SetValue("Sources", new string[] {logName, source}, RegistryValueKind.MultiString);
 
                        SetSpecialLogRegValues(logKey, logName);
 
                        // A source with the same name as the log has to be created
                        // by default. It is the behavior expected by EventLog API.
                        sourceLogKey = logKey.CreateSubKey(logName);
                        SetSpecialSourceRegValues(sourceLogKey, sourceData);
                    }
 
                    if (logName != source) {
                        if (!createLogKey) {
                            SetSpecialLogRegValues(logKey, logName);
                                                        
                            if (!SkipRegPatch) {
                                string[] sources = logKey.GetValue("Sources") as string[];
                                if (sources == null)
                                    logKey.SetValue("Sources", new string[] {logName, source}, RegistryValueKind.MultiString);
                                else {
                                    // We have a ---- with OS EventLog here.
                                    // OS might update Sources as well. We should avoid writing the 
                                    // source name if OS beats us.
                                    if( Array.IndexOf(sources, source) == -1) {
                                        string[] newsources = new string[sources.Length + 1];
                                        Array.Copy(sources, newsources, sources.Length);
                                        newsources[sources.Length] = source;
                                        logKey.SetValue("Sources", newsources, RegistryValueKind.MultiString);
                                    }
                                }
                            }
                        }
 
                        sourceKey = logKey.CreateSubKey(source);
                        SetSpecialSourceRegValues(sourceKey, sourceData);
                    }
                }
                finally {
                    if (baseKey != null) 
                        baseKey.Close();
 
                    if (eventKey != null)
                        eventKey.Close();
 
                    if (logKey != null) {
                        logKey.Flush();
                        logKey.Close();
                    }
 
                    if (sourceLogKey != null) {
                        sourceLogKey.Flush();
                        sourceLogKey.Close();
                    }
 
                    if (sourceKey != null) {
                        sourceKey.Flush();
                        sourceKey.Close();
                    }
 
                    // Revert registry and environment permission asserts
                    CodeAccessPermission.RevertAssert();
                }
            }
            finally {
                if (mutex != null) {
                    mutex.ReleaseMutex();
                    mutex.Close();
                }
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Removes
        ///       an event
        ///       log from the local computer.
        ///    </para>
        /// </devdoc>
        [ResourceExposure(ResourceScope.Machine)]   // See why someone would delete an event log
        [ResourceConsumption(ResourceScope.Machine)]
        public static void Delete(string logName) {
            Delete(logName, ".");
        }
 
        /// <devdoc>
        ///    <para>
        ///       Removes
        ///       an
        ///       event
        ///       log from the specified computer.
        ///    </para>
        /// </devdoc>
        [ResourceExposure(ResourceScope.Machine)]   // See why someone would delete an event log
        [ResourceConsumption(ResourceScope.Machine)]
        public static void Delete(string logName, string machineName) {
 
            if (!SyntaxCheck.CheckMachineName(machineName))
                throw new ArgumentException(SR.GetString(SR.InvalidParameterFormat, "machineName"));
            if (logName == null || logName.Length==0)
                throw new ArgumentException(SR.GetString(SR.NoLogName));
            if (!ValidLogName(logName, false))
                throw new InvalidOperationException(SR.GetString(SR.BadLogName));
 
            EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, machineName);
            permission.Demand();
 
            //Check environment before even trying to play with the registry
            SharedUtils.CheckEnvironment();
 
            //SECREVIEW: Note that EventLog permission is demanded above.
            PermissionSet permissionSet = _UnsafeGetAssertPermSet();
            permissionSet.Assert();
            
            RegistryKey eventlogkey = null;
 
            Mutex mutex = null;
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                SharedUtils.EnterMutex(eventLogMutexName, ref mutex);
 
                try {
                    eventlogkey  = GetEventLogRegKey(machineName, true);
                    if (eventlogkey  == null) {
                        // there's not even an event log service on the machine.
                        // or, more likely, we don't have the access to read the registry.
                        throw new InvalidOperationException(SR.GetString(SR.RegKeyNoAccess, "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\EventLog", machineName));
                    }
 
                    using (RegistryKey logKey = eventlogkey.OpenSubKey(logName)) {
                        if (logKey == null)
                            throw new InvalidOperationException(SR.GetString(SR.MissingLog, logName, machineName));
 
                        //clear out log before trying to delete it
                        //that way, if we can't delete the log file, no entries will persist because it has been cleared
                        EventLog logToClear = new EventLog(logName, machineName);
                        try {
                            logToClear.Clear();
                        }
                        finally {
                            logToClear.Close();
                        }
 
                        // 
 
 
                        string filename = null;
                        try {
                            //most of the time, the "File" key does not exist, but we'll still give it a whirl
                            filename = (string) logKey.GetValue("File");
                        }
                        catch { }
                        if (filename != null) {
                            try {
                                File.Delete(filename);
                            }
                            catch { }
                        }
                    }
 
                    // now delete the registry entry
                    eventlogkey.DeleteSubKeyTree(logName);
                }
                finally {
                    if (eventlogkey != null) eventlogkey.Close();
                
                    // Revert registry and environment permission asserts
                    CodeAccessPermission.RevertAssert();
                }
            }
            finally {
                if (mutex != null) mutex.ReleaseMutex();
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Removes the event source
        ///       registration from the event log of the local computer.
        ///    </para>
        /// </devdoc>
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        public static void DeleteEventSource(string source) {
            DeleteEventSource(source, ".");
        }
 
        /// <devdoc>
        ///    <para>
        ///       Removes
        ///       the application's event source registration from the specified computer.
        ///    </para>
        /// </devdoc>
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        public static void DeleteEventSource(string source, string machineName) {
            if (!SyntaxCheck.CheckMachineName(machineName)) {
                throw new ArgumentException(SR.GetString(SR.InvalidParameter, "machineName", machineName));
            }
 
            EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, machineName);
            permission.Demand();
 
            //Check environment before looking at the registry
            SharedUtils.CheckEnvironment();
 
            //SECREVIEW: Note that EventLog permission is demanded above.
            PermissionSet permissionSet = _UnsafeGetAssertPermSet();
            permissionSet.Assert();
            
            Mutex mutex = null;
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                SharedUtils.EnterMutex(eventLogMutexName, ref mutex);
                RegistryKey key = null;
 
                // First open the key read only so we can do some checks.  This is important so we get the same 
                // exceptions even if we don't have write access to the reg key. 
                using (key = FindSourceRegistration(source, machineName, true)) {
                    if (key == null) {
                        if (machineName == null)
                            throw new ArgumentException(SR.GetString(SR.LocalSourceNotRegistered, source));
                        else
                            throw new ArgumentException(SR.GetString(SR.SourceNotRegistered, source, machineName, "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\EventLog"));
                    }
            
                    // Check parent registry key (Event Log Name) and if it's equal to source, then throw an exception.
                    // The reason: each log registry key must always contain subkey (i.e. source) with the same name.
                    string keyname = key.Name;
                    int index = keyname.LastIndexOf('\\');
                    if ( string.Compare(keyname, index+1, source, 0, keyname.Length - index, StringComparison.Ordinal) == 0 )
                        throw new InvalidOperationException(SR.GetString(SR.CannotDeleteEqualSource, source));
                }
 
                try {
                    // now open it read/write to try to do the actual delete
                    key = FindSourceRegistration(source, machineName, false);
                    key.DeleteSubKeyTree(source);
                                        
                    if (!SkipRegPatch) { 
                        string[] sources = (string[]) key.GetValue("Sources");
                        ArrayList newsources = new ArrayList(sources.Length - 1);
 
                        for (int i=0; i<sources.Length; i++) {
                            if (sources[i] != source) {
                                newsources.Add(sources[i]);
                            }
                        }
                        string[] newsourcesArray = new string[newsources.Count];
                        newsources.CopyTo(newsourcesArray);
 
                        key.SetValue("Sources", newsourcesArray, RegistryValueKind.MultiString);
                    }
                }
                finally {
                    if (key != null) {
                        key.Flush();
                        key.Close();
                    }
                
                    // Revert registry and environment permission asserts
                    CodeAccessPermission.RevertAssert();
                }
            }
            finally {
                if (mutex != null)
                    mutex.ReleaseMutex();
            }
        }
 
        /// <devdoc>
        /// </devdoc>
        protected override void Dispose(bool disposing) {
            if(m_underlyingEventLog != null) {
                m_underlyingEventLog.Dispose(disposing);
            }
 
            base.Dispose(disposing);
        }
 
        /// <devdoc>
        /// </devdoc>
        public void EndInit() {
            m_underlyingEventLog.EndInit();
        }
 
        /// <devdoc>
        ///    <para>
        ///       Determines whether the log
        ///       exists on the local computer.
        ///    </para>
        /// </devdoc>
        public static bool Exists(string logName) {
            return Exists(logName, ".");
        }
 
        /// <devdoc>
        ///    <para>
        ///       Determines whether the
        ///       log exists on the specified computer.
        ///    </para>
        /// </devdoc>
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        public static bool Exists(string logName, string machineName) {
            if (!SyntaxCheck.CheckMachineName(machineName))
                throw new ArgumentException(SR.GetString(SR.InvalidParameterFormat, "machineName"));
 
            EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, machineName);
            permission.Demand();
 
            if (logName == null || logName.Length==0)
                return false;
 
            //Check environment before looking at the registry
            SharedUtils.CheckEnvironment();
 
            //SECREVIEW: Note that EventLog permission is demanded above.
            PermissionSet permissionSet = _UnsafeGetAssertPermSet();
            permissionSet.Assert();
            
            RegistryKey eventkey = null;
            RegistryKey logKey = null;
 
            try {
                eventkey = GetEventLogRegKey(machineName, false);
                if (eventkey == null)
                    return false;
 
                logKey = eventkey.OpenSubKey(logName, false);         // try to find log file key immediately.
                return (logKey != null );
            }
            finally {
                if (eventkey != null) eventkey.Close();
                if (logKey != null) logKey.Close();
                
                // Revert registry and environment permission asserts
                CodeAccessPermission.RevertAssert();
            }
        }
 
 
        // Try to find log file name with the same 8 first characters.
        // Returns 'null' if no "same first 8 chars" log is found.   logName.Length must be > 7
        private static string FindSame8FirstCharsLog(RegistryKey keyParent, string logName) {
 
            string logNameFirst8 = logName.Substring(0, 8);
            string[] logNames = keyParent.GetSubKeyNames();
 
            for (int i = 0; i < logNames.Length; i++) {
                string currentLogName = logNames[i];
                if ( currentLogName.Length >= 8  &&
                     string.Compare(currentLogName.Substring(0, 8), logNameFirst8, StringComparison.OrdinalIgnoreCase) == 0)
                    return currentLogName;
            }
 
            return null;   // not found
        }
 
        /// <devdoc>
        ///     Gets a RegistryKey that points to the LogName entry in the registry that is
        ///     the parent of the given source on the given machine, or null if none is found.
        /// </devdoc>
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        private static RegistryKey FindSourceRegistration(string source, string machineName, bool readOnly) {
            return FindSourceRegistration(source, machineName, readOnly, false);
        }
 
        /// <devdoc>
        ///     Gets a RegistryKey that points to the LogName entry in the registry that is
        ///     the parent of the given source on the given machine, or null if none is found.
        /// </devdoc>
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        private static RegistryKey FindSourceRegistration(string source, string machineName, bool readOnly, bool wantToCreate) {
            if (source != null && source.Length != 0) {
 
                //Check environment before looking at the registry
                SharedUtils.CheckEnvironment();
 
                //SECREVIEW: Any call to this function must have demmanded
                //                         EventLogPermission before.
                PermissionSet permissionSet = _UnsafeGetAssertPermSet();
                permissionSet.Assert();
                
                RegistryKey eventkey = null;
                try {
                    eventkey = GetEventLogRegKey(machineName, !readOnly);
                    if (eventkey == null) {
                        // there's not even an event log service on the machine.
                        // or, more likely, we don't have the access to read the registry.
                        return null;
                    }
 
                    StringBuilder inaccessibleLogs = null;
                    
                    // Most machines will return only { "Application", "System", "Security" },
                    // but you can create your own if you want.
                    string[] logNames = eventkey.GetSubKeyNames();
                    for (int i = 0; i < logNames.Length; i++) {
                        // see if the source is registered in this log.
                        // NOTE: A source name must be unique across ALL LOGS!
                        RegistryKey sourceKey = null;
                        try {
                            RegistryKey logKey = eventkey.OpenSubKey(logNames[i], /*writable*/!readOnly);
                            if (logKey != null) {
                                sourceKey = logKey.OpenSubKey(source, /*writable*/!readOnly);
                                if (sourceKey != null) {
                                    // found it
                                    return logKey;
                                } else {
                                    logKey.Close();
                                }
                            }
                            // else logKey is null, so we don't need to Close it
                        }
                        catch (UnauthorizedAccessException) {
                            if (inaccessibleLogs == null) {
                                inaccessibleLogs = new StringBuilder(logNames[i]);
                            }
                            else {
                                inaccessibleLogs.Append(", ");
                                inaccessibleLogs.Append(logNames[i]);
                            }
                        }
                        catch (SecurityException) {
                            if (inaccessibleLogs == null) {
                                inaccessibleLogs = new StringBuilder(logNames[i]);
                            }
                            else {
                                inaccessibleLogs.Append(", ");
                                inaccessibleLogs.Append(logNames[i]);
                            }
                        }
                        finally {
                            if (sourceKey != null) sourceKey.Close();
                        }
                    }
 
                    if (inaccessibleLogs != null)
                        throw new SecurityException(SR.GetString(wantToCreate ? SR.SomeLogsInaccessibleToCreate : SR.SomeLogsInaccessible, inaccessibleLogs.ToString()));
                    
                }
                finally {
                    if (eventkey != null) eventkey.Close();
                    
                    // Revert registry and environment permission asserts
                    CodeAccessPermission.RevertAssert();
                }
                // didn't see it anywhere
            }
 
            return null;
        }
 
        /// <devdoc>
        ///    <para>
        ///       Searches for all event logs on the local computer and
        ///       creates an array of <see cref='System.Diagnostics.EventLog'/>
        ///       objects to contain the
        ///       list.
        ///    </para>
        /// </devdoc>
        public static EventLog[] GetEventLogs() {
            return GetEventLogs(".");
        }
 
        /// <devdoc>
        ///    <para>
        ///       Searches for all event logs on the given computer and
        ///       creates an array of <see cref='System.Diagnostics.EventLog'/>
        ///       objects to contain the
        ///       list.
        ///    </para>
        /// </devdoc>
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        public static EventLog[] GetEventLogs(string machineName) {
            if (!SyntaxCheck.CheckMachineName(machineName)) {
                throw new ArgumentException(SR.GetString(SR.InvalidParameter, "machineName", machineName));
            }
 
            EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, machineName);
            permission.Demand();
 
            //Check environment before looking at the registry
            SharedUtils.CheckEnvironment();
 
            string[] logNames = new string[0];
            //SECREVIEW: Note that EventLogPermission is just demmanded above
            PermissionSet permissionSet = _UnsafeGetAssertPermSet();
            permissionSet.Assert();
            
            RegistryKey eventkey = null;
            try {
                // we figure out what logs are on the machine by looking in the registry.
                eventkey = GetEventLogRegKey(machineName, false);
                if (eventkey == null)
                    // there's not even an event log service on the machine.
                    // or, more likely, we don't have the access to read the registry.
                    throw new InvalidOperationException(SR.GetString(SR.RegKeyMissingShort, EventLogKey, machineName));
                // Most machines will return only { "Application", "System", "Security" },
                // but you can create your own if you want.
                logNames = eventkey.GetSubKeyNames();
            }
            finally {
                if (eventkey != null) eventkey.Close();
                // Revert registry and environment permission asserts
                CodeAccessPermission.RevertAssert();
            }
 
            // now create EventLog objects that point to those logs
            if (s_dontFilterRegKeys || machineName != "."){
                EventLog[] logs = new EventLog[logNames.Length];
                for (int i = 0; i < logNames.Length; i++) {
                    EventLog log = new EventLog(logNames[i], machineName);
                    logs[i] = log;
                }
 
                return logs;
            }
            else
            {
                List<EventLog> logs = new List<EventLog>(logNames.Length);
                for (int i = 0; i < logNames.Length; i++) {
                    EventLog log = new EventLog(logNames[i], machineName);
                    SafeEventLogReadHandle handle = SafeEventLogReadHandle.OpenEventLog(machineName, logNames[i]);
 
                    if (!handle.IsInvalid) {
                        handle.Close();
                        logs.Add(log);
                    }
                    else if (Marshal.GetLastWin32Error() != 87) { // Windows returns ERROR_INVALID_PARAMETER for special keys which were added in RS5+ but do not represent actual event logs.
                        logs.Add(log);
                    }
                }
 
                return logs.ToArray();
            }
        }
 
        private static bool IsWindowsRS5OrUp()
        {
            // Asserting permissions required to run unmananged code.
            new System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode).Assert();
            // VER_PLATFORM_WIN32_NT = 2
            NativeMethods.RTL_OSVERSIONINFOEX osv = new NativeMethods.RTL_OSVERSIONINFOEX();
            osv.dwOSVersionInfoSize = (uint)Marshal.SizeOf(osv);
            int ret = NativeMethods.RtlGetVersion(out osv);
            return ret == 0 && osv.dwPlatformId == 2 &&
                    (osv.dwMajorVersion > 10 || (osv.dwMajorVersion == 10 && (osv.dwMinorVersion > 0 || (osv.dwMinorVersion == 0 && osv.dwBuildNumber >= 17763))));
        }
 
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        internal static RegistryKey GetEventLogRegKey(string machine, bool writable) {
            RegistryKey lmkey = null;
            
            try {
                if (machine.Equals(".")) {
                    lmkey = Registry.LocalMachine;
                }
                else {
                    lmkey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, machine);
 
                }
                if (lmkey != null)
                    return lmkey.OpenSubKey(EventLogKey, writable);
            }
            finally {
                if (lmkey != null) lmkey.Close();
            }
 
            return null;
        }
 
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        internal static string GetDllPath(string machineName) {
            return Path.Combine(SharedUtils.GetLatestBuildDllDirectory(machineName), DllName);
        }
 
        /// <devdoc>
        ///    <para>
        ///       Determines whether an event source is registered on the local computer.
        ///    </para>
        /// </devdoc>
        public static bool SourceExists(string source) {
            return SourceExists(source, ".");
        }
 
        /// <devdoc>
        ///    <para>
        ///       Determines whether an event
        ///       source is registered on a specified computer.
        ///    </para>
        /// </devdoc>
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        public static bool SourceExists(string source, string machineName) {
            return SourceExists(source, machineName, false);
        }
 
        /// <devdoc>
        ///    <para>
        ///       Determines whether an event
        ///       source is registered on a specified computer.
        ///    </para>
        /// </devdoc>
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        [SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity", Justification = "Microsoft: Safe, machineName doesn't change")]
        internal static bool SourceExists(string source, string machineName, bool wantToCreate) {
            if (!SyntaxCheck.CheckMachineName(machineName)) {
                throw new ArgumentException(SR.GetString(SR.InvalidParameter, "machineName", machineName));
            }
 
            EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, machineName);
            permission.Demand();
 
            using (RegistryKey keyFound = FindSourceRegistration(source, machineName, true, wantToCreate)) {
                return (keyFound != null);
            }
        }
 
        /// <devdoc>
        ///     Gets the name of the log that the given source name is registered in.
        /// </devdoc>
        public static string LogNameFromSourceName(string source, string machineName) {
            EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, machineName);
            permission.Demand();
 
            return _InternalLogNameFromSourceName(source, machineName);
        }
 
        // No permission check, use with care!
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        internal static string _InternalLogNameFromSourceName(string source, string machineName) {
            using (RegistryKey key = FindSourceRegistration(source, machineName, true)) {
                if (key == null)
                    return "";
                else {
                    string name = key.Name;
                    int whackPos = name.LastIndexOf('\\');
                    // this will work even if whackPos is -1
                    return name.Substring(whackPos+1);
                }
            }
        }
 
 
        [ComVisible(false)]
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        public void ModifyOverflowPolicy(OverflowAction action, int retentionDays) {
            m_underlyingEventLog.ModifyOverflowPolicy(action, retentionDays);
        }
 
        [ComVisible(false)]
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        public void RegisterDisplayName(string resourceFile, long resourceId) {
            m_underlyingEventLog.RegisterDisplayName(resourceFile, resourceId);
        }
 
        // The reasoning behind filling these values is historical. WS03 RTM had a ----
        // between registry changes and EventLog service, which made the service wait 2 secs
        // before retrying to see whether all regkey values are present. To avoid this 
        // potential lag (worst case up to n*2 secs where n is the number of required regkeys) 
        // between creation and being able to write events, we started filling some of these
        // values explicitly but for XP and latter OS releases like WS03 SP1 and Vista this 
        // is not necessary and in some cases like the "File" key it's plain wrong to write. 
        private static void SetSpecialLogRegValues(RegistryKey logKey, string logName) {
            // Set all the default values for this log.  AutoBackupLogfiles only makes sense in 
            // Win2000 SP4, WinXP SP1, and Win2003, but it should alright elsewhere. 
 
            // Since we use this method on the existing system logs as well as our own,
            // we need to make sure we don't overwrite any existing values. 
            if (logKey.GetValue("MaxSize") == null)
                logKey.SetValue("MaxSize", DefaultMaxSize, RegistryValueKind.DWord);
            if (logKey.GetValue("AutoBackupLogFiles") == null)
                logKey.SetValue("AutoBackupLogFiles", 0, RegistryValueKind.DWord);
 
            if (!SkipRegPatch) { 
                // In Vista, "retention of events for 'n' days" concept is removed
                if (logKey.GetValue("Retention") == null)
                    logKey.SetValue("Retention", DefaultRetention, RegistryValueKind.DWord);
                
                if (logKey.GetValue("File") == null) { 
                    string filename;
                    if (logName.Length > 8)
                        filename = @"%SystemRoot%\System32\config\" + logName.Substring(0,8) + ".evt";
                    else
                        filename = @"%SystemRoot%\System32\config\" + logName + ".evt";
 
                    logKey.SetValue("File", filename, RegistryValueKind.ExpandString);
                }
            }
        }
 
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        private static void SetSpecialSourceRegValues(RegistryKey sourceLogKey, EventSourceCreationData sourceData) {
            if (String.IsNullOrEmpty(sourceData.MessageResourceFile))
                sourceLogKey.SetValue("EventMessageFile", GetDllPath(sourceData.MachineName), RegistryValueKind.ExpandString);
            else 
                sourceLogKey.SetValue("EventMessageFile", FixupPath(sourceData.MessageResourceFile), RegistryValueKind.ExpandString);
 
            if (!String.IsNullOrEmpty(sourceData.ParameterResourceFile))
                sourceLogKey.SetValue("ParameterMessageFile", FixupPath(sourceData.ParameterResourceFile), RegistryValueKind.ExpandString);
 
            if (!String.IsNullOrEmpty(sourceData.CategoryResourceFile)) {
                sourceLogKey.SetValue("CategoryMessageFile", FixupPath(sourceData.CategoryResourceFile), RegistryValueKind.ExpandString);
                sourceLogKey.SetValue("CategoryCount", sourceData.CategoryCount, RegistryValueKind.DWord);
            }
        }
 
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        private static string FixupPath(string path) {
            if (path[0] == '%')
                return path;
            else
                return Path.GetFullPath(path);
        }
        
        // Format message in specific DLL. Return <null> on failure.
        internal static string TryFormatMessage(SafeLibraryHandle hModule, uint messageNum, string[] insertionStrings) {
 
            if (insertionStrings.Length == 0) {
                // UnsafeTryFromatMessage will set FORMAT_MESSAGE_IGNORE_INSERTS when calling into the OS 
                // when there are no insertion strings, in this case we don't have to guard against insertionStrings
                // not having enough data since it is unused when FORMAT_MESSAGE_IGNORE_INSERTS is specified
                return UnsafeTryFormatMessage(hModule, messageNum, insertionStrings);
            }
 
            // If you pass in an empty array UnsafeTryFormatMessage will just pull out the message.
            string formatString = UnsafeTryFormatMessage(hModule, messageNum, new string[0]);
 
            if (formatString == null) {
                return null;
            }
 
            int largestNumber = 0;
 
            for (int i = 0; i < formatString.Length; i++) {
                if (formatString[i] == '%') {
                    // See if a number follows this, if so, grab the number.
                    if(formatString.Length > i + 1) {
                        StringBuilder sb = new StringBuilder();
                        while (i + 1 < formatString.Length && Char.IsDigit(formatString[i + 1])) {
                            sb.Append(formatString[i + 1]);
                            i++;
                        }
 
                        // move over the non number character that broke us out of the loop
                        i++;
 
                        if (sb.Length > 0) {
                            int num = -1;
                            if (Int32.TryParse(sb.ToString(), NumberStyles.None, CultureInfo.InvariantCulture, out num)) {
                                largestNumber = Math.Max(largestNumber, num);
                            }
                        }
                    }
                }
            }
 
            // Replacement strings are 1 indexed.
            if (largestNumber > insertionStrings.Length) {
                string[] newStrings = new string[largestNumber];
                Array.Copy(insertionStrings, newStrings, insertionStrings.Length);
                for (int i = insertionStrings.Length; i < newStrings.Length; i++) {
                    newStrings[i] = "%" + (i + 1);
                }
 
                insertionStrings = newStrings;
            }
 
            return UnsafeTryFormatMessage(hModule, messageNum, insertionStrings);
        }
 
        // FormatMessageW will AV if you don't pass in enough format strings.  If you call TryFormatMessage we ensure insertionStrings
        // is long enough.  You don't want to call this directly unless you're sure insertionStrings is long enough!
        internal static string UnsafeTryFormatMessage(SafeLibraryHandle hModule, uint messageNum, string[] insertionStrings) {
            string msg = null;
 
            int msgLen = 0;
            StringBuilder buf = new StringBuilder(1024);
            int flags = NativeMethods.FORMAT_MESSAGE_FROM_HMODULE | NativeMethods.FORMAT_MESSAGE_ARGUMENT_ARRAY;
 
            IntPtr[] addresses = new IntPtr[insertionStrings.Length];
            GCHandle[] handles = new GCHandle[insertionStrings.Length];
            GCHandle stringsRoot = GCHandle.Alloc(addresses, GCHandleType.Pinned);
 
            // Make sure that we don't try to pass in a zero length array of addresses.  If there are no insertion strings, 
            // we'll use the FORMAT_MESSAGE_IGNORE_INSERTS flag . 
            // If you change this behavior, make sure you look at TryFormatMessage which depends on this behavior!
            if (insertionStrings.Length == 0) {
                flags |= NativeMethods.FORMAT_MESSAGE_IGNORE_INSERTS;
            }
            
            try {
                for (int i=0; i<handles.Length; i++) {
                    handles[i] = GCHandle.Alloc(insertionStrings[i], GCHandleType.Pinned);
                    addresses[i] = handles[i].AddrOfPinnedObject();
                }
                int lastError = NativeMethods.ERROR_INSUFFICIENT_BUFFER;
                while (msgLen == 0 && lastError == NativeMethods.ERROR_INSUFFICIENT_BUFFER) {
                    msgLen = SafeNativeMethods.FormatMessage(
                        flags,
                        hModule,
                        messageNum,
                        0,
                        buf,
                        buf.Capacity,
                        addresses);
 
                    if (msgLen == 0) {
                        lastError = Marshal.GetLastWin32Error();
                        if (lastError == NativeMethods.ERROR_INSUFFICIENT_BUFFER)
                            buf.Capacity = buf.Capacity * 2;
                    }
                }
            }
            catch {
                msgLen = 0;              // return empty on failure
            }
            finally  {
                for (int i=0; i<handles.Length; i++) {
                    if (handles[i].IsAllocated) handles[i].Free();
                }
                stringsRoot.Free();
            }
            
            if (msgLen > 0) {
                msg = buf.ToString();
                // chop off a single CR/LF pair from the end if there is one. FormatMessage always appends one extra.
                if (msg.Length > 1 && msg[msg.Length-1] == '\n')
                    msg = msg.Substring(0, msg.Length-2);
            }
 
            return msg;
        }
 
 
        // CharIsPrintable used to be Char.IsPrintable, but Jay removed it and
        // is forcing people to use the Unicode categories themselves.  Copied
        // the code here.  
        private static bool CharIsPrintable(char c) {
            UnicodeCategory uc = Char.GetUnicodeCategory(c);
            return (!(uc == UnicodeCategory.Control) || (uc == UnicodeCategory.Format) ||
                    (uc == UnicodeCategory.LineSeparator) || (uc == UnicodeCategory.ParagraphSeparator) ||
            (uc == UnicodeCategory.OtherNotAssigned));
        }
 
        // SECREVIEW: Make sure this method catches all the strange cases.
        internal static bool ValidLogName(string logName, bool ignoreEmpty) {
            // No need to trim here since the next check will verify that there are no spaces.
            // We need to ignore the empty string as an invalid log name sometimes because it can
            // be passed in from our default constructor.
            if (logName.Length == 0 && !ignoreEmpty)
                return false;
 
            //any space, backslash, asterisk, or question mark is bad
            //any non-printable characters are also bad
            foreach (char c in logName)
                if (!CharIsPrintable(c) || (c == '\\') || (c == '*') || (c == '?'))
                    return false;
 
            return true;
        }
 
        /// <devdoc>
        ///    <para>
        ///       Writes an information type entry with the given message text to the event log.
        ///    </para>
        /// </devdoc>
        public void WriteEntry(string message) {
            WriteEntry(message, EventLogEntryType.Information, (short) 0, 0, null);
        }
 
        /// <devdoc>
        /// </devdoc>
        public static void WriteEntry(string source, string message) {
            WriteEntry(source, message, EventLogEntryType.Information, (short) 0, 0, null);
        }
 
        /// <devdoc>
        ///    <para>
        ///       Writes an entry of the specified <see cref='System.Diagnostics.EventLogEntryType'/> to the event log. Valid types are
        ///    <see langword='Error'/>, <see langword='Warning'/>, <see langword='Information'/>,
        ///    <see langword='Success Audit'/>, and <see langword='Failure Audit'/>.
        ///    </para>
        /// </devdoc>
        public void WriteEntry(string message, EventLogEntryType type) {
            WriteEntry(message, type, (short) 0, 0, null);
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static void WriteEntry(string source, string message, EventLogEntryType type) {
            WriteEntry(source, message, type, (short) 0, 0, null);
        }
 
        /// <devdoc>
        ///    <para>
        ///       Writes an entry of the specified <see cref='System.Diagnostics.EventLogEntryType'/>
        ///       and with the
        ///       user-defined <paramref name="eventID"/>
        ///       to
        ///       the event log.
        ///    </para>
        /// </devdoc>
        public void WriteEntry(string message, EventLogEntryType type, int eventID) {
            WriteEntry(message, type, eventID, 0, null);
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static void WriteEntry(string source, string message, EventLogEntryType type, int eventID) {
            WriteEntry(source, message, type, eventID, 0, null);
        }
 
        /// <devdoc>
        ///    <para>
        ///       Writes an entry of the specified type with the
        ///       user-defined <paramref name="eventID"/> and <paramref name="category"/>
        ///       to the event log. The <paramref name="category"/>
        ///       can be used by the event viewer to filter events in the log.
        ///    </para>
        /// </devdoc>
        public void WriteEntry(string message, EventLogEntryType type, int eventID, short category) {
            WriteEntry(message, type, eventID, category, null);
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static void WriteEntry(string source, string message, EventLogEntryType type, int eventID, short category) {
            WriteEntry(source, message, type, eventID, category, null);
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static void WriteEntry(string source, string message, EventLogEntryType type, int eventID, short category,
                               byte[] rawData) {
            using (EventLogInternal log = new EventLogInternal("", ".", CheckAndNormalizeSourceName(source))) {
                log.WriteEntry(message, type, eventID, category, rawData);
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Writes an entry of the specified type with the
        ///       user-defined <paramref name="eventID"/> and <paramref name="category"/> to the event log, and appends binary data to
        ///       the message. The Event Viewer does not interpret this data; it
        ///       displays raw data only in a combined hexadecimal and text format.
        ///    </para>
        /// </devdoc>
        public void WriteEntry(string message, EventLogEntryType type, int eventID, short category,
                               byte[] rawData) {
 
            m_underlyingEventLog.WriteEntry(message, type, eventID, category, rawData);
        }
 
        [ComVisible(false)]
        public void WriteEvent(EventInstance instance, params Object[] values) {
            WriteEvent(instance, null, values);
        }
 
        [ComVisible(false)]
        public void WriteEvent(EventInstance instance, byte[] data, params Object[] values) {
            m_underlyingEventLog.WriteEvent(instance, data, values);
        }
 
        public static void WriteEvent(string source, EventInstance instance, params Object[] values) {
            using (EventLogInternal log = new EventLogInternal("", ".", CheckAndNormalizeSourceName(source))) {
                log.WriteEvent(instance, null, values);
            }
        }
 
        public static void WriteEvent(string source, EventInstance instance, byte[] data, params Object[] values) {
            using (EventLogInternal log = new EventLogInternal("", ".", CheckAndNormalizeSourceName(source))) {
                log.WriteEvent(instance, data, values);
            }
        }
 
        // The EventLog.set_Source used to do some normalization and throw some exceptions.  We mimic that behavior here.
        private static string CheckAndNormalizeSourceName(string source) {
            if (source == null)
                source = string.Empty;
 
            // this 254 limit is the max length of a registry key.
            if (source.Length + EventLogKey.Length > 254)
                throw new ArgumentException(SR.GetString(SR.ParameterTooLong, "source", 254 - EventLogKey.Length));
 
            return source;
        }
    }
 
}