Anda di halaman 1dari 31

Using the Windows Event Log from Visual FoxPro

Using the Windows Event Log


from Visual FoxPro
Craig Berntson Cole Gleave
Email: craig@craigberntson.com cgleave@yahoo.com
3M Health Information Systems 3M Health Information Systems

Overview
In this article, you will learn how to use the Windows Event Log from Visual FoxPro. While you
can use Windows Scripting Host (WSH) to read and write to the Windows Event Log, WSH
could be disabled due to today s security requirements. Therefore, it is necessary to use the
Win32 API to access the log. Using several API calls, you will learn how to read, write, and
maintain the log.

Logging Overview
Capturing error conditions is an important part of a robust application. Historically, Visual
FoxPro applications have used tables (DBFs) or text files to capture error information. However,
other types of logging are available. These include XML files or the Windows Event Log.

Using the Windows Event Log is desirable because other file types use proprietary formats,
different user interfaces for reporting, and they cannot be merged with logs from other
applications. The Windows Event Log is a standard format, stored in a centralized location and
single user interface can be used to view log information from a variety of programs. Also, third
party software can be purchased that will report on entries stored in the Windows Event Log.

In addition to error conditions, the Windows Event Log can be used to store information such as
a user logging on, opening a database, starting a file transfer, or other information. The Windows
Event Log should not be used as a tracing tool or to record transactional information such as
database changes.

The Windows Event Log actually contains several types of logs. The standard logs and what
they are used for are listed in Table 1. Under Windows 2000 and later you may find User
Defined types. I will talk more about these later in this document. Your programs will make
registry entries under the Application or a User Defined type.

The Windows Event Log is actually a group of files. Each file represents a different log type.
The location of these files is specified by Registry entries.

Craig Berntson, Cole Gleave, 2004, 2005


Page 1 of 31
Using the Windows Event Log from Visual FoxPro

Table 1. A listing of the standard log types and their locations.


Log Type Description File Location
Security Security information such as logon, logoff, \WinNT\System32\Config\SecEvent.Evt
password changes, etc. This log is reserved
by Windows.
System Information from Windows. This log is \WinNT\System32\Config\SysEvent.Evt
reserved by Windows.
Application Information from application software. \WinNT\System32\Config\AppEvent.Evt

Each entry in the event log contains the following information:

Date
Time
Username: The user that wrote the entry.
Computer: The name of the computer that wrote the error. Generally, you will use the
local event log. However, if you have the proper rights, you can log information to a
server or other computer.
Source: The application, module, or component that wrote the event.
Message Type: Can be Success, Error, Warning, Information, Audit Success or Audit
Failure.
Category: Specified by a message file. Message files are discussed later in this document
Event ID: Specified by a message file.
Description ID: Specified by a message file.
Binary Data

The primary way to view information from the event log is the Event Log Viewer (Figure 1).
Under Windows 2000 or later, you access the Viewer from the Administrative Tools applet of
Windows Control Panel.

Craig Berntson, Cole Gleave, 2004, 2005


Page 2 of 31
Using the Windows Event Log from Visual FoxPro

Figure 1. The Event Viewer is used to view and manage the Windows Event Log.

When you double-click on an entry, the Event Properties dialog (Figure 2) for that event is
displayed. You can see from Figure XX that not all the information is required. For example, the
Category and User are not specified. The Description is actually loaded from a message file.

Figure 2. You can view properties of a specific event.

Using the Event Log


There are several things you need to do to use the event log, but by creating generic message
files and reusing code, it becomes easier. The three steps to use the Windows Event Log are:

1. Create the Message Files


2. Register the Event Source
3. Call the Event Log API

Step three consists of several Win32 API calls and is discussed in detail later in this document.

Creating Message Files


Message files are resource DLLs that store the Category, Event ID, and Message ID for a
particular event. You can use a separate resource DLL for each item or combine all three items
into a single file and message files can be shared by multiple event sources.

Craig Berntson, Cole Gleave, 2004, 2005


Page 3 of 31
Using the Windows Event Log from Visual FoxPro

To create a resource DLL you will need the Resource Compiler, Message Compiler, and Linker.
These tools ship with Visual Studio, the Microsoft Platform SDK, and other developer tools. I
did an Internet search and got hits for free tools that may give you the same functionality.

The following steps walk you through creating a message file.

1. Create a text file for the messages. This example uses FoxMessages.mc.

LanguageNames =
(
English = 0x0409:Messages_ENU
)
;////////////////////////////////////////
;// Eventlog categories
;//
;// These always have to be the first entries in a message file

MessageId = 1
SymbolicName = CATEGORY_ONE
Severity = Success
Language = English
First category event
.

MessageId = 2
SymbolicName = CATEGORY_TWO
Severity = Success
Language = English
Second category event
.

;////////////////////////////////////////
;// Events

MessageId = 1000
SymbolicName = HELLO
Language = English
Hello World!
.

MessageId = 1001
SymbolicName = GREETING
Language = English
Hello %1. How are you?
.

MessageId = 1002
SymbolicName = GENERIC1
Language = English
%1
.
MessageId = 1003
SymbolicName = GENERIC2
Language = English
Message 1: %1 Message 2: %2

Craig Berntson, Cole Gleave, 2004, 2005


Page 4 of 31
Using the Windows Event Log from Visual FoxPro

2. In the Windows Command Interpreter, compile the message file using the Message
Compiler.

mc FoxMessages.mc

The message compiler outputs the following files:

FoxMessages.h: A C++ header file containing the event information


FoxMessages.rc: A Resource source file. This will be the input for resource compiler.
Messages_ENU.bin: A binary file for US English messages.

3. In the Windows Command Interpreter, compile the resource file using the Resource
Compiler.

rc FoxMessages.rc

The Resource Compiler will create the resource file FoxMessages.res.

4. In the Windows Command Interpreter, link the resource file.

link -dll -noentry -out:FoxMessages.dll FoxMessages.res

The dll parameter tells the linker to create a .dll file (FoxMessages.dll) and noentry
says there will not be entry point. This is required because the linker is not creating a
Win32 dll, but rather a resource dll.

The resource dll is now ready to use. The sample code that accompanies this article includes the
batch file Make.bat that does all three steps for you.

Updating the Registry


Before you can use the resource dll, you need to make a registry entry so that Windows can find
it when the event is read and written. You can use the Registry Fox Foundation Class to do this
programmatically, make the entry when you install the application, or edit the registry manually
using RegEdit.

However you do it, the entry goes in HKEY_LOCAL_MACHINE\SYSTEM\


ControlSet001\Services\EventLog\Application. If you want your event information to go under
the Application log, use the above location. If you want a custom log, say, MyApps , create a
new entry called MyApp at the same level as Application. Next, make an entry for your

Craig Berntson, Cole Gleave, 2004, 2005


Page 5 of 31
Using the Windows Event Log from Visual FoxPro

application name. In this example, I m using HKEY_LOCAL_MACHINE\SYSTEM\


ControlSet001\Services\EventLog\Application\FoxMessages. Here are the steps I used:

