Figure 1 Default event log message displayed
Contents
Have you ever seen the message in Figure 1 and wondered about this link displayed below your message text? The link is added automatically every time your .NET application writes a message into the event log. Unfortunately, the MSDN library does not offer direct instructions for .NET developers on how to get rid of that link which diverts curious visitors to Microsoft's Help and Support center. Maybe, it would be a good idea to display here a different link diverting visitors to your own web based support center (see Figure 2) or even discard the link completely. In this article, I will show you an easy approach to how to discard or replace that annoying default link with your own customized message.
Figure 2 Event log displaying a customized link
The approach I will discuss assumes you are familiar with the System.Diagnostics.EventLog
class. As a matter of fact, you should know, that this class offered by the .NET Framework 1.1 is a limited subset over a broader range of possibilities available through the corresponding SDK functions. David Field has already posted an article on the CodeProject, Enhanced EventLog writing for .NET Applications, describing how to gain unlimited access to these core Win32 functions via P/Invokes. Of course, you can use that class, however my approach works also with the standard System.Diagnostics.EventLog
class and you are not forced to use enhanced wrapper classes.
In this article, I would rather prefer remaining in the realm of the managed EventLog
class than going beyond its boundaries. Although the talk is about C# and .NET, you will be forced to take a short look at a simple C++ project in order to create a customized resource-only dynamic link library. There is no C++ programming skills required to understand and practice the approach I'm going to show you.
The reason the mentioned link to the Microsoft's Help and Support center appears, lies in the fact, that the .NET Framework uses a pre-built resource-only DLL by default, which contains a large but completely empty message text resource table. This resource-only DLL is located at <systemroot>\Microsoft.NET\Framework\v1.1.4322 \EventLogMessages.dll. This absolute path is written into the registry value EventMessageFile
every time a new event source is created by the EventLog.CreateEventSource
method. The registry key follows the pattern like:
HKLM\SYSTEM\CurrentControlSet\Services\Eventlog\<Log>\<Source> EventMessageFile
The default values of <Log>
are well known, like Application, Security or System. Customized logs can also be created, e.g., StefanoLog. The <Source>
is either set to known values like COM, COM+, ASP.NET 1.1 etc., or to custom values, e.g., StefanoSource (see Figure 3).
Figure 3 The Event log registry key
The good thing is, we can safely replace the value of the EventMessageFile
with any path pointing to a location of a valid custom resource-only DLL. Thus the task we want to accomplish reduces to the following two major and a few minor steps:
- Creating a customized resource-only DLL.
- Create a message text file (.mc)
- Compile the message text file using the Message Compiler tool to a resource script file (.rc)
- Compile the resource script file using a Resource Compiler tool to a resource table (.res)
- Link the resource table file (.res) into a valid resource-only DLL
- Altering the appropriate
EventMessageFile
registry value.
Do not be afraid of those minor steps, even if you are not familiar with the terms listed. The first major tasks can be easily accomplished using a simple batch file with just three lines of commands (see Figure 4). The customdefs.mc is a message text file containing the customized Message ID values (see Figure 5). The syntax of that message text file is somewhat strange and old fashioned. Today, architects would rather prefer a kind of XML configuration file instead. You can read more about the exact syntax at MSDN, in the article Message Text Files.
mc customdefs.mc
rc -r -fo customdefs.res customdefs.rc
link -dll -noentry -out:customdefs.mc.dll customdefs.res /MACHINE:X86
Figure 4 The batch file content
MessageId=0x0 Severity=Success Facility=Application
SymbolicName=SUCCESS_GENERAL
Language=English
This is success: %1
.
MessageId=0x1 Severity=Success Facility=Application
SymbolicName=SUCCESS_FIXED_MESSAGE
Language=English
This is a fixed length message. Hallo!!!
.
MessageId=0x2 Severity=Success Facility=Application
SymbolicName=SUCCESS_FIXED_MESSAGEWITHLINK
Language=English
This is a fixed length message displaying a link to the support web site.
%r%r%rClick here to learn more about this error http://www.codeproject.com
.
Figure 5 Message text file
The content of a message text file is composed of building blocks starting with a hex value of a customized Message ID and ending with a dot and an empty new line. Note also the %1
placeholders for the string you will send calling the WriteEntry()
method later on.
Be aware, the message text file also supports localization. In this case, make sure the message text file is saved in Unicode encoding format (due to the additional characters which appear in most languages). The message compiler is aware of Unicode content using the "-u" switch: mc -u customdefs.mc
. Figure 6 shows the part of a message text file supporting two languages, English and German.
LanguageNames=(English=0x409:MSG00409)
LanguageNames=(German=0x407:MSG00407)
MessageId=0x0 Severity=Success Facility=Application
SymbolicName=SUCCESS_GENERAL
Language=English
This is success: %1
.
Language=German
Das ist eine Erfolgsmeldung: %1
.
MessageId=0x1 Severity=Success Facility=Application
SymbolicName=SUCCESS_FIXED_MESSAGE
Language=English
This is a fixed length message.Hallo!!!
.
Language=German
Das ist eine Meldung von fixer L�nge. Hallo!!!
.
Figure 6 Localized message text file
The solution consists of four particular projects, listed below in their exact build order reflecting the dependencies:
- MakeCustomsource NMake project, which is a C++ batch
- DefineInserter C# Console Application project
- EventLogDemo C# Windows Application project
- Setup and Deployment project which packages the primary output of the Windows Application and the linked resource-only DLL
As mentioned above, a single trip into the realm of a C++ project is assumed at this stage. You have to create a Makefile project in order to create a resource-only DLL. Clicking on the right mouse button in the Solution Explorer pane and choosing "Add New Project...", a dialog box appears. Select "Visual C++ Projects / General / Makefile Project" (Figure 7). In the project's "NMake" property, make sure you added a single batch file customsource.bat (content shown in Figure 4) both to the "Build Command Line" and "Rebuild Command Line" values (Figure 8). This project creates the resource-only DLL and an additional C++ header file holding the customized Message ID values. The C++ header is used in the next project (DefineInserter) to parse it and inject automatically all those predefined Message ID values into your C# source as a single enumeration enum messageCodes : int
.
Figure 7 Creating a Makefile Project
Figure 8 NMake properties
This Console Application project is a bridge between the previous MakeCustomsource project and the main Widows Application project. The Console Application simply takes the C++ header (generated at the previous step) and injects all found Message ID values as an enumeration into the C# source code. Therefore, the Console Application assumes two command line arguments, first the name of the C++ header and second the name of the C# source file. The Console Application injects and updates the enumeration between the marked sections in the Form1
class. The marks are configured like C++ style comments as shown in Figure 9.
The DefineInserter project has also a post-build event defined. On successful build, the Console Application is started and refreshes the C# source file with the recently built Message ID values. This kind of an approach makes sure, that every build time, you have the actualized enumeration according to the recent message text file definitions, ready to use in the WriteEntry()
method's third (integer type) parameter.
public class Form1 : System.Windows.Forms.Form
{
enum messageCodes : int
{
SUCCESS_GENERAL = 0x00000000,
SUCCESS_FIXED_MESSAGE = 0x00000001,
SUCCESS_FIXED_MESSAGEWITHLINK = 0x00000002,
SUCCESS_MESSAGE_SHORT = 0x00000003,
SUCCESS_MESSAGE_MIDDLE = 0x00000004,
SUCCESS_MESSAGE_LONG = 0x00000005,
SUCCESS_WARNING_GENERAL = 0x00000100,
SUCCESS_ERROR_GENERAL = 0x00000200,
}
...
}
Figure 9 The marked section in the C# source code file
This Windows Application project is the main demo application. At every startup, it checks and tries to create both a new custom event log and a new custom event source. The .NET Framework behaves in a little strange manner, as creating a new event source not only creates the requested event log (parent) and the event source in it (child), but also creates a new event source within the event log using the very same name as the event log (see Figure 3). Thus, for a single event log (parent), two event sources (children) are created. The Windows Application (Figure 10) also overrides at startup one of those custom event sources' EventMessageFile
value, with the absolute path of the custom resource-only DLL customdefs.mc.dll, whereas the other event source remains referencing the .NET default EventLogMessages.dll.
You can switch between these two event sources using the CheckBox
control at the top of the application. Use the Write button and note the different formats as they appear in the EventViewer.
Figure 10 The tester Windows Application
Creating event logs and event sources is a registry operation which requires write access to the HKLM hive. Therefore, you have to run the application using an appropriate Administrative account, otherwise the operation will fail creating the custom event source. This limitation and a solution is discussed also in the knowledge base article number 842795. You receive the "Requested registry access is not allowed" error message when you try to create a custom event log. If your application has to run in a user context with reduced privileges, I suggest you create your event sources immediately at deployment time.
The single ComboBox shown in Figure 10 contains all the Message ID values edited and created in the message text file earlier. Every time you build the solution, the ComboBox is actualized automatically.
Due to the limitations of the EventLog
class, every Message ID can be used in a combination with the three different EventLogEntryType
enumerations (Information, Warning, Error). However in the message text file, every Message ID has the predefined Severity set to Success. Bear in mind, that at any full level event log usage scenario (ReportEvent()
), for warnings and errors, you have to declare Message ID values set to severities Warning or Error (see Figure 11). These severities would generate unsigned integer enumeration values inappropriate for the WriteEntry()
method. For this reason, in the recent solution, you are advised to use severities set to Success for whatever warning or error you want to report.
MessageId=0x200 Severity=Error Facility=Application
SymbolicName=ERROR_GENERAL
Language=English
Error: %1
.
Figure 11 Message text with Severity set to Error
This deployment project just packages the tester Windows Application assembly and the custom resource-only DLL file. This would be also the right place to extend the project and create the custom event source as a deployment custom action, e.g., using the Installer
class.
In this article, you learned the basics about how to deal with message text files and particularly how to create a resource-only DLL for .NET Applications based on a message text file. Using customized resource-only DLLs, you can divert visitors to your company's Help and Support web site, catch the sent information and display appropriate resources for the users. Customized resource-only DLLs are a great place for storing fixed messages intended to be written into a Windows Event-Log. Resource-only DLLs can also be easily localized.