Showing posts with label Code. Show all posts
Showing posts with label Code. Show all posts

Saturday, March 15, 2008

The .NET Dataset for Stored Procedures in Visual Studio – the easy way

In case you need to call or retrieve data from a lot of stored procedures in SQL Server. You basically have two options:

A) “Hardcore”

You create your own class and define a SqlCommand object for each stored procedure manually. While doing this, tell your wife you can't see her for the next weeks

B) “Lazy man”

You use a dataset and let Visual Studio do the hard work. You use those generated procedure, tell your boss how many hours this took and leave early to enjoy your weekend.


The last option seems to be better IMHO, but the DataSet has some funny glitches that can make using it a pain. I’ll try to sort them out here.

Step 1: Create a dataset

Easy, just right-click your project, select “Add item” and choose “Dataset”. In this case, the dataset is called “DataSetStoredProcedures”.

Step 2: Move the generated dataset

To not clutter the project, create a new folder (right-click project, “Add” -> “New Folder”) called “DatabaseDataset”. Drag and drop “DataSetStoredProcedures.xsd” there.

Step 3: Add stored procedures

Select “View” -> “Server Explorer” to display the Server Explorer window. Right-click “Data Connections” and select “Add Connection”. Define the properties so Visual Studio can connect to the database. Open the new entry in the tree view and move to “Stored Procedures”.


In case you haven’t opened DataSetStoredProcedures.xsd, simply double-click it. Simply drag and drop the stored procedures you want to use to this window.

You may be asked how the connection should be called that will be used to execute this stored procedure, simply choose a name that makes sense to you. Once this all has being done, the project should look something like this:


Step 4: Check the stored procedures

At any time, you can simply click on a stored procedure, open the properties window and click on the “…” button for the Parameters:


You should do this simply because sometimes the generation is not done right. For example, in this project there is a user defined function (yes, you can also add UDFs) called “fncLTC2UTC” that return an SQL Server datetime value. However, the dataset converted this to “Object”. By using the parameters collection, you can simply change the value of @RETURN from object to DateTime.

Step 5: The glitches, Part I

By using the object browser, you can have a look at the namespace layout:


The project where we have added all this is called “x.server”. Because we have created a subfolder to put the dataset in, the namespace of the dataset has become “x.server.DatabaseDataset”. Beside this, the stored procedures where create in a sub object “QueriesTableAdapter” that has the namespace “x.server.DatabaseDataSet.DataSetStoredProceduresTableAdapters”:

Now this is a hell of a namespace we need to add it before we can call a stored procedure:


using x.server.DatabaseDataset.DataSetStoredProceduresTableAdapters;