1. Run Regedit and drill down until you reach the above key (Figure 3)

Figure 3. The Registry Editor is used to view and manage entries in the Windows registry.

2. Right-click on Application and select New | Key from the context menu. Enter
FoxMessages as the key.

3. Right-click on FoxMessages and select New | String Value. Enter EventMessageFile.

4. Double-click EventMessageFile. The Edit String dialog (Figure 4) is displayed. Enter the
path to your resource dll and click OK.

Figure 4. You can add or change an entry in the registry from the Edit String dialog.

Craig Berntson, Cole Gleave, 2004, 2005


Page 6 of 31
Using the Windows Event Log from Visual FoxPro

5. Right-click on FoxMessages and select New | DWORD Value. Enter TypesSupported.

6. Double-click on TypesSupported. The Edit DWORD Value dialog (Figure 5) is


displayed. Enter 7 for Value data so that the log entry can support all types. The types are
listed in Table 2. Make sure Hexadecimal is selected, and then click OK.

Figure 5. You can add or edit a DWORD in the registry using the Edit DWORD Value dialog.

Table 2. The Bitmask values for the TypesSupported registry entry.


Description Hex Value Binary Value
EVENT_LOG_ERROR_TYPE 0x0001 1
EVENT_LOG_WARNING_TYPE 0x0002 2
EVENT_LOG_INFORMATION_TYP 0x0004 4
E

7. Add an entry for CategoryMessageFile. The Value will be the same path and file name
you entered for EventMessageFile.
8. Add another entry for CategoryCount. The Value will be 2, as that is the number of
categories in the message file.

The Registry Editor should now look like Figure 6.

Craig Berntson, Cole Gleave, 2004, 2005


Page 7 of 31
Using the Windows Event Log from Visual FoxPro

Figure 6. The Windows registry after making entries for FoxMessages.

9. Close the registry editor.


10. Reboot your computer.

You have completed the registry entries needed for your application. Next, I ll show you how to
write to the Windows Event Log.

Using the Win32 API


Writing to and reading from the event log requires that you declare Win32 API functions. Some
of these functions are part of the Event Logging API. Others are supporting functions.

It is common when using the Win32 API to make the function call then call another function to
check if an error occurred. The function to call is GetLastError( ). Most Win32 API functions set
the error condition when they fail. However, some set the error code when they succeed.
Because of this, you should always call GetLastError( ) after making a Win32 API call.

DECLARE INTEGER GetLastError IN WIN32API

Before you can write to an event source, you need to register it. The function returns a handle to
the event source or NULL if an error occurs. Pass a NULL or empty string as the first parameter
to use the Windows Event Log on the local computer.

DECLARE INTEGER RegisterEventSource IN WIN32API ;


STRING UNCServerName, ;

Craig Berntson, Cole Gleave, 2004, 2005


Page 8 of 31
Using the Windows Event Log from Visual FoxPro

STRING SourceName

The parameters for RegisterEventSource( ) are listed in Table 3.

Table 3. A listing of the parameters for the RegisterEventSource function.


Parameters Description
UNCServerName The server name where the event is logged. If logging to the local machine,
pass an empty string.
SourceName The source name that is saved to the Registry. Earlier, I named the source,
FoxMessage .

When you finish with log, call DeregisterEventSource( ), which has a single parameter,
hEventLog. This is the handle that was returned by RegisterEventSource( ).

DECLARE INTEGER DeregisterEventSource IN WIN32API ;


INTEGER hEventLog

The ReportEvent( ) function actually records the event into the log. If the message is
successfully recorded to the log, the return value is non-zero. If the function fails, a 0 is returned.

DECLARE INTEGER ReportEvent IN WIN32API ;


INTEGER EventLog, ;
INTEGER Type, ;
INTEGER Category, ;
INTEGER EventID, ;
INTEGER UserSid, ;
INTEGER NumStrings, ;
INTEGER DataSize, ;
STRING @Strings, ;
INTEGER RawData

The parameters are defined in Table 4.

Table 4. A listing of the parameters for the ReportEvent function.


Parameters Description
EventLog Handle to the event log. This is the handle returned by
RegisterEventSource( ).
Type Event type to log.
Category Event category. The categories are defined in the message file.
EventID The event identifier as defined in the message file.
UserSid Points to the user s security identifier to log the username. Can be NULL if
it is not required.
NumStrings The number of strings in the @Strings parameter.
DataSize The number of bytes in the RawData written to the log. If zero, no RawData
is present.
@Strings As defined in the Win32 API help file, this is an array of NULL terminated
strings to be merged into the message. In VFP, a function will be used to
create a string that mimics the C++ array.

Craig Berntson, Cole Gleave, 2004, 2005


Page 9 of 31
Using the Windows Event Log from Visual FoxPro

RawData Binary data to be stored to the log.

The event type (Type parameter) can be one of five values:

#DEFINE EVENTLOG_SUCCESS 0
#DEFINE EVENTLOG_ERROR_TYPE 1
#DEFINE EVENTLOG_WARNING_TYPE 2
#DEFINE EVENTLOG_INFORMATION_TYPE 4
#DEFINE EVENTLOG_AUDIT_SUCCESS 8
#DEFINE EVENTLOG_AUDIT_FAILURE 10

When reading the event log, the first thing you need to do is call OpenEventLog( ).

DECLARE INTEGER OpenEventLog IN WIN32API ;


STRING UNCServerName, ;
STRING UNCSourceName

If the function succeeds, it returns a handle to the event log. If it fails, it returns NULL. The
parameters are listed in Table 5.
Table 5. A listing of the parameters for the OpenEventLog function.
Parameters Description
UNCServerName The server name where the event is logged. If logging to the local machine,
pass an empty string.
SourceName The source name that is saved to the Registry. Earlier, I named the source,
FoxMessage .

When you finish using the event log, you should call the CloseEventLog( ) function.

DECLARE INTEGER CloseEventLog IN WIN32API ;


INTEGER hEventLog

The single parameter, hEventLog, is the handle returned by OpenEventLog( ). CloseEventLog( )


will return a non-zero value if it succeeds or zero if it fails.

Once the event log is open, it can be read. The event log API allows you to read a single entry at
a time using the ReadEventLog( ) function.

DECLARE INTEGER ReadEventLog IN WIN32API ;


INTEGER hEventLog, ;
INTEGER ReadFlags, ;
INTEGER RecordOffset, ;
STRING @Buffer, ;
INTEGER NumberOfBytesToRead, ;
STRING @BytesRead, ;
STRING @MinNumberOfBytesNeeded

Craig Berntson, Cole Gleave, 2004, 2005


Page 10 of 31
Using the Windows Event Log from Visual FoxPro

If successful, a non-zero value is returned. Zero is returned if the function fails. The parameters
are listed in Table 6.

Table 6. A listing parameters for the ReadEventLog function.


