Logging to the Windows EventLog without administrative privileges in .Net

This was a question I had from one of the customers I work for. My first reply was to use the static method on the built in .Net Framework class System.Diagnostics.EventLog.WriteEntry. This is easy and straight forward, but it has one disadvantage: If you are not an administrator the first time you write to the EventLog you will get a permission error. This is because the method attempts to register the source that you use to write the event in the Registry by writing an an entry here HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application. This will throw an Exception like System.Security.SecurityException: Requested registry access is not allowed.

One solution is of course to implement an installer class or to manually create the EventSource but both these actions require admin rights. Se https://support.microsoft.com/default.aspx?scid=kb;EN-US;329291 for more details.

But what if don’t want to register eventsources or is not allowed to by strict sysadmins (Don’t blame them – it is their job to be pissed every time you come with new fancy application to shake their system). Well, one solution is to reuse one of the previously installed EventSources like COM, .Net Runtime or Office – but this can get confusing for Operation Management systems like MOM. (Another great way to get the administrators mad…)

Another solution is to us an unmanaged Win32 API call. First you need to register some dlls and add a couple of consts

[DllImport("advapi32.dll")]
private static extern IntPtr RegisterEventSource(string lpUNCServerName, string lpSourceName);

[DllImport("advapi32.dll")]
private static extern bool DeregisterEventSource(IntPtr hEventLog);

[DllImport("advapi32.dll", EntryPoint = "ReportEventW", CharSet = CharSet.Unicode)]

private static extern bool ReportEvent(

            IntPtr hEventLog,

            ushort wType,

            ushort wCategory,

            int dwEventID,

            IntPtr lpUserSid,

            ushort wNumStrings,

            uint dwDataSize,

            string[] lpStrings,

            byte[] lpRawData

            );

public const ushort EVENTLOG_INFORMATION_TYPE = 0x0004;

public const ushort EVENTLOG_WARNING_TYPE = 0x0002;

public const ushort EVENTLOG_ERROR_TYPE = 0x0001;

Then you can wrap the necessary method calls in a static method

 

public static void WriteEventLogTextEntryApi(string text, ushort logType, int logEventId, byte[] rawData)

{

//Temporary registry of eventsource

IntPtr hEventLog = RegisterEventSource(null, "EventSourceName");

uint dataSize = (uint)(rawData != null ? rawData.Length : 0);

//Write event to eventlog

ReportEvent(hEventLog, logType, 0, logEventId, IntPtr.Zero, 1, dataSize, new string[] { text }, rawData);

//Remove temporary registration

DeregisterEventSource(hEventLog);

}  

 

And finally you log an event by calling the static method

 

WriteEventLogTextEntryApi("An error occured", EVENTLOG_ERROR_TYPE, 1, null);

The only drawbacks of this solution are of course the call to unmanaged APIs and then the fact that when you view the event in the Eventlog, this text will always be display before the errortext because the Eventviewer cannot find the eventsource.

The description for Event ID ( 1 ) in Source ( EventSourceName ) cannot be found. The local computer may not have the necessary registry information or message DLL files to display messages from a remote computer. You may be able to use the /AUXSOURCE= flag to retrieve this description; see Help and Support for details. The following information is part of the event