Click here to Skip to main content
15,867,686 members
Articles / Desktop Programming / MFC
Article

Simple class to fire messages to Windows EventLog

Rate me:
Please Sign up or sign in to vote.
4.67/5 (6 votes)
27 Oct 20043 min read 74.7K   1.8K   35   8
Shows a simple class to fire messages to the Windows EventLog.

Sample Image - CEventLog.jpg

Introduction

When writing my new application, I thought it was fine if my app would fire messages to the Windows EventLog. Based on the code of the article "Using MC.exe, message resources and the NT event log in your own projects" published by Daniel Lohmann, I designed a class that does it all for you.

Background

Well, to do this, you have to create a message resource using the MC.EXE from the DevStudio (for details, click here). Just that there is a small change (or bug) in the article mentioned above. The calling convention of the MC.EXE needs to corrected. In the article, it is described as:

mc.exe -A "$(InputDir)\$(InputName).mc" 
       -r "$(InputDir)\res" -h "$(InputDir)" <FONT color=#ff0000>// this is false!!</FONT>

Instead, the correct entry would be:

mc.exe -r "$(InputDir)\res" 
  -h "$(InputDir)"    "$(InputDir)\$(InputName).mc" <FONT color=#008000>// this is correct!!</FONT>

The rest of the article worked fine for me. I have skipped describing the syntax and generation of message resources, though I would like you to refer to the article mentioned above.

One weird thing to know about!

To fire messages to the EventLog, you have to register your application first. On doing this, you are returned a handle to the EventLog. Bernd Lohman writes in his article (at least, this was my understanding), that this handle will be NULL until you have added all necessary entries to the Windows Registry.

My experience of that is a little different: Even if your application is not fully qualified within the Windows Registry, you will receive a valid handle and be able to fire messages. The tragic of this is that if you later open your message, EventLog is unable to resolve the message resources of at least "Category" (German: "Kategorie") (and probably some others too). EventLog does not store these strings at the time when the message is fired, but "on the fly" while scanning through these events.

EventLog.h

The code is as simple as it could be. It is just one class, containing all necessary methods you need to fire messages towards the EventLog.

// EventLog.h: Interface for class CEventLog.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_EVENTLOG_H__7D48CC33_4E41_4E0C_B16A_5FC714CAC457__INCLUDED_)
#define AFX_EVENTLOG_H__7D48CC33_4E41_4E0C_B16A_5FC714CAC457__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include <afxwin.h>

class CEventLog : public CObject  
{
  DECLARE_DYNCREATE(CEventLog)

public:
  CEventLog(void);
  virtual ~CEventLog(void);

  BOOL Initialize(CString csApp);
  HANDLE GetHandle(void){return m_hLog;};

  BOOL Fire(WORD wType, WORD wCategory, DWORD dwEventID, ...);
  BOOL FireWithData(WORD wType, WORD wCategory, 
       DWORD dwEventID, DWORD dwData, LPVOID ptr, ...);

  CString LoadMessage( DWORD dwMsgId, ... );
  BOOL LaunchViewer(void);

  DWORD AddEventSource(CString csName, 
                       DWORD dwCategoryCount = 0);
  DWORD RemoveEventSource(CString csApp);

protected:
  PSID GetUserSID(PSID * ppSid);

protected:
  HANDLE m_hLog;
};

extern CEventLog theEventLog;
#endif
// !defined(AFX_EVENTLOG_H__7D48CC33_4E41_4E0C_B16A_5FC714CAC457__INCLUDED_)

What are these methods for?

The constructor CEventLog(void); only does some initializations. The destructor method ~CEventLog(void); releases the allocated EventLog-Handle (if not already done previously).

  • Initialize(CString csApp); is called very early from within your application (e.g., within CYourApp::InitInstance();). It tries to ensure that your application is fully qualified within the Windows Registry for the usage of the EventLog. Additionally, it registers the handle to the EventLog.
  • GetHandle(void); simply returns the allocated handle to the EventLog.
  • Fire(WORD wType, WORD wCategory, DWORD dwEventID, ...) then fires a message without additional binary data to the EventLog.

    Note: When calling this method, the last parameter needs to be an empty string ("").

  • FireWithData(WORD wType, WORD wCategory, DWORD dwEventID, DWORD dwData, LPVOID ptr, ...) then fires a message with additional binary data to the EventLog.

    Note: When calling this method, the last parameter needs to be an empty string ("").

  • LoadMessage( DWORD dwMsgId, ... ) simply formats your own messages for other usage (e.g., MessageBox etc.).
  • LaunchViewer(void); gives you an easy way to open the EventLog from within your application.
  • AddEventSource(CString csName, DWORD dwCategoryCount = 0); fully qualifies your application within the Windows Registry.
  • RemoveEventSource(CString csApp); de-qualifies your application from the Windows Registry.
  • GetUserSID(PSID * ppSid); is for internal usage to return the SID of the currently logged on user.