Parameters Description
hEventLog The handle to the event log.
ReadFlags Specifies how to read the log. See the #DEFINEs bellow.
RecordOffset The log entry number where the read operation should start.
@Buffer A buffer for the data read.
NumberOfBytesToRead The size, in bytes, of the buffer.
@BytesRead The number of bytes actually read.
@MinNumberOfBytesNeede The number of bytes required for the next log entry.
d

The values for ReadFlags are listed below.

#DEFINE EVENTLOG_SEQUENTIAL_READ 1
#DEFINE EVENTLOG_SEEK_READ 2
#DEFINE EVENTLOG_FORWARDS_READ 4
#DEFINE EVENTLOG_BACKWARDS_READ 8

Now that you ve seen the primary functions in the Event Log API, it s time to see some
additional functions you can do in the Windows Event Log. The first tells you how many records
are in the event log.

DECLARE INTEGER GetNumberOfEventLogRecords IN WIN32API ;


INTEGER hEventLog, ;
STRING @NumberOfRecords

The return value will be nonzero if the function succeeds or zero if it fails. Table 7 lists the
parameters.

Table 7. A listing of parameters for the GetNumberOfEventLogRecords function.


Parameters Description
hEventLog The event log handle.
@NumberOfRecords The number of records in the event log.

GetOldestEventLogRecord( ) gets the record number for the oldest event in the log. It returns a
nonzero value if successful or zero if it fails. Table 8 lists the parameters.

DECLARE INTEGER GetOldestEventLogRecord IN WIN32API ;


INTEGER hEventLog, ;
STRING @OldestRecord

Table 8. A listing parameters for the GetOldestEventLogRecord function.


Parameters Description

Craig Berntson, Cole Gleave, 2004, 2005


Page 11 of 31
Using the Windows Event Log from Visual FoxPro

hEventLog The event log handle.


@OldestRecord The record number of the oldest record in the event log.

That completes the actual event log API calls. There are some additional Win32 API functions
that you ll need to use. The first of these is GlobalAlloc( ), which allocates memory on the heap.
If the function succeeds, it returns a handle to the allocated memory. If it fails, it returns NULL.

DECLARE INTEGER GlobalAlloc IN WIN32API ;


INTEGER Flags, ;
INTEGER Bytes

Table 9 defines the parameters.


Table 9. A listing of parameters for the GlobalAlloc function.
Parameters Description
Flags Indicates how to fill the memory being allocated. For the event log, the
memory can be filled with zeros, as defined below.
Bytes The number of bytes to allocate.

The following line defines the value to pass for the Flags parameter:

#DEFINE GMEM_ZEROINIT 0x0040

Since memory is allocated, is must also be deallocated. That is the purpose of GlobalFree( ). The
single parameter is the handle to the memory, as returned by GlobalAlloc( ). If GlobalFree( )
succeeds in deallocating the memory, it returns NULL. Any other value indicates failure.

DECLARE INTEGER GlobalFree IN WIN32API ;


INTEGER Mem

The strings that are used for the @Strings parameter of the ReportEvent function need to be
copied from Visual FoxPro memory into the allocated memory. Keep in mind that the Event API
function requires a C++ array string. By copying the memory and doing some other hocus-pocus
that I will explain later, the VFP string will look like a C++ array string. The CopyMemory( )
function will handle this for us.

DECLARE RtlMoveMemory IN WIN32API AS CopyMemory ;


INTEGER Destination, ;
STRING Source, ;
INTEGER Length

This function has no return value. The parameters are listed in Table 10.

Craig Berntson, Cole Gleave, 2004, 2005


Page 12 of 31
Using the Windows Event Log from Visual FoxPro

Table 10. A listing of parameters for the RtlMoveMemory function.


Parameters Description
Destination The starting address of the copied block s destination.
Source The starting address of the block of memory to copy.
Length The size, in bytes, of the block of memory to copy.

One of the optional parameters of the ReportEvent( ) function is UserSid, which is the User s
Security ID. The LookupAccountSid( ) function gets that information for you.

DECLARE INTEGER LookupAccountSid IN WIN32API ;


STRING SystemName, ;
STRING @Sid, ;
STRING @Name, ;
STRING @cbName, ;
STRING @ReferencedDomainName, ;
STRING @cbReferencedDomainName, ;
STRING @peUse

A nonzero return value indicates success, zero indicates failure. Table 11 lists the parameters.

Table 11. A listing of parameters for the LookupAccountSID function.


Parameters Description
SystemName The server name to query. Pass an empty string to query the local
system.
@Sid A SID structure for the account information to look up.
@Name The account name corresponding to the @Sid parameter.
@cbName The number of bytes contained in the @Name parameter. If the function
fails because @Name is too small, this will contain the number of bytes
needed.
@ReferencedDomainName The domain where the account is found.
@cbReferencedDomainNam The number of bytes contained in the @ReferencedDomainName
e parameter. If the function fails because @ReferencedDomainName is
too small, this will contain the number of bytes needed.
@peUse Contains the type of account when the function returns.

The next function, LoadLibraryEx( ) maps an executable module into the address space of the
calling process. For the Windows Event Log, it is used to load the message file. Here is the
syntax:

DECLARE LONG LoadLibraryEx IN WIN32API ;


STRING LibFileName, ;
INTEGER RESERVED, ;
INTEGER Flags

If the function call succeeds, the return value is a handle to the mapped executable module. If it
fails, NULL is returned. Table 12 lists the parameters.

Table 12. A listing of parameters for the LoadLibraryEx function.

Craig Berntson, Cole Gleave, 2004, 2005


Page 13 of 31
Using the Windows Event Log from Visual FoxPro

Parameters Description
LibFileName The name of the executable (DLL or EXE) module.
RESERVED Must be NULL
Flags The action to take when loading the module. For event log usage, the only
value you need to use here is to load the library as a datafile.

#DEFINE LOAD_LIBRARY_AS_DATAFILE 0x00000002

After you finish using the library, you need to release it. The FreeLibrary( ) function will do this.
It takes a single parameter, hLibModule, which is the return value of LoadLibraryEx( ). If the
function succeeds, it returns a nonzero value. Any other return value indicates failure.

DECLARE INTEGER FreeLibrary IN WIN32API ;


LONG hLibModule

You will need to format the message string. The FormatMessageString( ) function does this.

DECLARE INTEGER FormatMessage IN WIN32API ;


INTEGER Flags, ;
LONG Source, ;
INTEGER MessageId, ;
INTEGER LanguageId, ;
STRING @Buffer, ;
INTEGER BufferSize, ;
STRING @Arguments

FormatMessage( ) returns the number of bytes in the output buffer. If it fails, it returns a zero.
Table 13 lists the parameters.
Table 13. A listing parameters for the FormatMessage function.
Parameters Description
Flags A series of bit flags that specify aspects of the formatting process and how
to interpret the Source parameter. See the FORMAT_MESSAGE constants
below.
Source Specifies the location of the message definition. This will be the resource
file loaded by LoadLibraryEx.
MessageId The Message Id from the Message file.
LanguageId The language Id. You can use 0 if no language is specified.
@Buffer Used to return the formatted message.
BufferSize The maximum number of bytes that can be stored in @Buffer.
@Arguments Values that are used as insert values in the message.

