Saturday, 12 February 2022

Writing to and reading from the windows event Log

I've written some classes that writes to and reads from the Windows event log. The TRBWindowsEventLogs class contains the writer and reader objects, when creating the object from this class it requires the application name to be passed, this is can be different is required to the application, but is also used to retrieve the log entries.

unit uWindowsEvents;

interface

uses classes, Windows, SvcMgr, Vcl.StdCtrls, Generics.Collections;

type

  { /----------------------------------------------------------------------------------------------------------------- }
  TRBWindowsEvent = class(TObject)
  strict private
    fRecordNumber: integer;
    fMessage: string;
    fComputerName: string;
    fEventData: string;
    fLogFile: string;
    fCategory: string;
    fEventCode: integer;
  public
    property Category: string read fCategory write fCategory;
    property ComputerName: string read fComputerName write fComputerName;
    property EventCode: integer read fEventCode write fEventCode;
    property Message: string read fMessage write fMessage;
    property RecordNumber: integer read fRecordNumber write fRecordNumber;
    property LogFile: string read fLogFile write fLogFile;
    property EventData: string read fEventData write fEventData;

    procedure Populate(aEvent: OLEVariant);
  end;

  { /----------------------------------------------------------------------------------------------------------------- }
  IRBEventReaderOutput = interface
    ['{4ADA872A-C1DA-4B3B-BE67-0F628C61039C}']
    procedure AddEventLog(aEventLog: TRBWindowsEvent);
    procedure SaveToFile(aFileName: string);
    function OutputString: string;
  end;

  { /----------------------------------------------------------------------------------------------------------------- }
  TRBWindowsEventLogsReader = class(TObjectList<TRBWindowsEvent>)
  strict private
    fApplicationName: string;
    fMaxNumberOfEntries: integer;

    function EventQuery: string;

    procedure GetWindowsEventLogs;
    procedure AddErrorMessage(aErrorMessage: string);
  public
    property MaxNumberOfEntries: integer read fMaxNumberOfEntries write fMaxNumberOfEntries;

    constructor Create(aApplicationName: string; aMaxNumberOfEntries: integer);

    procedure PopulateEvents(aReaderOutput: IRBEventReaderOutput);
  end;

  { /----------------------------------------------------------------------------------------------------------------- }
  TRBWindowsEventLogsWriter = class(TObject)
  strict private
    fWindowsEventLogger: TEventLogger;
  public
    constructor Create(aApplicationName: string);
    destructor Destroy; override;

    procedure WriteInformationToWindowsEvents(aMessage: string);
    procedure WriteErrorToWindowsEvents(aMessage: string);
  end;

  { /----------------------------------------------------------------------------------------------------------------- }
  TRBWindowsEventLogs = class(TObject)
  strict private
    fApplicationName: string;

    fWriter: TRBWindowsEventLogsWriter;
    fReader: TRBWindowsEventLogsReader;

  public
    property Writer: TRBWindowsEventLogsWriter read fWriter;
    property Reader: TRBWindowsEventLogsReader read fReader;

    constructor Create(aApplicationName: string); overload;
    constructor Create(aApplicationName: string; aMaxNumberOfEntries: integer); overload;
    destructor Destroy; override;
  end;

implementation

uses SysUtils, ComObj, ActiveX, System.Variants, DateUtils;

{ TRBWindowsEvent }

procedure TRBWindowsEvent.Populate(aEvent: OLEVariant);
var
  insertion: array of String;
  i: integer;
begin
  fCategory := string(aEvent.Category);
  fComputerName := string(aEvent.ComputerName);
  fEventCode := integer(aEvent.EventCode);
  fMessage := string(aEvent.Message);
  fRecordNumber := integer(aEvent.RecordNumber);
  fLogFile := string(aEvent.LogFile);

  if not VarIsNull(aEvent.InsertionStrings) then
  begin
    insertion := aEvent.InsertionStrings;
    for i := VarArrayLowBound(insertion, 1) to VarArrayHighBound(insertion, 1) do
    begin
      fEventData := fEventData + insertion[i];
    end;
  end;
