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.
#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
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
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)
{
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);
}
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 );
dwError = RegCreateKey( HKEY_LOCAL_MACHINE, szPath, &hRegKey );
GetModuleFileName( NULL, szPath, MAX_PATH );
dwError = RegSetValueEx( hRegKey, _T("EventMessageFile"), 0,
REG_EXPAND_SZ, (PBYTE) szPath,
(_tcslen( szPath) + 1) * sizeof TCHAR );
if (dwError == 0)
{
DWORD dwTypes = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE |
EVENTLOG_INFORMATION_TYPE;
dwError = RegSetValueEx( hRegKey, _T("TypesSupported"),
0, REG_DWORD, (LPBYTE) &dwTypes, sizeof dwTypes );
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,
NULL,
dwMsgId,
LANG_NEUTRAL,
pszBuffer,
cchBuffer,
&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;
}
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;
}
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);
if (csAccName.IsEmpty() || ppSid == NULL)
{
return NULL;
}
DWORD cbSid = 0;
DWORD dwErrorCode = 0;
DWORD dwSidBufferSize = INITIAL_SIZE;
DWORD cchDomainName = INITIAL_SIZE;
CString csDomainName;
SID_NAME_USE eSidType;
HRESULT hr = 0;
*ppSid = (PSID) new BYTE[dwSidBufferSize];
if (*ppSid == NULL)
{
return NULL;
}
memset(*ppSid, 0, dwSidBufferSize);
for ( ; ; )
{
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();
if (dwErrorCode == ERROR_INSUFFICIENT_BUFFER)
{
if (cbSid > dwSidBufferSize)
{
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 (!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;
::GetUserName(csUserName.GetBufferSetLength(MAX_PATH), &size);
csUserName.ReleaseBuffer(size);
...
theEventLog.Initialize(m_pszAppName);
theEventLog.Fire(EVENTLOG_INFORMATION_TYPE, CATEGORY_ONE,
EVENT_STARTED_BY, m_pszAppName, csUserName, "");
}
...
BOOL CYourApp::ExitInstance()
{
CString csUserName;
DWORD size = MAX_PATH;
::GetUserName(csUserName.GetBufferSetLength(MAX_PATH), &size);
csUserName.ReleaseBuffer(size);
...
theEventLog.Fire(EVENTLOG_INFORMATION_TYPE, CATEGORY_ONE,
EVENT_STOPPED_BY, m_pszAppName, csUserName, "");
}
...
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.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.