#DEFINE FORMAT_MESSAGE_ALLOCATE_BUFFER 256


#DEFINE FORMAT_MESSAGE_IGNORE_INSERTS 512

Craig Berntson, Cole Gleave, 2004, 2005


Page 14 of 31
Using the Windows Event Log from Visual FoxPro

#DEFINE FORMAT_MESSAGE_FROM_HMODULE 2048


#DEFINE FORMAT_MESSAGE_ARGUMENT_ARRAY 8192

The last API function is ExpandEnvironmentStrings( ). This function expands environment


variable strings and replaces them with their user-defined values.

DECLARE INTEGER ExpandEnvironmentStrings IN WIN32API ;


STRING @Src, ;
STRING @Dst, ;
INTEGER nSize

If the function succeeds, the return value is the number of characters stored in the destination
buffer. If the number of characters is greater than the size of the destination buffer, the return
value is the size of the buffer required to hold the expanded strings. If the function fails, the
return value is zero. Table 14 lists the parameters.

Table 14. A list of parameters for the ExpandEnvironmentStrings function.


Parameters Description
@Src The environment variable string to expand. Must be of the form:
%variableName%.
@Dst The buffer to receive the value specified by @Src.
nSize The maximum number of bytes that can be held by @Dst.

That completes the Win32 API functions. However, there is still just a bit more to cover before
looking at the full code. I haven t completely showed you how to do the conversion from a VFP
string to the C++ array string. The following VFP functions will do this:

* Adapted from KB Article ID: Q181289


*-- The following function converts a long integer to an ASCII
*-- character representation of the passed value in low-high format.
******************
FUNCTION LongToStr
******************
* Passed : 32-bit non-negative numeric value (lnLongval)
* Returns : ascii character representation of passed value in low-high
* format (lcRetstr)
* Example :
* m.long = "999999"
* m.longstr = long2str(m.long)

LPARAMETERS lnLongval

LOCAL lnI, lcRetstr

lcRetstr = ""
FOR lnI = 24 TO 0 STEP -8
lcRetstr = CHR(INT(lnLongval / (2 ^ lnI))) + lcRetstr
lnLongval = MOD(lnLongval, (2 ^ lnI))
NEXT
RETURN lcRetstr

Craig Berntson, Cole Gleave, 2004, 2005


Page 15 of 31
Using the Windows Event Log from Visual FoxPro

*-- The following function converts a string in low-high format to a


*-- long integer.
******************
FUNCTION StrToLong
******************
* Passed: 4-byte character string (lcLongstr) in low-high ASCII format
* Returns: long integer value
* Example:
* m.longstr = "1111"
* m.longval = str2long(m.longstr)

LPARAMETERS lcLongstr

LOCAL lnI, lnRetval

lnRetval = 0
FOR lnI = 0 TO 24 STEP 8
lnRetval = lnRetval + (ASC(lcLongstr) * (2 ^ lnI))
lcLongstr = RIGHT(lcLongstr, LEN(lcLongstr) - 1)
NEXT
RETURN lnRetval

Writing to the Event Log


Now that you ve seen the functions involved in using the Windows Event Log, it s time to learn
how write to the event log. Here is the code.

#define EVENTLOG_SUCCESS 0
#define EVENTLOG_ERROR_TYPE 1
#define EVENTLOG_WARNING_TYPE 2
#define EVENTLOG_INFORMATION_TYPE 4
#define EVENTLOG_AUDIT_SUCCESS 8
#define EVENTLOG_AUDIT_FAILURE 10

#define GMEM_ZEROINIT 0x0040

DECLARE INTEGER GetLastError IN WIN32API

DECLARE INTEGER RegisterEventSource IN WIN32API ;


STRING UNCServerName, ;
STRING SourceName

DECLARE INTEGER DeregisterEventSource IN WIN32API ;


INTEGER hEventLog

DECLARE INTEGER ReportEvent IN WIN32API ;


INTEGER EventLog, ;
INTEGER Type, ;
INTEGER Category, ;
INTEGER EventID, ;
INTEGER UserSid, ;
INTEGER NumStrings, ;
INTEGER DataSize, ;

Craig Berntson, Cole Gleave, 2004, 2005


Page 16 of 31
Using the Windows Event Log from Visual FoxPro

STRING @Strings, ;
INTEGER RawData

DECLARE INTEGER GlobalAlloc IN WIN32API ;


INTEGER Flags, ;
INTEGER Bytes

DECLARE INTEGER GlobalFree IN WIN32API ;


INTEGER Mem

DECLARE RtlMoveMemory in WIN32API as CopyMemory ;


INTEGER Destination, ;
STRING Source, ;
INTEGER Length

LOCAL lnHandle, lcName, pName, lcStrings, lnRetVal, ;


lcMessage1, lcMessage2, pMessage1, pMessage2,
lcMessages

lnHandle = RegisterEventSource("", "FoxMessages")

IF lnHandle > 0
* No parameter
lnRetVal = ReportEvent(lnHandle, EVENTLOG_SUCCESS, 1, 1000, 0, 0, 0,
NULL, 0)
IF lnRetVal = 0
MESSAGEBOX("ReportEvent Error: " + ALLTRIM(STR(GetLastError())))
ENDIF

* Single parameter
lcName = "Fred"
pName = GlobalAlloc(GMEM_ZEROINIT, LEN(lcName) + 1)
CopyMemory(pName, lcName, LEN(lcName))
lcStrings = LongToStr(pName)
lnRetVal = ReportEvent(lnHandle, EVENTLOG_SUCCESS, 2, 1001, 0, 1, 0,
@lcStrings, 0)
IF lnRetVal = 0
MESSAGEBOX("ReportEvent Error: " + ALLTRIM(STR(GetLastError())))
ENDIF
IF pName > 0
lnRet = GlobalFree(pName)
ENDIF

* Multiple parameters
lcMessage1 = "This is the first message."
lcMessage2 = "This is another message!"
pMessage1 = GlobalAlloc(GMEM_ZEROINIT, LEN(lcMessage1) + 1)
pMessage2 = GlobalAlloc(GMEM_ZEROINIT, LEN(lcMessage2) + 1)
CopyMemory(pMessage1, lcMessage1, LEN(lcMessage1))
CopyMemory(pMessage2, lcMessage2, LEN(lcMessage2))
lcMessages = LongToStr(pMessage1) + LongToStr(pMessage2)
lnRetVal = ReportEvent(lnHandle, EVENTLOG_SUCCESS, 1, 1003, 0, 2, 0,
@lcMessages, 0)
IF lnRetVal = 0
MESSAGEBOX("ReportEvent Error: " + ALLTRIM(STR(GetLastError())))
ENDIF
IF pMessage1 > 0

Craig Berntson, Cole Gleave, 2004, 2005


Page 17 of 31
Using the Windows Event Log from Visual FoxPro

lnRet = GlobalFree(pMessage1)
ENDIF
IF pMessage2 > 0
lnRet = GlobalFree(pMessage2)
ENDIF