end;

{ TRBWindowsEvents }

constructor TRBWindowsEventLogs.Create(aApplicationName: string);
begin
  Create(aApplicationName, 100);
end;

constructor TRBWindowsEventLogs.Create(aApplicationName: string; aMaxNumberOfEntries: integer);
begin
  inherited Create;
  fApplicationName := aApplicationName;
  fWriter := TRBWindowsEventLogsWriter.Create(aApplicationName);
  fReader := TRBWindowsEventLogsReader.Create(aApplicationName, aMaxNumberOfEntries);
end;

destructor TRBWindowsEventLogs.Destroy;
begin
  FreeAndNil(fReader);
  FreeAndNil(fWriter);
  inherited;
end;

{ TRBWindowsEventLogsWriter }

constructor TRBWindowsEventLogsWriter.Create(aApplicationName: string);
begin
  inherited Create;
  fWindowsEventLogger := TEventLogger.Create(aApplicationName);
end;

destructor TRBWindowsEventLogsWriter.Destroy;
begin
  FreeAndNil(fWindowsEventLogger);
  inherited;
end;

procedure TRBWindowsEventLogsWriter.WriteErrorToWindowsEvents(aMessage: string);
begin
  fWindowsEventLogger.LogMessage(aMessage, EVENTLOG_ERROR_TYPE);
end;

procedure TRBWindowsEventLogsWriter.WriteInformationToWindowsEvents(aMessage: string);
begin
  fWindowsEventLogger.LogMessage(aMessage, EVENTLOG_INFORMATION_TYPE);
end;

{ TRBWindowsEventLogsReader }

constructor TRBWindowsEventLogsReader.Create(aApplicationName: string; aMaxNumberOfEntries: integer);
begin
  inherited Create;
  fApplicationName := aApplicationName;
  fMaxNumberOfEntries := aMaxNumberOfEntries;
end;

function TRBWindowsEventLogsReader.EventQuery: string;
begin
  Result := 'SELECT * FROM Win32_NTLogEvent Where SourceName = "' + fApplicationName +
    '" AND Logfile = "Application" AND TimeGenerated >= "' + DateTimeToStr(IncDay(Now(), -1)) + '"';
end;

procedure TRBWindowsEventLogsReader.AddErrorMessage(aErrorMessage: string);
var
  event: TRBWindowsEvent;
begin
  event := TRBWindowsEvent.Create;
  event.Category := 'Error';
  event.Message := aErrorMessage;
  Add(event);
end;

procedure TRBWindowsEventLogsReader.GetWindowsEventLogs;
const
  wbemForwardOnly = 32;
  wbemReturnImmediately = 16;
var
  SWbemLocator: OLEVariant;
  WMIService: OLEVariant;
  WbemObjectSet: OLEVariant;
  WbemObject: OLEVariant;
  oEnum: IEnumvariant;
  iValue: LongWord;
  iCount: integer;
  event: TRBWindowsEvent;
begin
  try
    Clear;
    iCount := 0;
    SWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
    WMIService := SWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
    WbemObjectSet := WMIService.ExecQuery(EventQuery(), 'WQL', wbemReturnImmediately + wbemForwardOnly);
    oEnum := IUnknown(WbemObjectSet._NewEnum) as IEnumvariant;
    while oEnum.Next(1, WbemObject, iValue) = 0 do
    begin
      event := TRBWindowsEvent.Create;
      event.Populate(WbemObject);
      Add(event);
      WbemObject := Unassigned;
      inc(iCount);
      if iCount > fMaxNumberOfEntries then
      begin
        Break;
      end;
    end;
  except
    on E: EOleException do
      AddErrorMessage(Format('EOleException %s %x', [E.Message, E.ErrorCode]));
    on E: Exception do
      AddErrorMessage(E.Classname + ':' + E.Message);
  end;