EventLog.ccp

I'm sorry, but due to lack of time when writing this class, I skipped most comments within the source code. :'-(

#include "stdafx.h"
##include "EventLog.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Konstruktion/Destruktion
//////////////////////////////////////////////////////////////////////

IMPLEMENT_DYNAMIC(CEventLog, CObject)

CEventLog theEventLog;

CEventLog::CEventLog()
{
  m_hLog = NULL;
}

CEventLog::~CEventLog()
{
  if (m_hLog != NULL)
  {
    DeregisterEventSource(m_hLog);
    m_hLog = NULL;
  }
}


BOOL CEventLog::Initialize(CString csApp)
{
  // Try to add application to EventVwr
  if (AddEventSource(csApp, 3 ) != 0)
  {
    CString cs;
    cs.Format("Unable to register EventLog access for application %s.", cs);
    cs += "  Please log in with admin rights to do this.";
    cs += "  \nApplication will run without event logging";
    AfxMessageBox(cs, MB_ICONEXCLAMATION);
  }

  // Register to write
  m_hLog = ::RegisterEventSource( NULL, csApp);

  return TRUE;
}

DWORD CEventLog::AddEventSource(CString csName, DWORD dwCategoryCount)
{
  HKEY hRegKey = NULL; 
  DWORD dwError = 0;
  TCHAR szPath[ MAX_PATH ];
    
  _stprintf( szPath, _T("SYSTEM\\CurrentControlSet\\
                         Services\\EventLog\\Application\\%s"), csName );

  // Create the event source registry key
  dwError = RegCreateKey( HKEY_LOCAL_MACHINE, szPath, &hRegKey );
  // This error is ignored
  // if (dwError != 0)
  //   return dwError;

  // Name of the PE module that contains the message resource
  GetModuleFileName( NULL, szPath, MAX_PATH );

  // Register EventMessageFile
  dwError = RegSetValueEx( hRegKey, _T("EventMessageFile"), 0, 
                           REG_EXPAND_SZ, (PBYTE) szPath, 
                           (_tcslen( szPath) + 1) * sizeof TCHAR ); 
  if (dwError == 0)
  {
    // Register supported event types
    DWORD dwTypes = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | 
                    EVENTLOG_INFORMATION_TYPE; 
    dwError = RegSetValueEx( hRegKey, _T("TypesSupported"), 
              0, REG_DWORD, (LPBYTE) &dwTypes, sizeof dwTypes );

    // If we want to support event categories, we have
    // also to register the CategoryMessageFile.
    // and set CategoryCount. Note that categories need
    // to have the message ids 1 to CategoryCount!

    if (dwError == 0 && dwCategoryCount > 0 ) 
    {
      dwError = RegSetValueEx( hRegKey, _T("CategoryMessageFile"), 0, 
                               REG_EXPAND_SZ, (PBYTE) szPath, 
                               (_tcslen( szPath) + 1) * sizeof TCHAR );
      if (dwError == 0)
        dwError = RegSetValueEx( hRegKey, _T("CategoryCount"), 0, 
                  REG_DWORD, (PBYTE) &dwCategoryCount, 
                  sizeof dwCategoryCount );
    }
  }

  RegCloseKey( hRegKey );

  return dwError;
}

DWORD CEventLog::RemoveEventSource(CString csApp)
{
  DWORD dwError = 0;
  TCHAR szPath[ MAX_PATH ];

  _stprintf( szPath, _T("SYSTEM\\CurrentControlSet\\Services
                         \\EventLog\\Application\\%s"), csApp );
  return RegDeleteKey( HKEY_LOCAL_MACHINE, szPath );
}

CString CEventLog::LoadMessage(DWORD dwMsgId, ...)
{
  char  pszBuffer[1024];
  DWORD cchBuffer = 1024;

  va_list args;
  va_start( args, cchBuffer );
    
  if (FormatMessage( FORMAT_MESSAGE_FROM_HMODULE,
                     // Module (e.g. DLL) to search
                     // for the Message. NULL = own .EXE
                     NULL,
                     // Id of the message to look up (aus "Messages.h")
                     dwMsgId,
                     // Language: LANG_NEUTRAL = current thread's language
                     LANG_NEUTRAL,
                     // Destination buffer
                     pszBuffer,
                     // Character count of destination buffer
                     cchBuffer,
                     // Insertion parameters
                     &args
                   ))

    return pszBuffer;
  else
    return "";
}

BOOL CEventLog::Fire(WORD wType, WORD wCategory, DWORD dwEventID, ...)
{
  PSID sid = NULL;
  va_list args;
  va_start( args, dwEventID );

  CString cs;
  int iCount = 0;

  while(1)
  {
    char *p = va_arg( args, char *);
    if (*p != '\0')
      iCount++;
    else
      break;
  }

  // Jump to beginning of list
  va_start( args, dwEventID );

  if (m_hLog == NULL)
    return FALSE;

  BOOL bRet = ReportEvent(m_hLog, wType, wCategory, dwEventID, 
                          GetUserSID(&sid), iCount, 0, 
                          (LPCTSTR *)args, NULL);
  va_end(args);
  if (sid != NULL)
    delete [] sid;
  return bRet;
}

BOOL CEventLog::FireWithData(WORD wType, WORD wCategory, 
                DWORD dwEventID, DWORD dwData, LPVOID ptr,...)
{
  PSID sid = NULL;
  va_list args;
  va_start( args, ptr );

  CString cs;
  int iCount = 0;

  while(1)
  {
    char *p = va_arg( args, char *);
    if (*p != '\0')
      iCount++;
    else
      break;
  }

  // Jump to beginning of list
  va_start( args, ptr );

  if (m_hLog == NULL)
    return FALSE;

  BOOL bRet = ReportEvent(m_hLog, wType, wCategory, dwEventID, 
                          GetUserSID(&sid), iCount, dwData, 
                          (LPCTSTR *)args, ptr);
   va_end(args);
  if (sid != NULL)
    delete [] sid;
  return bRet;
}

BOOL CEventLog::LaunchViewer()
{
  CString csVwr = "%SystemRoot%\\system32\\eventvwr.msc", csParam = " /s";
  CString csVwrExpand, csDefaultDir, csMsg;
  long lErr = ExpandEnvironmentStrings(csVwr, 
              csVwrExpand.GetBufferSetLength(MAX_PATH), MAX_PATH);
  if (lErr == 0)
    return FALSE;

  csVwrExpand.ReleaseBuffer();
  int iPos = csVwrExpand.ReverseFind('\\');
  if (iPos != -1)
    csDefaultDir = csVwrExpand.Left(iPos);

  long hinst = (long)::FindExecutable(csVwrExpand, csDefaultDir, 
                       csVwr.GetBufferSetLength(MAX_PATH));
  csVwr.ReleaseBuffer();
  switch (hinst)
  {
    case 0:
      AfxMessageBox("The system is out of memory or resources.", MB_ICONSTOP);
      return  FALSE;
    case 31:
      csMsg.Format("No association for file type of '%s' found.", csVwrExpand);
      AfxMessageBox(csMsg, MB_ICONSTOP);
      return  FALSE;
    case ERROR_FILE_NOT_FOUND:
      csMsg.Format("File '%s' not found.", csVwrExpand);
      AfxMessageBox(csMsg, MB_ICONSTOP);
      return  FALSE;
    case ERROR_PATH_NOT_FOUND:
      csMsg.Format("Path of file '%s' not found.", csVwrExpand);
      AfxMessageBox(csMsg, MB_ICONSTOP);
      return  FALSE;
    case ERROR_BAD_FORMAT:
      csMsg.Format("The executable file '%s' is invalid 
                    (non-Win32® .exe or error in .exe image).", csVwr);
      AfxMessageBox(csMsg, MB_ICONSTOP);
      return  FALSE;
    default:
      if (hinst < 32)
      {
        csMsg.Format("Unknown error %d returned from FindExecutable().", hinst);
        AfxMessageBox(csMsg, MB_ICONSTOP);
        return  FALSE;
      }
      break;
  }

  hinst = (long)::ShellExecute(NULL, "open", csVwr, csVwrExpand + csParam, 
                                             csDefaultDir, SW_SHOWNORMAL);
  switch (hinst)
  {
    case 0:
      AfxMessageBox("The operating system is out 
                     of memory or resources.", MB_ICONSTOP);
      return FALSE;
    case ERROR_FILE_NOT_FOUND:
      csMsg.Format("File '%s' not found.", csVwr);
      AfxMessageBox(csMsg, MB_ICONSTOP);
      return FALSE;
    case ERROR_PATH_NOT_FOUND:
      csMsg.Format("Path of file '%s' not found.", csVwr);
      AfxMessageBox(csMsg, MB_ICONSTOP);
      return FALSE;
    case ERROR_BAD_FORMAT:
      csMsg.Format("The executable for file '%s' is invalid 
                    (non-Win32® .exe or error in .exe image).", csVwr);
      AfxMessageBox(csMsg, MB_ICONSTOP);
      return FALSE;
    case SE_ERR_ACCESSDENIED:
      csMsg.Format("The operating system denied access to file '%s'.", csVwr);
      AfxMessageBox(csMsg, MB_ICONSTOP);
      return FALSE;
    case SE_ERR_ASSOCINCOMPLETE:
      csMsg.Format("Name association for file %s' is incomplete 
                    or invalid.", csVwr);
      AfxMessageBox(csMsg, MB_ICONSTOP);
      return FALSE;
    case SE_ERR_DDEBUSY:
      AfxMessageBox("The DDE transaction could not be completed 
                     because other DDE transactions were being processed.", 
                     MB_ICONSTOP);
      return FALSE;
    case SE_ERR_DDEFAIL:
      AfxMessageBox("The DDE transaction failed.", MB_ICONSTOP);
      return FALSE;
    case SE_ERR_DDETIMEOUT:
      AfxMessageBox("The DDE transaction could not be completed 
                     because the request timed out.", MB_ICONSTOP);
      return FALSE;
    case SE_ERR_DLLNOTFOUND:
      AfxMessageBox("The specified dynamic-link library 
                     was not found.", MB_ICONSTOP);
      return FALSE;
    case SE_ERR_NOASSOC:
      csMsg.Format("No association for file type of '%s' found.", csVwr);
      AfxMessageBox(csMsg, MB_ICONSTOP);
      return FALSE;
    case SE_ERR_OOM:
      AfxMessageBox("The system is out of memory or resources.", MB_ICONSTOP);
      return FALSE;
    case SE_ERR_SHARE:
      AfxMessageBox("A sharing violation occurred.", MB_ICONSTOP);
      return FALSE;
    default:
      if (hinst < 32)
      {
        csMsg.Format("Unknown error %d returned from ShellExecute().", hinst);
        AfxMessageBox(csMsg, MB_ICONSTOP);
        return FALSE;
      }
      return TRUE;
  }
  return FALSE;
}

PSID CEventLog::GetUserSID(PSID * ppSid)
{
  BOOL bRet = FALSE;
  const DWORD INITIAL_SIZE = MAX_PATH;

  CString csAccName;
  DWORD size = INITIAL_SIZE;

  ::GetUserName(csAccName.GetBufferSetLength(size), &size);
  csAccName.ReleaseBuffer(size);

  // Validate the input parameters.
  if (csAccName.IsEmpty() || ppSid == NULL)
  {
    return NULL;
  }


  // Create buffers.
  DWORD cbSid = 0;
  DWORD dwErrorCode = 0;
  DWORD dwSidBufferSize = INITIAL_SIZE;
  DWORD cchDomainName = INITIAL_SIZE;
  CString csDomainName;
  SID_NAME_USE eSidType;
  HRESULT hr = 0;


  // Create buffers for the SID.
  *ppSid = (PSID) new BYTE[dwSidBufferSize];
  if (*ppSid == NULL)
  {
    return NULL;
  }
  memset(*ppSid, 0, dwSidBufferSize);


  // Obtain the SID for the account name passed.
  for ( ; ; )
  {

    // Set the count variables to the buffer sizes and retrieve the SID.
    cbSid = dwSidBufferSize;
    bRet = LookupAccountName(NULL, csAccName, *ppSid, &cbSid, 
                             csDomainName.GetBufferSetLength(cchDomainName), 
                             &cchDomainName,&eSidType);
    csDomainName.ReleaseBuffer();
    if (bRet)
    {
      if (IsValidSid(*ppSid) == FALSE)
      {
        CString csMsg;
        csMsg.Format("The SID for %s is invalid.\n", csAccName);
        AfxMessageBox(csMsg, MB_ICONSTOP);
        bRet = FALSE;
      }
      break;
    }
    dwErrorCode = GetLastError();


    // Check if one of the buffers was too small.
    if (dwErrorCode == ERROR_INSUFFICIENT_BUFFER)
    {
      if (cbSid > dwSidBufferSize)
      {

        // Reallocate memory for the SID buffer.
        TRACE("The SID buffer was too small. It will be reallocated.\n");
        FreeSid(*ppSid);
        *ppSid = (PSID) new BYTE[cbSid];
        if (*ppSid == NULL)
        {
          return NULL; 
        }
        memset(*ppSid, 0, cbSid);
        dwSidBufferSize = cbSid;
      }
    }
    else
    {
      CString csMsg;
      csMsg.Format("LookupAccountNameW failed. 
                    GetLastError returned: %d\n", dwErrorCode);
      AfxMessageBox(csMsg, MB_ICONSTOP);
      hr = HRESULT_FROM_WIN32(dwErrorCode);
      break;
    }
  }
 
  // If we had an error, free memory of SID
  if (!bRet && *ppSid != NULL)
  {
    delete [] *ppSid;
    *ppSid = NULL; 
  }

  return *ppSid;
 }

What else do you have to do?

Well, since CEventLog is created as global object, all you have to do is to add #include "EventLog.h" wherever you want to fire a message. Don't forget to #include ".\res\yourmessages.h" file to stdafx.h, so your category and event constants are known everywhere. Then, within the code, call Fire(WORD wType, WORD wCategory, DWORD dwEventID, ...) or FireWithData(WORD wType, WORD wCategory, DWORD dwEventID, DWORD dwData, LPVOID ptr, ...). That's it.

Sample CYourApp.cpp

...
#include "EventLog.h"
...
BOOL CYourApp::InitInstance()
{
  CString csUserName;
  DWORD size = MAX_PATH;

  // Get name of current user.
  ::GetUserName(csUserName.GetBufferSetLength(MAX_PATH), &size);
  csUserName.ReleaseBuffer(size);
  ...
  // Initliaize CEventLog.
  theEventLog.Initialize(m_pszAppName);
  // Fire message, that application is up an running.
  theEventLog.Fire(EVENTLOG_INFORMATION_TYPE, CATEGORY_ONE, 
                   EVENT_STARTED_BY, m_pszAppName, csUserName, "");
                   // last parameter is an empty string
}
...
BOOL CYourApp::ExitInstance()
{
  CString csUserName;
  DWORD size = MAX_PATH;

  // Get name of current user.
  ::GetUserName(csUserName.GetBufferSetLength(MAX_PATH), &size);
  csUserName.ReleaseBuffer(size);
  ...
 // Fire message, that application has endend properly.
  theEventLog.Fire(EVENTLOG_INFORMATION_TYPE, CATEGORY_ONE, 
                   EVENT_STOPPED_BY, m_pszAppName, csUserName, "");
                   // last parameter is an empty string
}
...

Disclaimer

Now, that's it so far, this should help you along. Feel free to use and change this code to your own needs and extent.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
CEO
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionDo you have resources? Tnx! Pin
Member 153320126-Feb-22 8:49
Member 153320126-Feb-22 8:49 
QuestionComment by "kjhsdfksdhfjkds ": The documentation of VerQueryInfo is useless Pin
luetz12-Dec-11 23:16
luetz12-Dec-11 23:16 
GeneralUsing with Visual Studio 2005+ Pin
tontobiker18-Jul-08 0:50
tontobiker18-Jul-08 0:50 
Generalbug in LoadMessage() Pin
zhao wei31-Mar-08 17:10
zhao wei31-Mar-08 17:10 
GeneralUsing FireWithData method... Pin
meraydin4-Jul-05 15:37
meraydin4-Jul-05 15:37 
QuestionRe: Using FireWithData method... Pin
mohammadmot26-Jan-10 21:53
mohammadmot26-Jan-10 21:53 
GeneralCorrection... Pin
Morteza_Ar15-Nov-04 4:41
Morteza_Ar15-Nov-04 4:41 
GeneralRe: Correction... Pin
luetz15-May-07 21:06
luetz15-May-07 21:06 
Thanks for this notification and under this circumstance I honestly aplogize to have call it FALSE.

And many thanks too for updating me and my intermediate knowledge Big Grin | :-D

Cheers Luetz

Hartmut Luetz-Hawranke
tetronik GmbH AEN
http://www.tetronik.com/

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.