lnRetVal = DeregisterEventSource(lnHandle)
ELSE
MESSAGEBOX("RegisterEventSource Error: " ;
+ ALLTRIM(STR(GetLastError())))
ENDIF
RETURN

******************
* From KB Article ID: Q181289
******************
FUNCTION LongToStr
* The following function converts a long integer to an ASCII
* character representation of the passed value in low-high format.
* Passed: 32-bit non-negative numeric value (lnLongval)
* Returns: ascii character representation of passed value in low-high
* format

LPARAMETERS lnLongval
LOCAL lnI, lcRetstr

lcRetstr = ""
FOR lnI = 24 TO 0 STEP -8
lcRetstr = CHR(INT(lnLongval / (2 ^ lnI))) + lcRetstr
lnLongval = MOD(lnLongval, (2 ^ lnI))
NEXT
RETURN lcRetstr

******************
FUNCTION StrToLong
* Convert a string in low-high format to a long integer.
* Passed: 4-byte character string (lcLongstr) in low-high ASCII format
* Returns: long integer value

LPARAMETERS lcLongstr
LOCAL lnI, lnRetval

lnRetval = 0
FOR lnI = 0 TO 24 STEP 8
lnRetval = lnRetval + (ASC(lcLongstr) * (2 ^ lnI))
lcLongstr = RIGHT(lcLongstr, LEN(lcLongstr) - 1)
NEXT
RETURN lnRetval

As you can see from this example, the actual FoxPro code is fairly simple. The key to using the
event log is defining the API calls and converting FoxPro data types to the C++ data types
needed for the calls.

Craig Berntson, Cole Gleave, 2004, 2005


Page 18 of 31
Using the Windows Event Log from Visual FoxPro

Reading from the Event Log


Here s the code to read from the event log. The results are displayed in a Message Box.

#DEFINE EVENTLOG_SEQUENTIAL_READ 1
#DEFINE EVENTLOG_SEEK_READ 2
#DEFINE EVENTLOG_FORWARDS_READ 4
#DEFINE EVENTLOG_BACKWARDS_READ 8

#DEFINE ERROR_INSUFFICIENT_BUFFER 122


#DEFINE ERROR_HANDLE_EOF 38

#DEFINE EVENTLOG_SUCCESS 0
#DEFINE EVENTLOG_ERROR_TYPE 1
#DEFINE EVENTLOG_WARNING_TYPE 2
#DEFINE EVENTLOG_INFORMATION_TYPE 4
#DEFINE EVENTLOG_AUDIT_SUCCESS 8
#DEFINE EVENTLOG_AUDIT_FAILURE 10

#DEFINE HKEY_LOCAL_MACHINE -2147483646 && BITSET(0,31)+2

#DEFINE FORMAT_MESSAGE_ALLOCATE_BUFFER 256


#DEFINE FORMAT_MESSAGE_IGNORE_INSERTS 512
#DEFINE FORMAT_MESSAGE_FROM_HMODULE 2048
#DEFINE FORMAT_MESSAGE_ARGUMENT_ARRAY 8192

#DEFINE LOAD_LIBRARY_AS_DATAFILE 0x00000002

#DEFINE GMEM_ZEROINIT 0x0040

DECLARE INTEGER GetLastError IN WIN32API

DECLARE INTEGER OpenEventLog IN WIN32API ;


STRING UNCServerName, ;
STRING UNCSourceName

DECLARE INTEGER CloseEventLog IN WIN32API ;


INTEGER hEventLog

DECLARE INTEGER GetNumberOfEventLogRecords IN WIN32API ;


INTEGER hEventLog, ;
STRING @NumberOfRecords

DECLARE INTEGER GetOldestEventLogRecord IN WIN32API ;


INTEGER hEventLog, ;
STRING @OldestRecord

DECLARE INTEGER ReadEventLog IN WIN32API ;


INTEGER hEventLog, ;
INTEGER ReadFlags, ;
INTEGER RecordOffset, ;
STRING @BUFFER, ;
INTEGER NumberOfBytesToRead, ;
STRING @BytesRead, ;
STRING @MinNumberOfBytesNeeded

DECLARE INTEGER LookupAccountSid IN WIN32API ;

Craig Berntson, Cole Gleave, 2004, 2005


Page 19 of 31
Using the Windows Event Log from Visual FoxPro

STRING SystemName, ;
STRING @Sid, ;
STRING @NAME, ;
STRING @cbName, ;
STRING @ReferencedDomainName, ;
STRING @cbReferencedDomainName, ;
STRING @peUse

DECLARE LONG LoadLibraryEx IN WIN32API ;


STRING LibFileName, ;
INTEGER RESERVED, ;
INTEGER FLAGS

DECLARE INTEGER FreeLibrary IN WIN32API ;


LONG hLibModule

DECLARE INTEGER FormatMessage IN WIN32API ;


INTEGER FLAGS, ;
LONG SOURCE, ;
INTEGER MessageId, ;
INTEGER LanguageId, ;
STRING@ BUFFER, ;
INTEGER BufferSize, ;
STRING@ Arguments

DECLARE INTEGER ExpandEnvironmentStrings IN WIN32API ;


STRING@ Src, ;
STRING@ Dst, ;
INTEGER nSize

DECLARE INTEGER GlobalAlloc IN WIN32API ;


INTEGER FLAGS, ;
INTEGER Bytes

DECLARE INTEGER GlobalFree IN WIN32API ;


INTEGER Mem

DECLARE RtlMoveMemory IN WIN32API AS CopyMemory ;


INTEGER Destination, ;
STRING SOURCE, ;
INTEGER LENGTH

**************************************
LOCAL lcUNCSourceName, loReg, hEventLog, lcNumberOfRecords, lnRetVal, ;
lcOldestRecord, lcBuffer, lcBytesRead, lcMinNumberOfBytesNeeded

lcUNCSourceName = "C:\"

SET PROCEDURE TO (ADDBS(HOME()) + "samples\classes\registry.prg")


ADDITIVE
loReg = CREATEOBJECT("FileReg")

* lpUNCSourceName = "3MHIS"
hEventLog = OpenEventLog(NULL, lcUNCSourceName)
IF hEventLog = 0
MESSAGEBOX("OpenEventLog Error: " + ALLTRIM(STR(GetLastError())))
RETURN

Craig Berntson, Cole Gleave, 2004, 2005


Page 20 of 31
Using the Windows Event Log from Visual FoxPro

ENDIF

* Get the number of records in the event log


lcNumberOfRecords = SPACE(4)
lnRetVal = GetNumberOfEventLogRecords(hEventLog, @lcNumberOfRecords)
IF lnRetVal = 0
MESSAGEBOX("GetNumberOfEventLogRecords Error: " +
ALLTRIM(STR(GetLastError())))
ELSE
MESSAGEBOX("Number of Event Log Records: " +
ALLTRIM(STR(StrToLong(lcNumberOfRecords))))
ENDIF

* Get the oldest record