end;

procedure TRBWindowsEventLogsReader.PopulateEvents(aReaderOutput: IRBEventReaderOutput);
var
  event: TRBWindowsEvent;
begin
  GetWindowsEventLogs;
  for event in Self do
  begin
    aReaderOutput.AddEventLog(event);
  end;
end;

end.


To use these classes I've created some output classes implemented from the IRBEventReaderOutput interface.

unit uWindowsEventsOutput;

interface

uses classes, Windows, uWindowsEvents, System.JSON;

type
  { /----------------------------------------------------------------------------------------------------------------- }
  TStringsReaderOutput = class(TInterfacedObject, IRBEventReaderOutput)
  strict private
    fStrings: TStringList;
  public
    procedure AddEventLog(aEventLog: TRBWindowsEvent);
    procedure SaveToFile(aFileName: string);
    function OutputString: string;

    constructor Create;
    destructor Destroy; override;
  end;

  { /----------------------------------------------------------------------------------------------------------------- }
  TJSONReaderOutput = class(TInterfacedObject, IRBEventReaderOutput)
  strict private
    fJSON_Array: TJSONArray;
  public
    procedure AddEventLog(aEventLog: TRBWindowsEvent);
    procedure SaveToFile(aFileName: string);
    function OutputString: string;

    constructor Create;
    destructor Destroy; override;
  end;

  { /----------------------------------------------------------------------------------------------------------------- }
  TCSVReaderOutput = class(TInterfacedObject, IRBEventReaderOutput)
  strict private
    fCSVString: string;
    procedure AddHeader;
    procedure AddLine(aLine: string);
  public
    procedure AddEventLog(aEventLog: TRBWindowsEvent);
    procedure SaveToFile(aFileName: string);
    function OutputString: string;
  end;

implementation

uses SysUtils;

{ TStringsReaderOutput }

constructor TStringsReaderOutput.Create;
begin
  inherited Create;
  fStrings := TStringList.Create;
end;

destructor TStringsReaderOutput.Destroy;
begin
  FreeAndNil(fStrings);
  inherited;
end;

function TStringsReaderOutput.OutputString: string;
begin
  Result := fStrings.CommaText;
end;

procedure TStringsReaderOutput.SaveToFile(aFileName: string);
begin
  fStrings.SaveToFile(aFileName);
end;

procedure TStringsReaderOutput.AddEventLog(aEventLog: TRBWindowsEvent);
begin
  fStrings.Add('Category: ' + aEventLog.Category);
  fStrings.Add('Computer Name: ' + aEventLog.ComputerName);
  fStrings.Add('Event Code: ' + aEventLog.EventCode.ToString);
  fStrings.Add('Message: ' + aEventLog.Message);
  fStrings.Add('Record Number: ' + aEventLog.RecordNumber.ToString);
  fStrings.Add('Log File: ' + aEventLog.LogFile);
  fStrings.Add('Event Data');
  fStrings.Add(aEventLog.EventData);
  fStrings.Add('-------------------------');
end;


{ TJSONReaderOutput }

constructor TJSONReaderOutput.Create;
begin
  inherited Create;
  fJSON_Array := TJSONArray.Create;
end;

destructor TJSONReaderOutput.Destroy;
begin
  FreeAndNil(fJSON_Array);
  inherited;
end;

function TJSONReaderOutput.OutputString: string;
begin
  if Assigned(fJSON_Array) then
  begin
    Result := fJSON_Array.ToJSON;
  end;
end;

procedure TJSONReaderOutput.SaveToFile(aFileName: string);
var
  sl: TStringList;
begin
  sl := TStringList.Create;
  try
    sl.Text := fJSON_Array.ToJSON;
    sl.SaveToFile(aFileName);
  finally
    sl.Free;
  end;
end;