class JustTesting
{
public static void Test1()
{
QueriesTableAdapter qta = new QueriesTableAdapter();
qta.procClientAuthentication_GetLoginData(…


Step 6: The glitches, Part II

Beside this namespace glitch, there is another one: By default, the dataset insists on using the connection string that each stored procedure has attached to (Properties viewer) and you can not to change this. In case you do not want to have the database connection inside app.config this would normally stop the use of the dataset.


Step 7: The solution

However, both glitches can easily be fixed. Within your normal namespace (e.g. “x.server”) simply define a class like this:


using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using x.server.DatabaseDataset.DataSetStoredProceduresTableAdapters;

namespace x.server
{
///


/// Shortcut class to all database stored procedures (procXXX).
///
public class DatabaseProcs:QueriesTableAdapter
{
public DatabaseProcs()
: base()
{
string sConnString = “Your Connection string here”;
foreach (IDbCommand cmd in CommandCollection)
{
cmd.Connection.ConnectionString = sConnString;
}
}
}
}



This class solves the two glitches: You need to reference the namespace only in this class (DatabaseProcs), all other objects that will use this class do not need to do it.

Second, DatabaseProcs is derived from QueriesTableAdapter so it can use the protected property “ConnectionString” and change it to anything you want.

With this class in between, calling a stored procedure is quite simple:


using System;
using System.Collections.Generic;
using System.Text;

namespace x.server
{
class JustTesting
{
public static void Test1()
{
DatabaseProcs procs = new DatabaseProcs();

procs.procClientAuthentication_GetLoginData(…




No more stupid namespaces and you your custom connection string is also set.


Note

The generated class will also leave the connection in the same state as it was before. This means, when you use the class DatabaseProcs (aka “QueriesTableAdapter”) like this, a connection will be opened when you execute it and closed right after that.

This might lead to a performance issue if you do not use connection pooling. To enable connection pooling simply use a connection string like this:


Data Source=(local)\SQLEXPRESS;Initial Catalog=X;Integrated Security=True;Pooling=True;Min Pool Size=0;Max Pool Size=5;Application Name=MyApp


If pooling is activated, a Connection.Close() does not actually close the connection but instead it will put the connection in the pool and reuse it when the next request to Connection.Open() comes in.


Enjoy!

Monday, February 18, 2008

Setting the right font for a Windows Forms application

If you create a new form in Visual Studio, the default font will always be "Microsoft Sans Serif" although you should use a font that depends on the Windows version you are running on. For Windows 2000, XP, Server 2003 you should use "Tahoma" for Vista and above it should be "Segoe UI". In any case, do not use "MS Sans Serif".

Unfortunately, there is no simple property available so the form uses the correct font automatically. Benjamin Hollis has a blog entry about this problem already and his code is quite simple and works quite well.

It just does not take into account if a form contains other, specialized fonts or if the fonts used have special styles (Underlined, Italics etc.) applied. I have tweaked his code a little bit.. ähm.. a lot actually and this is the result: FormFontFixer.

UPDATE: The code is now licensed under a BSD license (see below)


//Original idea and code (3 lines :-) by Benjamin Hollis: http://brh.numbera.com/blog/index.php/2007/04/11/setting-the-correct-default-font-in-net-windows-forms-apps/
//Copyright (C) TeX HeX of Xteq Systems: http://texhex.blogspot.com/ and http://www.texhex.info/
public static class FormFontFixer
{
//This list contains the fonts we want to replace.
static readonly List<string> FontReplaceList
= new List<string>( new string[] { "Microsoft Sans Serif", "Tahoma" } );


static Font _DefaultFont;
static bool _CanFixFonts;


static FormFontFixer()
{
//Basically the font name we want to use should be easy to choose by using the SystemFonts class. However, this class
//is hard-coded (!!) and doesn't seem to work right. On XP, it will mostly return "Microsoft Sans Serif" except
//for the DialogFont property (=Tahoma) but on Vista, this class will return "Tahoma" instead of "SegoiUI" for this property!

//Therefore we will do the following: If we are running on a OS below XP, we will exit because the only font available
//will be MS Sans Serif. On XP, we gonna use "Tahoma", and any other OS we will use the value of the MessageBoxFont
//property because this seems to be set correctly on Vista an above.

if (Environment.OSVersion.Platform==PlatformID.Win32Windows)
{
//95, 98 and other crap
_CanFixFonts = false;
return;
}

if (Environment.OSVersion.Version.Major < 5)
{
//Windows NT
_CanFixFonts = false;
return;
}

if (Environment.OSVersion.Version.Major < 6)
{
//Windows 2000 (5.0), Windows XP (5.1), Windows Server 2003 and XP Pro x64 Edtion v2003 (5.2)
_CanFixFonts = true;
_DefaultFont = SystemFonts.DialogFont; //Tahoma hopefully
}
else
{
//Vista and above
_CanFixFonts = true;
_DefaultFont = SystemFonts.MessageBoxFont; //should be SegoiUI
}
}

public static void Fix(Form form)
{
//If we can't fix the font, exit
if (_CanFixFonts == false)
{
return;
}


//Now start with the real work...
foreach (Control c in form.Controls)
{
//only replace fonts that use one the "system fonts" we have declared
if (FontReplaceList.IndexOf(c.Font.Name) > -1)
{
//Now check the size, when the size is 9 or below it's the default font size and we do not keep the size since
//SegoiUI has a complete different spacing (and thus size) than MS SansS or Tahoma.

//Also check if there are any styles applied on the font (e.g. Italic) which we need to apply to the new
//font as well.

bool bUseDefaultSize = true;
bool bUseDefaultStyle = true;

//is this a special size?
if ((c.Font.Size <= 8) || (c.Font.Size >= 9))
{
bUseDefaultSize = false;
}

//are any special styles (bold, italic etc.) applied to this font?
if ( (c.Font.Italic == true) ||
(c.Font.Strikeout == true) ||
(c.Font.Underline == true) ||
(c.Font.Bold == true))
{
bUseDefaultStyle = false;
}

//if everything is set to defaults, we can use our prepared font right away
if ((bUseDefaultSize == true) && (bUseDefaultStyle == true))
{
c.Font = _DefaultFont;
}
else
{
//There are non default properties set so
//there is some work we need to do...


//Restrive custom font style
FontStyle Style = FontStyle.Regular;
if (bUseDefaultStyle == false)
{
if (c.Font.Italic) {
Style = Style | FontStyle.Italic;
}
if (c.Font.Strikeout) {
Style = Style | FontStyle.Strikeout;
}
if (c.Font.Underline) {
Style = Style | FontStyle.Underline;
}
if (c.Font.Bold){
Style = Style | FontStyle.Bold;
}
}

//Retrive custom size
float fFontSize = _DefaultFont.SizeInPoints;
if (bUseDefaultSize == false)
{
fFontSize = c.Font.SizeInPoints;

}

//Finally apply this font...
Font font = new Font(_DefaultFont.Name, fFontSize, Style, GraphicsUnit.Point);
c.Font = font;

}
}
}

}
}


Copyright (c) 2008, TeX HeX (http://www.texhex.info/)

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the Xteq Systems (http://www.xteq.com/) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Thursday, January 31, 2008

WiX Toolkit (Windows Installer) Custom actions and conditions

Normally, for setup projects I use InnoSetup from Jordan Russel which is, put simple, the best setup creator you will find.

However, for a project I was forced to produce a Windows Installer package and used the WiX toolkit for it. Basically, after several dead-ends I was finally completely stuck. I simply wanted to have Windows Installer executing a custom action on every install of the application, regardless if it's already installed or not. If the application is being removed, my custom action shouldn't be executed.

By default, you would simply use the condition "NOT Installed" which means: If the application is not installed, execute it. Else, leave it alone. But as I said I wanted it to be execute every time somebody issues a command like MSIEXEX /I MyProduct.msi. The default condition would evaluate to FALSE (since the application is already installed) and thus, the custom action isn't started. Of course, you could pass /fa to MSIEXEC.exe but I feared this would be forgotten and thus I would get more "Nothing is working here calls".

After searching and testing for nearly two hours (and found an excellent post about custom actions and properties from Jeff Wharton) I was able to find the correct solution:

<custom action="LaunchExe" after="InstallInitialize">NOT (REMOVE="ALL")</custom>

This simply means: If the application is NOT being removed, execute the custom action.

If you know how, it's simple.

Thursday, January 24, 2008

Using caspol.exe to change .NET security policy - done right

Maybe you know CASPOL.exe to modify or add your own security policy for the .NET Framework.

Most examples you will find on the internet will simply add a code group to the configuration and most people use it this way: Upon each installation, CASPOL.EXE is exeucted.

What most people not realize is that CASPOL does not remove the old group when adding a new one with the same name. See this screenshot:

This is no broken installation, for .NET everything is fine even if 100+ groups with the same name would exist. However, for the user this looks like a bug so he will call support. To make things even worse: When you have changed the group membership of your custom group later on you will end up with two groups with completely different membership conditions.

To avoid this, I developed the following batch file that you can use and customize. The basic trick is to first list all available groups and if the script finds one that has the same name, it will be deleted.

For more information about CasPol.exe, see MSDN.

Enjoy!


@echo off
Rem .NET Framework 2.0 CasPol.exe batch by TeX HeX
Rem http://texhex.blogspot.com
Rem Version 1.0

Rem Set this to the name of the group you want to create
SET GROUP=Testing123
Rem Set this to the description your group should have
SET GROUPDESC=Just testing group

SET CASPOL=%WINDIR%\Microsoft.Net\Framework\v2.0.50727\caspol.exe
SET ERRLVL=9

echo ---- Setting prompt off ----
%caspol% -polchgprompt off

Rem Check if this group exists already
echo ---- Check group existence ----
%caspol% -m -ld|find /C /I "%GROUP%"
IF NOT ERRORLEVEL 1 GOTO DELETE_GROUP
GOTO CREATE_GROUP


Rem Deleting old group (two times to make sure that we do not have one left over)
:delete_group
echo ---- Removing old group ----
CASPOL% -m -remgroup "%GROUP%"
CASPOL% -m -remgroup "%GROUP%" >NUL


:create_group
echo ---- Creating group ----
REM %CASPOL% -m -addgroup All_Code -url "\*" -zone MyComputer FullTrust -name "%GROUP%"
REM %CASPOL% -m -addgroup All_Code -strong -file c:\arg.dll -noname -noversion FullTrust -name "%GROUP%" -description "%GROUPDESC%"

%CASPOL% -m -addgroup All_Code -zone MyComputer FullTrust -name "%GROUP%" -description "%GROUPDESC%"
SET ERRLVL=%ERRORLEVEL%

echo Result is %ERRLVL%

Rem Patch prompting again
echo ---- Setting prompt on ----
%caspol% -polchgprompt on



Rem Now check the result
IF %ERRLVL% EQU 0 (
echo "All fine!"
exit 0
) ELSE (
echo "Error!"
exit -1
)




Monday, August 27, 2007

SQL Server: Get row count for each table

This T-SQL (for SQL Server 2000 and 2005) script I just found will print out the number of rows for each table in the current database:

create table #rowcount (tablename varchar(128), rowcnt int)
exec sp_MSforeachtable 'insert into #rowcount select ''?'', count(*) from ?'
select * from #rowcount order by rowcnt desc
drop table #rowcount

The result will look like this:

Saturday, May 26, 2007

Snom Updates: A custom ASP firmware script

Update: This script is outdated, see http://texhex.blogspot.com/2008/01/new-snom-auto-update-script.html


I really like the VoIP phones from Snom. They are of good quality, not expensive and they have an excellent Wiki that describes all settings quite good. However, when it comes to Firmware updates, you only get the basic information and some PHP examples but not really a detailed description.

Especially about one issue with firmware updates: If you define a firmware configuration file the phone will restart ALWAYS, even if the firmware provided is not newer than the one installed - even if the Snom is currently being used!

To solve this issue, I did the following:

Inside the normal configuration file (called snom.cnf in our case) I defined the following settings for the firmware:


# Updates
firmware_status$: http://update-slinger.local/snom/snom-firmware.asp
update_policy: auto_update
# Check every three hours
firmware_interval$: 180


Inside the folder /snom I placed all current firmware BIN files from Snom and the ASP Script snom-firmware.asp.

This script (see below) simply checks the user agent to make sure that the right phone gets the right firmware. Also, it will ONLY set the "firmware" tag with the URL if it's between 23:00 (11 PM) and 04:00 (4 AM). That way, a Snom that checks for a new firmware during the day will not get the firmware tag and thus does not restart because there is nothing to install.

As we have set the firmware_interval to three hours inside snom.cnf, we have made sure that every telephone will check at least once between 11 PM and 4 AM so a new firmware is deployed to all telephones in one night.

<html>
<pre>

# Auto Update Skript

<%
'Snom Simple Update ASP Script 1.3
'Coypright (C) 2007 by TeX HeX
'http://www.texhex.info


'Definition of download URLs
s300="http://update-slinger.local/snom/snom300-6.5.10-SIP-j.bin"
s320="http://update-slinger.local/snom/snom320-6.5.10-SIP-j.bin"
s360="http://update-slinger.local/snom/snom360-6.5.10-SIP-j.bin"



Dim sUA
sUA=Request.ServerVariables("HTTP_USER_AGENT")

'Debug output, just for reference
Response.Write("# Found USER AGENT --> " + sUA)
Response.Write(chr(13) + chr(10))


'Only write out "firmware:"" tag if it's in the night (23:00 = 11 PM, 04:00 = 4 AM)
if ( hour(now())>=23 or hour(now())<=4 ) then Dim sURL pos=InStr(sUA,"snom300-SIP") if pos>0 then
sURL=s300
end if

pos=InStr(sUA,"snom320-SIP")
if pos>0 then
sURL=s320
end if

pos=InStr(sUA,"snom360-SIP")
if pos>0 then
sURL=s360
end if


Response.Write("firmware: " + sURL)
Response.Write(chr(13) + chr(10))

else

'Debug output, I don't think Snoms can actually undestand this :)
Response.Write("# You did not received a firmware because it's not in the night. We have now: " + cstr(now()) )
Response.Write(chr(13) + chr(10))

end if


%>

</pre>
</html>

Monday, May 07, 2007

SQL Server Integration Services (SSIS) Lookup does not like large data from SQL Server 2000

If you are using the Lookup component of SSIS inside a package and your lookup is really big (let's say 400,000 rows) and this lookup comes from an SQL Server 2000 table, you will have the strange effect that Lookup will report all rows as "not found".

All rows are then send to the error output so the data flow will look like this:

I have seen this behavior only if the data comes from SQL Server 2000, when using data from an SQL Server 2005 the component worked just fine.

The solution is simple: Just limit the cache size on the Lookup component and everything will work.

Saturday, February 03, 2007

A regular expression filtering Non-Select SQL

For a project I'm currently working on, we needed a very simple filter class. The project has the ability to allow administrators to run queries against the database. We needed to make sure that only "SELECT" queries are made and not, by mistake, an UPDATE, INSERT, ALTER or something like that.

Combining several regular expressions I found on the internet, I created the below class in C#. I really don't think its bullet proof but it should work just fine (if not: they gonna call me for sure :-).

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace SQLSelectFilter
{
public sealed class SQLSelectOnlyFilter
{
private SQLSelectOnlyFilter() {
}

bool bIsOK = false;
string sBadPattern = "";

public SQLSelectOnlyFilter(string SQLStatement)
{
const string c_RegEx = @"
(UPDATE\s[\w]+\sSET\s[\w\,\'\=]+)|
(INSERT\sINTO\s[\d\w]+[\s\w\d\)\(\,]*\sVALUES\s\([\d\w\'\,\)]+)|
(DELETE\sFROM\s[\d\w\'\=]+)|
(ALTER(\s|\+)\w+)|
(EXEC(\s|\+)+(s|x)p\w+)
";

Regex regex = new Regex(c_RegEx, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
Match mtch = regex.Match(SQLStatement);

if (mtch.Success == true)
{
bIsOK = false;
sBadPattern = mtch.ToString();
}
else
{
bIsOK = true;
}

}

public bool OK
{
get
{
return bIsOK;
}
}

public string BadPattern
{
get
{
return sBadPattern;
}
}
}
}

Thursday, January 25, 2007

DataTableGenerator: Generate a DataTable from any class

In any modern application, you deal with a lot of custom classes, like Employee, Article, or Manager - each of them having their own properties, member variables, and methods.

Often, you need a quick way to capture the current values of the class at once, view the current data contained in them, or simply transform all values to an XML file.

Luckily, there is class that is able to do all of this already: the DataTable. A DataTable can contain thousands of rows, dozens of columns, can be easily viewed by using a grid, and finally, using XmlSerializer, can be turned easily to an XML file.

The only question is how a custom class can transformed to a DataTable? Well, this is exactly where Xteq.Data.DataTableGenerator jumps in I describe at CodeProject.

Sunday, May 08, 2005

Creating an event message file for the event log with Delphi

When creating a service application you usually log important events to the Windows event log by using the Windows API function ReportEvent().

This works without any problems with Delphi, however since Windows expects a so called message file to display the logged events using eventvwr.exe, the user will see all your events starting with "The description of the event id xxx was not found in…". This is because you did not provide an event message file and thus Windows displays this stupid message. Proving an event message file is very simple and I'll show you how.

First of all, you need to understand why Windows relies on event message files. When Windows NT was created (the first Windows with an event log) one goal of the development team was globalization. This means that Windows NT could be easily transferred into any language. Since an event usually contains one part that is always the same (e.g. "The following file was not found: ") and a variable part, like the filename that was not found, the team had the idea of event message files.

Inside the event message file, the common part will be noted like "The following file was not found:". The data (like the file name) will be submitted by the application logging the event and joined with the event message file. This way, the final message would look like "The following file was not found: C:\test\arg.txt". Using this technique, you could easily give the event message file a translator and let him translate the text inside this file. For example, the German translation might read "Die folgende Datei wurde nicht gefunden: ". When you then install this translated message file, all messages of the program are translated without a single change inside the application itself. This is because the server application does not contain the language-specific strings, it does only write an ID (e.g. 100) and the variable data. The text for ID 100 will then be retrieved from the translated event message file.

So much about the theory, let's get this rolling. To create an event message file you first need to fire up Delphi and select File - New - DLL. Remove the "uses" statement completely since we won't need this.

If you want your DLL to later on have version information, you should directly enable this by using Project - Version Info - Include version info in project.

Save this project and compile it once. You should now have a DLL with round about 18 KB in size. Go to the folder where you saved this project and copy the *.RES file to a new file, e.g. eventlogmessages.res.

You'll now need the PE Resource Explorer from http://www.wilsonc.demon.co.uk/d7resourceexplorer.htm. Download, install it and run it. Select File - Open -> Type = Resource Files (*.RES) files and open the newly crated eventlogmessages.res.

Add a message table resource block by using Resource - Add Resource - Message Table. Since Delphi has properly created an Icon Group resource, open this branch and delete the icon at the end of this branch by using Resource - Delete Resource, we do not need any icon in this file.

Now, open the Message Table resource, open the "1" key and finally open the "Language Neutral" branch. On the right side of the Window you will now see a list view of all messages defined in this file. Of course the list is empty so right click the list and select "Add String". PE Resource Explorer will automatically add a string with the ID of 1 and we can enter some text. Because we first want to test this feature, we'll enter "My cool event. Data: [%1] Even more text". This does of course not make any sense; it is simply a demonstration how the event message file works.

Save the filer and exit PE Resource Explorer. Back in Delphi, change the line " {$R *.RES}" to be " {$R eventlogmessages.RES}" and finally recompile it. This will merge the event message with have just created into the DLL so we have now an event message file!

However, at this time the event log viewer (eventvwr.exe) does not know that this event message file exists, so we need to a little bit more to register our file with Windows.

The event log viewer expects to find all event message files in the registry path HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application\[Application Name]. For example, if your application uses ReportEvent (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/reporting_an_event.asp) with the event source name of "MyCoolApp" the registry path has to be HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application\MyCoolApp. Below this path there are only two values that need to be set.

The first is of type REG_SZ or REG_EXPAND_SZ (if you want to work with %VARIABLE%) with the name of "EventMessageFile". This value should point to the event message file, for example c:\Projects\EventLogDll\eventlogdll.dll.

Secondly, it should contain a REG_DWORD value with name "TypesSupported" that will simply tell Windows will types of events your application logs. This is a bit mask of the following values:

1 = Error
2 = Warning

4 = Information

8 = Audit Success

16 = Audit Failure

So, for example your application only logs Warnings and Errors, you would enter 3 (decimal) into this field (Warnings (2) + Error (1) = 3). If you do not want to use this bit mask, simply enter decimal 31 (HEX: 1f) which will tell Windows that your application can log any event type.

Well, this is it. When you now use ReportEvent() with the event source name of "MyCoolApp" and the ID of 1 and a event data of "x234" the Event Viewer will display:

My cool event. Data: [x234] Even more text.

For a very simple application, you might decide to only have one entry in the message table with the ID of 1: "%1". This will add no additional text to your message but instead simply write whatever the application logs. Of course, this means all your events will have the ID of "1" regardless of what the text says.

Most administrators expect a service to log different IDs for different events so they are easily identified. For example, you might decide that event 100 should by "Application started", event 200 "Application ended", 101 stands for "Fatal Exit" and so on.

How many events you log is up to you but you should always keep in mind that each event ID you log using the service, you'll need to provide in the event message file as well.

In this case please remember that the IDs inside a message table are noted in HEX, while the event viewer will display them decimal. For example, if you want an event with the ID 100, you need to write event 100 with ReportEvent() and the event viewer will also display this event as 100. However, inside the event message file, the ID for this event would be "64" (Dec 100 = Hex 64). If you would write the ID 100 into the event message file, this would be the text for the event 256 (Dec 256 = Hex 100). Please keep this in mind.