lcOldestRecord = SPACE(4)
lnRetVal = GetOldestEventLogRecord(hEventLog, @lcOldestRecord)
IF lnRetVal = 0
MESSAGEBOX("GetOldestEventLogRecord Error: " +
ALLTRIM(STR(GetLastError())))
ELSE
MESSAGEBOX("Oldest Event Log Record: " +
ALLTRIM(STR(StrToLong(lcOldestRecord))))
ENDIF

* Loop through messages


DO WHILE .T.
lcBuffer = SPACE(1)
lcBytesRead = SPACE(4)
lcMinNumberOfBytesNeeded = SPACE(4)

lnRetVal = ReadEventLog(hEventLog, ;
BITOR(EVENTLOG_SEQUENTIAL_READ, ;
EVENTLOG_FORWARDS_READ), ;
0, ;
@lcBuffer, ;
LEN(lcBuffer), ;
@lcBytesRead, ;
@lcMinNumberOfBytesNeeded)

IF GetLastError() = ERROR_INSUFFICIENT_BUFFER
lcBuffer = SPACE(StrToLong(lcMinNumberOfBytesNeeded))
lcBytesRead = SPACE(4)
lcMinNumberOfBytesNeeded = SPACE(4)

lnRet = ReadEventLog(hEventLog, ;
BITOR(EVENTLOG_SEQUENTIAL_READ, ;
EVENTLOG_FORWARDS_READ), ;
0, ;
@lcBuffer, ;
LEN(lcBuffer), ;
@lcBytesRead, ;
@lcMinNumberOfBytesNeeded)

IF lnRet = 0
MESSAGEBOX("ReadEventLog Error: " + ALLTRIM(STR(GetLastError())))
EXIT
ELSE

Craig Berntson, Cole Gleave, 2004, 2005


Page 21 of 31
Using the Windows Event Log from Visual FoxPro

lcMessage = ParseLogRecord(lcBuffer, lcUNCSourceName, loReg)


IF MESSAGEBOX(lcMessage, 1) = 2
EXIT
ENDIF
ENDIF
ELSE
IF GetLastError() = ERROR_HANDLE_EOF
MESSAGEBOX("End of File")
EXIT
ELSE
MESSAGEBOX("ReadEventLog Error: " + ALLTRIM(STR(GetLastError())))
EXIT
ENDIF
ENDIF
ENDDO

lnRetVal = CloseEventLog(hEventLog)
IF lnRetVal = 0
MESSAGEBOX("CloseEventLog Error: " + ALLTRIM(STR(GetLastError())))
ENDIF
RETURN

****************************************************

FUNCTION ParseLogRecord
LPARAMETERS tcBuffer, tcUNCSourceName, toReg
LOCAL lcMessage, lnCounter, lnDataLen, pBuffer

lcMessage = "Length: " + ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer, 1, 4))))


+ CHR(13) + CHR(10)
lcMessage = lcMessage + "Record Number: " +
ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer, 9, 4)))) + CHR(13) + CHR(10)
lcMessage = lcMessage + "Time Generated: " + TTOC({^1969/12/31 18:00:00}
+ StrToLong(SUBSTR(tcBuffer, 13, 4))) + CHR(13) + CHR(10)
lcMessage = lcMessage + "Time Written: " + TTOC({^1969/12/31 18:00:00} +
StrToLong(SUBSTR(tcBuffer, 17, 4))) + CHR(13) + CHR(10)
lcMessage = lcMessage + "Event ID: " +
ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer, 21, 2)))) + CHR(13) + CHR(10)

lcMessage = lcMessage + GetEventType(tcBuffer)

lcMessage = lcMessage + "Number of Strings: " +


ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer, 27, 2)))) + CHR(13) + CHR(10)
lcMessage = lcMessage + "Source Name: "
lcSource = ""
lnCounter = 57
DO WHILE SUBSTR(tcBuffer, lnCounter, 1) <> CHR(0)
lcSource = lcSource + SUBSTR(tcBuffer, lnCounter, 1)
lnCounter = lnCounter + 1
ENDDO
lcMessage = lcMessage + lcSource
lcMessage = lcMessage + CHR(13) + CHR(10)

lcMessage = lcMessage + GetEventCategory(tcBuffer, tcUNCSourceName,


lcSource, toReg)

lnCounter = lnCounter + 1

Craig Berntson, Cole Gleave, 2004, 2005


Page 22 of 31
Using the Windows Event Log from Visual FoxPro

lcMessage = lcMessage + "Computer Name: "


DO WHILE SUBSTR(tcBuffer, lnCounter, 1) <> CHR(0)
lcMessage = lcMessage + SUBSTR(tcBuffer, lnCounter, 1)
lnCounter = lnCounter + 1
ENDDO
lcMessage = lcMessage + CHR(13) + CHR(10)

lcMessage = lcMessage + GetUserInfo(tcBuffer)


lcMessage = lcMessage + GetDescription(tcBuffer, tcUNCSourceName,
lcSource, toReg)

lnDataLen = StrToLong(SUBSTR(tcBuffer, 49, 4))


IF lnDataLen > 0
lcMessage = lcMessage + "Data: "
pBuffer = StrToLong(SUBSTR(tcBuffer, 53, 4))
FOR lnCounter = 1 TO lnDataLen
lcMessage = lcMessage + STR(ASC(SUBSTR(tcBuffer, pBuffer +
lnCounter, lnDataLen)))
NEXT
lcMessage = lcMessage + CHR(13) + CHR(10)
ENDIF
RETURN lcMessage

*************************************************

FUNCTION GetEventType
LPARAMETERS tcBuffer
LOCAL lcRetVal, lnEventType

lnEventType = StrToLong(SUBSTR(tcBuffer, 25, 2))


DO CASE
CASE lnEventType = EVENTLOG_SUCCESS
lcRetVal = "Success"
CASE lnEventType = EVENTLOG_ERROR_TYPE
lcRetVal = "Error"
CASE lnEventType = EVENTLOG_WARNING_TYPE
lcRetVal = "Warning"
CASE lnEventType = EVENTLOG_INFORMATION_TYPE
lcRetVal = "Information"
CASE lnEventType = EVENTLOG_AUDIT_SUCCESS
lcRetVal = "Audit Success"
CASE lnEventType = EVENTLOG_AUDIT_FAILURE
lcRetVal = "Audit Failure"
ENDCASE
lcRetVal = "Event Type: " + lcRetVal + CHR(13) + CHR(10)
RETURN lcRetVal

************************************

FUNCTION GetEventCategory
LPARAMETERS tcBuffer, tcUNCSourceName, tcSource, toReg
LOCAL lcRetVal, lnCategory, lcLookup, lnKey, lcCMF, lnKeyValue, lcCMFx,
lnRet, ;
hResource, lcBuffer, lnI

lcRetVal = ""
lnCategory = StrToLong(SUBSTR(tcBuffer, 29, 2))

Craig Berntson, Cole Gleave, 2004, 2005


Page 23 of 31
Using the Windows Event Log from Visual FoxPro