procedure TJSONReaderOutput.AddEventLog(aEventLog: TRBWindowsEvent);
var
  JSON_Object: TJSONObject;
begin
  JSON_Object := TJSONObject.Create;
  JSON_Object.AddPair('category', aEventLog.Category);
  JSON_Object.AddPair('computerName', aEventLog.ComputerName);
  JSON_Object.AddPair('eventCode', aEventLog.EventCode.ToString);
  JSON_Object.AddPair('message', aEventLog.Message);
  JSON_Object.AddPair('recordNumber', aEventLog.RecordNumber.ToString);
  JSON_Object.AddPair('logFile', aEventLog.LogFile);
  JSON_Object.AddPair('eventData', aEventLog.EventData);
  fJSON_Array.Add(JSON_Object);
end;


{ TCSVReaderOutput }

procedure TCSVReaderOutput.AddEventLog(aEventLog: TRBWindowsEvent);
var
  s: string;
  procedure AddValue(aValue: string);
  begin
    s := s + aValue + ',';
  end;

begin
  AddHeader;
  AddValue(aEventLog.Category);
  AddValue(aEventLog.ComputerName);
  AddValue(aEventLog.EventCode.ToString);
  AddValue(aEventLog.Message);
  AddValue(aEventLog.RecordNumber.ToString);
  AddValue(aEventLog.LogFile);
  AddValue(aEventLog.EventData);
  AddLine(s);
end;

procedure TCSVReaderOutput.AddHeader;
begin
  if fCSVString = '' then
  begin
    AddLine('category,computerName,eventCode,message,recordNumber,logFile,eventData,');
  end;
end;

procedure TCSVReaderOutput.AddLine(aLine: string);
begin
  fCSVString := fCSVString + aLine + chr(13) + chr(10);
end;

function TCSVReaderOutput.OutputString: string;
begin
  Result := fCSVString;
end;

procedure TCSVReaderOutput.SaveToFile(aFileName: string);
var
  sl: TStringList;
begin
  sl := TStringList.Create;
  try
    sl.Text := fCSVString;
    sl.SaveToFile(aFileName);
  finally
    sl.Free;
  end;
end;

end.

Here are some examples of how to use the output classes.

To write to the Events Log.

procedure TfrmWindowsEvents.AddLogBtnClick(Sender: TObject);
begin
  fWindowsEvents.Writer.WriteInformationToWindowsEvents(MessageEdt.Text);
end;

To read from the Events Log

procedure TfrmWindowsEvents.StringBtnClick(Sender: TObject);
var
  stringsReader: IRBEventReaderOutput;
begin
  stringsReader := TStringsReaderOutput.Create;

  fWindowsEvents.Reader.PopulateEvents(stringsReader);
  stringsReader.SaveToFile('stringsoutput.txt');
  MemoEvents.Lines.CommaText := stringsReader.OutputString;
end;

procedure TfrmWindowsEvents.JsonBtnClick(Sender: TObject);
var
  jsonReader: IRBEventReaderOutput;
begin
  jsonReader := TJSONReaderOutput.Create;

  fWindowsEvents.Reader.PopulateEvents(jsonReader);
  jsonReader.SaveToFile('jsonoutput.txt');
  MemoEvents.Lines.Text := jsonReader.OutputString;
end;

procedure TfrmWindowsEvents.CsvBtnClick(Sender: TObject);
var
  csvReader: IRBEventReaderOutput;
begin
  csvReader := TCSVReaderOutput.Create;

  fWindowsEvents.Reader.PopulateEvents(csvReader);
  csvReader.SaveToFile('csvoutput.csv');
  MemoEvents.Lines.Text := csvReader.OutputString;
end;

One thing to note is that querying the events can be very slow depending on the amount of logs in the database. You can improve this in the Event Viewer application by selecting Windows Logs > Application and from either the main menu 'Action' or from the right click menu select 'Clear Logs'.


No comments:

Post a Comment