IF lnCategory = 0
lcRetVal = "None"
ELSE
lcLookUp = "SYSTEM\CurrentControlSet\Services\EventLog\" +
ALLTRIM(tcUNCSourceName) + "\" + tcSource

lnKey = toReg.OpenKey(lcLookUp, HKEY_LOCAL_MACHINE, .F.)


IF lnKey = 0
lcCMF = ""
lnKeyValue = toReg.GetKeyValue("CategoryMessageFile", @lcCMF)
IF lnKeyValue = 0
lcCMFx = SPACE(255)
lnRet = ExpandEnvironmentStrings(@lcCMF, @lcCMFx, 255)
hResource = LoadLibraryEx(ALLTRIM(lcCMFx), 0,
LOAD_LIBRARY_AS_DATAFILE)
IF hResource > 0
lcBuff = SPACE(100)
lnRet = FormatMessage(BITOR(FORMAT_MESSAGE_FROM_HMODULE,
FORMAT_MESSAGE_IGNORE_INSERTS), ;
hResource, ;
lnCategory, ;
0, ;
@lcBuff, ;
100, ;
NULL)
IF lnRet > 0
FOR lnI = 1 TO lRet
IF SUBSTR(tcBuffer, lnI, 1) = CHR(13)
EXIT
ELSE
lcRetVal = lcRetVal + SUBSTR(tcBuffer, lnI, 1)
ENDIF
NEXT
ELSE
lcRetVal = lcRetVal + "(" +
ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer, 29, 2)))) + ")"
ENDIF
FreeLibrary(hResource)
ELSE
lcRetVal = lcRetVal + "(" +
ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer, 29, 2)))) + ")"
ENDIF
ELSE
lcRetVal = lcRetVal + "(" + ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer,
29, 2)))) + ")"
ENDIF
oReg.CloseKey()
ELSE
lcRetVal = lcRetVal + "(" + ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer,
29, 2)))) + ")"
ENDIF
ENDIF

lcRetVal = "Event Category: " + lcRetVal + CHR(13) + CHR(10)

RETURN lcRetVal

Craig Berntson, Cole Gleave, 2004, 2005


Page 24 of 31
Using the Windows Event Log from Visual FoxPro

**************************************************

FUNCTION GetUserInfo
LPARAMETERS tcBuffer
LOCAL lnUserSIDLength, lnUserSIDOffset, lcRetVal, lcSID, lnI, lcName,
cbName, ;
lcReferencedDomainName, cbReferencedDomainName, peUse, lnRet

lnUserSIDLength = StrToLong(SUBSTR(tcBuffer, 41, 4))


lnUserSIDOffset = StrToLong(SUBSTR(tcBuffer, 45, 4))
lcRetVal = ""

IF lnUserSIDLength > 0
lcSid = ""
FOR lnI = 1 TO lnUserSIDLength
lcSid = lcSid + SUBSTR(tcBuffer, lnUserSIDOffset + lnI, 1)
NEXT

lcName = SPACE(1)
cbName = LongToStr(1)
lcReferencedDomainName = SPACE(1)
cbReferencedDomainName = LongToStr(1)
peUse = SPACE(1)
lnRet = LookupAccountSid("", ;
@lcSid, ;
@lcName, ;
@cbName, ;
@lcReferencedDomainName, ;
@cbReferencedDomainName, ;
@peUse)
lnRet = GetLastError()
IF lnRet = ERROR_INSUFFICIENT_BUFFER
lcName = SPACE(StrToLong(cbName))
lcReferencedDomainName = SPACE(StrToLong(cbReferencedDomainName))
lnRet = LookupAccountSid("", ;
@lcSid, ;
@lcName, ;
@cbName, ;
@lcReferencedDomainName, ;
@cbReferencedDomainName, ;
@peUse)
IF lnRet = 1
lcRetVal = lcRetVal + lcName
ELSE
lcRetVal = lcRetVal + "N/A"
ENDIF
ELSE
lcRetVal = lcRetVal + "N/A"
ENDIF
ELSE
lcRetVal = lcRetVal + "N/A"
ENDIF
lcRetVal = "User: " + lcRetVal + CHR(13) + CHR(10)

RETURN lcRetVal

Craig Berntson, Cole Gleave, 2004, 2005


Page 25 of 31
Using the Windows Event Log from Visual FoxPro

***********************************************

PROCEDURE GetDescription
LPARAMETERS tcBuffer, tcUNCSourceName, tcSource, toReg
LOCAL lcLookup, lnRet, lcEMF, lcEMFx, lnNumStrings, pStr, lcPtrs, lnI, ;
hResource, lcBuffer, lnEventNo, lcMessage, lcRetVal

lcLookUp = "SYSTEM\CurrentControlSet\Services\EventLog\" +
ALLTRIM(tcUNCSourceName) + "\" + tcSource

lnRet = toReg.OpenKey(lcLookUp, HKEY_LOCAL_MACHINE, .F.)

IF lnRet = 0
lcEMF = ""
lnRet = oReg.GetKeyValue("EventMessageFile", @lcEMF)
IF lnRet = 0
lnNumStrings = StrToLong(SUBSTR(tcBuffer, 27, 2))
pStr = StrToLong(SUBSTR(cBuffer, 37, 4)) + 1
lcMessage = ""
lcPtrs = ""

FOR lnI = 1 TO lnNumStrings


DO WHILE .T.
IF SUBSTR(tcBuffer, pStr, 1) <> CHR(0)
lcMessage = lcMessage + SUBSTR(tcBuffer, pStr, 1)
pStr = pStr + 1
ELSE
lcPtrs = lcPtrs + LongToStr(GlobalAlloc(GMEM_ZEROINIT,
LEN(lcMessage) + 1))
CopyMemory(StrToLong(RIGHT(lcPtrs, 4)), lcMessage,
LEN(lcMessage))
pStr = pStr + 1
lcMessage = ""
EXIT
ENDIF
ENDDO
NEXT

lcEMFx = SPACE(255)
lnRet = ExpandEnvironmentStrings(@lcEMF, @lcEMFx, 255)
hResource = LoadLibraryEx(ALLTRIM(lcEMFx), 0,
LOAD_LIBRARY_AS_DATAFILE)
IF hResource > 0
lcBuffer = SPACE(1000)

lnRet = FormatMessage(BITOR(FORMAT_MESSAGE_FROM_HMODULE,
FORMAT_MESSAGE_ARGUMENT_ARRAY, 60), ;
hResource, ;
StrToLong(SUBSTR(tcBuffer, 21, 2)), ;
0, ;
@lcBuffer, ;
10000, ;
@lcPtrs)

IF lnRet = 0
lnEventNo = StrToLong(SUBSTR(tcBuffer, 21, 2))
lnRet = GetLastError()

Craig Berntson, Cole Gleave, 2004, 2005


Page 26 of 31
Using the Windows Event Log from Visual FoxPro

ELSE
lcRetVal = ALLTRIM(lcBuffer)
ENDIF
FreeLibrary(hResource)
ENDIF

FOR lnI = 1 TO lnNumStrings


lnRet = GlobalFree(StrToLong(SUBSTR(lcPtrs, lnI * 4 - 3, 4)))
NEXT
ENDIF
toReg.CloseKey()
ENDIF
lcRetVal = IIF(EMPTY(lcRetVal), "", "Description: " + lcRetVal)
RETURN lcRetVal

******************
* From KB Article ID: Q181289
******************
FUNCTION LongToStr
* The following function converts a long integer to an ASCII
* character representation of the passed value in low-high format.
* Passed: 32-bit non-negative numeric value (lnLongval)
* Returns: ascii character representation of passed value in low-high
* format

LPARAMETERS tnLongval
LOCAL lnI, lcRetVal

lcRetVal = ""
FOR lnI = 24 TO 0 STEP -8
lcRetVal = CHR(INT(tnLongVal / (2 ^ lnI))) + lcRetVal
lnLongval = MOD(tnLongVal, (2 ^ lnI))
NEXT
RETURN lcRetVal

******************
FUNCTION StrToLong
* Convert a string in low-high format to a long integer.
* Passed: 4-byte character string (lcLongstr) in low-high ASCII format
* Returns: long integer value

LPARAMETERS tcLongStr
LOCAL lcLongStr, lnI, lnRetval

lcLongStr = tcLongStr
lnRetVal = 0
FOR lnI = 0 TO 24 STEP 8
lnRetVal = lnRetVal + (ASC(lcLongStr) * (2 ^ lnI))
lcLongStr = RIGHT(lcLongStr, LEN(lcLongStr) - 1)
NEXT

RETURN lnRetVal

You can see that the code that actually reads the log is quite small. Most of the code is for
formatting the output.

Craig Berntson, Cole Gleave, 2004, 2005


Page 27 of 31
Using the Windows Event Log from Visual FoxPro

Managing the Event Log


The Windows Event Log requires periodic maintenance. You can schedule this maintenance to
occur as needed or manage it yourself. This maintenance is required due to limitations with the
Event Log. The Windows Event Log documentation says that the log size for all logs is limited
to 4 Gig, but testing at 3M HIS showed that the practical limit is 300 MB. The log size is also
limited by memory and other services and applications using the log.

The following steps show you how to setup automatic maintenance of the log:

1. Open the Event Viewer (Figure 1).


2. Right-click on Application and select Properties. The Application Properties dialog
(Figure 7) is displayed.

Figure 7. The Application Properties dialog is used to maintain the Event Log.

3. Use the Log size settings to maintain the size of the Event Log.
4. Click Clear Log to clear all entries from the log
5. Click OK to close the properties dialog.

You can also maintain the log programmatically. The following code shows how to backup the
log:

Craig Berntson, Cole Gleave, 2004, 2005


Page 28 of 31
Using the Windows Event Log from Visual FoxPro

DECLARE INTEGER GetLastError IN WIN32API

DECLARE INTEGER OpenEventLog IN WIN32API ;


STRING UNCServerName, ;
STRING UNCSourceName

DECLARE INTEGER BackupEventLog IN WIN32API ;


INTEGER hEventLog, ;
STRING BackupLog

DECLARE INTEGER CloseEventLog IN WIN32API ;


INTEGER hEventLog

***************************************

LOCAL hEventLog, lcBackupLog, lnRet

lcUNCSourceName = "C:\"
lcBackupLog = "C:\Temp\BackupLog.Evt"

hEventLog = OpenEventLog(NULL, lcUNCSourceName)


IF hEventLog = 0
MESSAGEBOX("OpenEventLog Error: " + ALLTRIM(STR(GetLastError())))
RETURN
ENDIF

ERASE (lcBackupLog)

lnRet = BackupEventLog(hEventLog, lcBackupLog)


IF lnRet = 0
MESSAGEBOX("BackupEventLog Error: " + ALLTRIM(STR(GetLastError())))
ENDIF

lnRet = CloseEventLog(hEventLog)
IF lnRet = 0
MESSAGEBOX("CloseEventLog Error: " + ALLTRIM(STR(GetLastError())))
ENDIF

The following code shows how to clear the Windows Event Log:

DECLARE INTEGER GetLastError IN WIN32API

DECLARE INTEGER OpenEventLog IN WIN32API ;


STRING UNCServerName, ;
STRING UNCSourceName

DECLARE INTEGER ClearEventLog IN WIN32API ;


INTEGER hEventLog, ;
STRING BackupLog

DECLARE INTEGER CloseEventLog IN WIN32API ;


INTEGER hEventLog

***************************************

Craig Berntson, Cole Gleave, 2004, 2005


Page 29 of 31
Using the Windows Event Log from Visual FoxPro

LOCAL hEventLog, lcBackupLog, lnRet, lcNewBackup

lcUNCSourceName = "C:\"
lcBackupLog = "C:\Temp\Backup.Evt"
lcNewBackup = NULL
hEventLog = OpenEventLog(NULL, lcUNCSourceName)

IF hEventLog = 0
MESSAGEBOX("OpenEventLog Error: " + ALLTRIM(STR(GetLastError())))
RETURN
ENDIF

IF !ISNULL(lcBackupLog)
ERASE (lcBackupLog)
ENDIF

lnRet = ClearEventLog(hEventLog, lcBackupLog)


IF lnRet = 0
MESSAGEBOX("ClearEventLog Error: " + ALLTRIM(STR(GetLastError())))
ENDIF

lnRet = CloseEventLog(hEventLog)
IF lnRet = 0
MESSAGEBOX("CloseEventLog Error: " + ALLTRIM(STR(GetLastError())))
ENDIF

Summary
It may seem that using the Windows Event Log is very complex, but you could create a class that
will handle this for you and a generic message file, making the job easier. By using the Windows
Event Log, you will centralize event reporting, use a format common to many other applications,
and make event reporting easier for the end user.

Craig Berntson is a Microsoft Most Valuable Professional (MVP) for Visual FoxPro, a Microsoft Certified Solution
Developer, and President of the Salt Lake City Fox User Group. He wrote the book CrysDev: A Developer s
Guide to Integrating Crystal Reports , available from Hentzenwerke Publishing. He has also written for FoxTalk
and the Visual FoxPro User Group (VFUG) newsletter. He has spoken at Advisor DevCon, Essential Fox, the Great
Lakes Great Database Workshop, Southwest Fox, Microsoft DevDays and user groups around the country.
Currently, Craig is a Senior Software Engineer at 3M Health Information Systems in Salt Lake City. You can reach
him a craig@craigberntson.com or visit his website, www.craigberntson.com.

Cole Gleave is a Senior Software Engineer at 3M Health Information Systems in Salt Lake City. Previously he was
Vice-President of Technology at Convenient Automation, specializing in software for the retail industry. He has
worked with FoxPro since version 2.6. You can reach him at cgleave@yahoo.com.

Craig Berntson, Cole Gleave, 2004, 2005


Page 30 of 31
This document was created with Win2PDF available at http://www.daneprairie.com.
The unregistered version of Win2PDF is for evaluation or non-commercial use only.

Anda mungkin juga menyukai