424 lines
13 KiB
ObjectPascal
424 lines
13 KiB
ObjectPascal
{*******************************************************}
|
|
{ MiTeC Common Routines }
|
|
{ Machine Journal }
|
|
{ }
|
|
{ }
|
|
{ Copyright (c) 1997-2021 Michal Mutl }
|
|
{ }
|
|
{*******************************************************}
|
|
|
|
{$INCLUDE Compilers.inc}
|
|
|
|
unit MiTeC_MachineJournal;
|
|
|
|
interface
|
|
|
|
uses {$IFDEF RAD9PLUS}
|
|
WinAPI.Windows, System.Classes, System.SysUtils,
|
|
{$ELSE}
|
|
Windows, Classes, SysUtils,
|
|
{$ENDIF}
|
|
MiTeC_EventLogNT, MiTeC_AccountsNT;
|
|
|
|
type
|
|
TEventKind = (ekNone, ekStartup, ekShutdown, ekSleep, ekWakeup, ekLogin, ekLogoff, ekLoginFail, ekCrash, ekUpdate, ekLock, ekUnlock);
|
|
|
|
TEventRecord = record
|
|
ID: Cardinal;
|
|
Kind: TEventKind;
|
|
Timestamp: TDateTime;
|
|
Source,
|
|
Description,
|
|
Details: string;
|
|
_Timestamp: TFiletime;
|
|
end;
|
|
|
|
TMachineJournal = class
|
|
private
|
|
dl,il: TStringList;
|
|
FEL: TEventLog;
|
|
FRecords: array of TEventRecord;
|
|
FMachine: string;
|
|
FHost: string;
|
|
FUA: TAccounts;
|
|
procedure Add(AID: Cardinal; AKind: TEventKind; ADatetime: TDateTime; ATimestamp: TFiletime; const ASource,ADesc,ADetails: string);
|
|
//procedure Delete(AIndex: integer);
|
|
procedure Sort;
|
|
function GetCount: integer;
|
|
function GetRecord(AIndex: integer): TEventRecord;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Clear;
|
|
function Find(AKind: TEventKind; ATimestamp: TDateTime): Integer;
|
|
function FindEx(AKind: TEventKind; ATimestamp: TDateTime): Integer;
|
|
procedure Save(const AFilename: string);
|
|
function RefreshData: Cardinal;
|
|
|
|
property Host: string read FHost write FHost;
|
|
property Machine: string read FMachine write FMachine;
|
|
property Count: integer read GetCount;
|
|
property Records[AIndex: integer]: TEventRecord read GetRecord;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses {$IFDEF RAD9PLUS}
|
|
System.DateUtils, System.Math,
|
|
{$ELSE}
|
|
DateUtils, Math,
|
|
{$ENDIF}
|
|
MiTeC_Routines, MiTeC_StrUtils, MiTeC_Datetime;
|
|
|
|
{ TMachineJournal }
|
|
|
|
procedure TMachineJournal.Add(AID: Cardinal; AKind: TEventKind;
|
|
ADatetime: TDateTime; ATimestamp: TFiletime; const ASource, ADesc, ADetails: string);
|
|
begin
|
|
SetLength(FRecords,Length(FRecords)+1);
|
|
with FRecords[High(FRecords)] do begin
|
|
ID:=AID;
|
|
Kind:=AKind;
|
|
Timestamp:=ADatetime;
|
|
Source:=ASource;
|
|
Description:=ADesc;
|
|
Details:=ADetails;
|
|
_Timestamp:=ATimestamp;
|
|
end;
|
|
end;
|
|
|
|
procedure TMachineJournal.Clear;
|
|
begin
|
|
Finalize(FRecords);
|
|
end;
|
|
|
|
constructor TMachineJournal.Create;
|
|
begin
|
|
FMachine:=MachineName;
|
|
FHost:=MachineName;
|
|
dl:=TStringList.Create;
|
|
il:=TStringList.Create;
|
|
il.Sorted:=True;
|
|
il.Duplicates:=dupIgnore;
|
|
FEL:=TEventLog.Create;
|
|
FUA:=TAccounts.Create;
|
|
end;
|
|
|
|
{procedure TMachineJournal.Delete(AIndex: integer);
|
|
var
|
|
i: integer;
|
|
begin
|
|
for i:=AIndex to High(FRecords)-1 do
|
|
FRecords[i]:=FRecords[i+1];
|
|
SetLength(FRecords,High(FRecords));
|
|
end;}
|
|
|
|
destructor TMachineJournal.Destroy;
|
|
begin
|
|
Clear;
|
|
FEL.Free;
|
|
FUA.Free;
|
|
dl.Free;
|
|
il.Free;
|
|
inherited;
|
|
end;
|
|
|
|
function TMachineJournal.Find(AKind: TEventKind;
|
|
ATimestamp: TDateTime): Integer;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result:=-1;
|
|
for i:=0 to High(FRecords) do
|
|
if (FRecords[i].Kind=AKind) and SameDateTime(FRecords[i].Timestamp,ATimestamp) then begin
|
|
Result:=i;
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
function TMachineJournal.FindEx(AKind: TEventKind;
|
|
ATimestamp: TDateTime): Integer;
|
|
var
|
|
i: Integer;
|
|
k: set of TEventKind;
|
|
begin
|
|
Result:=-1;
|
|
k:=[AKind];
|
|
if AKind=ekLogin then
|
|
k:=k+[ekLogoff];
|
|
if AKind=ekLogoff then
|
|
k:=k+[ekLogin];
|
|
for i:=0 to High(FRecords) do
|
|
if (FRecords[i].Kind in k) and SameDateTime(FRecords[i].Timestamp,ATimestamp) then begin
|
|
Result:=i;
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
function TMachineJournal.GetCount: integer;
|
|
begin
|
|
Result:=Length(FRecords);
|
|
end;
|
|
|
|
function TMachineJournal.GetRecord(AIndex: integer): TEventRecord;
|
|
begin
|
|
Result:=FRecords[AIndex];
|
|
end;
|
|
|
|
function TMachineJournal.RefreshData: Cardinal;
|
|
var
|
|
dt: TDateTime;
|
|
i,lt,idx: Integer;
|
|
s,d,un: string;
|
|
sleep: Boolean;
|
|
k: TEventKind;
|
|
u: PUserInfo;
|
|
fs: TFormatSettings;
|
|
begin
|
|
sleep:=False;
|
|
Clear;
|
|
|
|
{$IFDEF RAD9PLUS}
|
|
fs:=TFormatSettings.Create;
|
|
{$ELSE}
|
|
GetLocaleFormatSettings(GetThreadLocale,fs);
|
|
{$ENDIF};
|
|
fs.DateSeparator:='/';
|
|
fs.ShortDateFormat:='M/D/YYYY';
|
|
FUA.Machine:=FMachine;
|
|
FUA.RefreshUsers;
|
|
FEL.ForceOldAPI:=True;
|
|
FEL.ExpandMessages:=False;
|
|
FEL.ReverseOrder:=False;
|
|
FEL.Machine:=FHost;
|
|
FEL.ConvertTimeToLocal:=True;
|
|
FEL.SourceFilter:='Microsoft-Windows-Kernel-General,Microsoft-Windows-Kernel-Power,EventLog,Microsoft-Windows-WindowsUpdateClient'; //,Microsoft-Windows-Power-Troubleshooter;
|
|
FEL.SourceName:='System';
|
|
FEL.RefreshData(True);
|
|
//FEL.Sort;
|
|
for i:=0 to FEL.RecordCount-1 do begin
|
|
if (CompareDate(FEL.Records[i].DateTime,Date)<>1) and (
|
|
((FEL.Records[i].EventID in [12,13]) and SameText(FEL.Records[i].Source,'Microsoft-Windows-Kernel-General')) or
|
|
//((FEL.Records[i].EventID=1) and SameText(FEL.Records[i].Source,'Microsoft-Windows-Power-Troubleshooter')) or
|
|
((FEL.Records[i].EventID=42) and SameText(FEL.Records[i].Source,'Microsoft-Windows-Kernel-Power')) or
|
|
((FEL.Records[i].EventID=41) and SameText(FEL.Records[i].Source,'Microsoft-Windows-Kernel-Power')) or
|
|
(((FEL.Records[i].EventID=1) and SameText(FEL.Records[i].Source,'Microsoft-Windows-Kernel-General')) and sleep) or
|
|
((FEL.Records[i].EventID=6008) and SameText(FEL.Records[i].Source,'EventLog')) or
|
|
((FEL.Records[i].EventID=6006) and SameText(FEL.Records[i].Source,'EventLog') and (OS<=osVista)) or
|
|
((FEL.Records[i].EventID=6009) and SameText(FEL.Records[i].Source,'EventLog') and (OS<=osVista)) or
|
|
((FEL.Records[i].EventID=19) and SameText(FEL.Records[i].Source,'Microsoft-Windows-WindowsUpdateClient'))
|
|
)
|
|
then begin
|
|
sleep:=(FEL.Records[i].EventID=42) and SameText(FEL.Records[i].Source,'Microsoft-Windows-Kernel-Power');
|
|
dt:=FEL.Records[i].DateTime;
|
|
s:='';
|
|
d:='';
|
|
dl.Text:=FEL.Records[i].Values;
|
|
if (FEL.Records[i].EventID in [1,12]) or (FEL.Records[i].EventID=6009) then begin
|
|
if (FEL.Records[i].EventID=1) then begin
|
|
s:='The system has resumed from sleep';
|
|
k:=ekWakeup;
|
|
end else begin
|
|
s:='The operating system started';
|
|
k:=ekStartup;
|
|
end;
|
|
end else begin
|
|
if (FEL.Records[i].EventID=6008) or (FEL.Records[i].EventID=41) then begin
|
|
s:='Unexpected shutdown';
|
|
if (FEL.Records[i].EventID=6008) then begin
|
|
d:=FastStringReplace(GetCSVData(FEL.Records[i].Values,1,#$D),'?','')+' '+
|
|
FastStringReplace(GetCSVData(FEL.Records[i].Values,0,#$D),'?','');
|
|
d:=FastStringReplace(s,#$200E,'');
|
|
//dt:=StrToDateTimeDef(s,FEL.Records[i].DateTime{$IFNDEF FPC},fs{$ENDIF});
|
|
d:=DateTimeToStr(StrToDateTimeDef(d,FEL.Records[i].DateTime{$IFNDEF FPC},fs{$ENDIF}));
|
|
end else if (FEL.Records[i].EventID=41) then begin
|
|
s:=s+' (Power issue)';
|
|
d:=GetCSVData(FEL.Records[i].Values,6,#$D);
|
|
d:=DateTimeToStrDef(Int64ToDateTime(StrToInt64Def(d,0),True),'');
|
|
if d<>'' then
|
|
d:='Power button pressed at '+d;
|
|
end;
|
|
k:=ekCrash;
|
|
end else if (FEL.Records[i].EventID=42) then begin
|
|
s:='The system entered sleep';
|
|
k:=ekSleep;
|
|
end else if (FEL.Records[i].EventID=19) then begin
|
|
s:='The operating system was succesfully updated';
|
|
k:=ekUpdate;
|
|
if (dl.Count>0) then
|
|
d:=dl[0];
|
|
end else begin
|
|
s:='The operating system was shut down';
|
|
k:=ekShutdown;
|
|
end;
|
|
end;
|
|
if (k<>ekNone) then
|
|
Add(FEL.Records[i].EventID,k,dt,FEL.Records[i].Timestamp,FEL.Records[i].Source,s,d);
|
|
end;
|
|
end;
|
|
|
|
FEL.ClearRecords;
|
|
FEL.ConvertTimeToLocal:=True;
|
|
FEL.SourceFilter:='Microsoft-Windows-Security-Auditing';
|
|
FEL.SourceName:='Security';
|
|
Result:=FEL.RefreshData(True);
|
|
//FEL.Sort;
|
|
for i:=0 to FEL.RecordCount-1 do begin
|
|
if (FEL.Records[i].EventID=4624) or
|
|
(FEL.Records[i].EventID=4647) or
|
|
(FEL.Records[i].EventID=4634) or
|
|
(FEL.Records[i].EventID=4625) or
|
|
(FEL.Records[i].EventID=4800) or
|
|
(FEL.Records[i].EventID=4801) then begin
|
|
s:='';
|
|
d:='';
|
|
k:=ekNone;
|
|
dl.Text:=FEL.Records[i].Values;
|
|
if (FEL.Records[i].EventID=4624) and (dl.Count>19) then begin
|
|
lt:=StrToIntDef(dl[8],0);
|
|
case lt of
|
|
2: s:='locally';
|
|
10: s:='via RDP';
|
|
3: s:='for a shared resource';
|
|
7: s:='as unlock';
|
|
end;
|
|
u:=FUA.UserBySID[dl[4]];
|
|
if Assigned(u) then
|
|
un:=Alter(u.FullName,u.Name)
|
|
else
|
|
un:=dl[5];
|
|
s:=Trim(Format('User %s logged in %s',[un,s]));
|
|
d:=Format('Source IP: %s:%s',[dl[18],dl[19]]);
|
|
if not(lt in [4,5,8,9{,11}]) and (dl[18]<>'-') and (AnsiSameText(dl[6],FMachine) or SameText(dl[6],'MicrosoftAccount')) and (dl[5]<>'') then begin
|
|
k:=ekLogin;
|
|
il.Add(dl[7]);
|
|
end;
|
|
end else if ((FEL.Records[i].EventID=4647) and (dl.Count>3)) or
|
|
((FEL.Records[i].EventID=4634) and (dl.Count>4))
|
|
then begin
|
|
if dl.Count>4 then begin
|
|
lt:=StrToIntDef(dl[4],0);
|
|
case lt of
|
|
2: s:='locally';
|
|
10: s:='via RDP';
|
|
3: s:='for a shared resource';
|
|
end;
|
|
end else
|
|
lt:=0;
|
|
u:=FUA.UserBySID[dl[0]];
|
|
if Assigned(u) then
|
|
un:=Alter(u.FullName,u.Name)
|
|
else
|
|
un:=dl[1];
|
|
s:=Trim(Format('User %s logged off %s',[un,s]));
|
|
idx:=il.IndexOf(dl[3]);
|
|
if not(lt in [4,5,8,9{,11}]) and AnsiSameText(dl[2],FMachine) and (idx>-1) then begin
|
|
k:=ekLogoff;
|
|
il.Delete(idx);
|
|
end;
|
|
end else if (FEL.Records[i].EventID=4800) then begin
|
|
u:=FUA.UserBySID[dl[0]];
|
|
if Assigned(u) then
|
|
un:=Alter(u.FullName,u.Name)
|
|
else
|
|
un:=dl[1];
|
|
s:=Format('User %s locked workstation',[un]);
|
|
d:=Format('Workstation: %s',[dl[2]]);
|
|
k:=ekLock;
|
|
end else if (FEL.Records[i].EventID=4801) then begin
|
|
u:=FUA.UserBySID[dl[0]];
|
|
if Assigned(u) then
|
|
un:=Alter(u.FullName,u.Name)
|
|
else
|
|
un:=dl[1];
|
|
s:=Format('User %s unlocked workstation',[un]);
|
|
d:=Format('Workstation: %s',[dl[2]]);
|
|
k:=ekUnlock;
|
|
end else if (FEL.Records[i].EventID=4625) and (dl.Count>13) then begin
|
|
u:=FUA.UserBySID[dl[4]];
|
|
if Assigned(u) then
|
|
un:=Alter(u.FullName,u.Name)
|
|
else
|
|
un:=dl[5];
|
|
s:=Format('User %s login failed',[un]);
|
|
d:=Format('Workstation: %s',[dl[13]]);
|
|
k:=ekLoginFail;
|
|
end;
|
|
{if (k<>ekNone) then begin
|
|
idx:=FindEx(k,FEL.Records[i].DateTime);
|
|
if idx=-1 then
|
|
Add(FEL.Records[i].EventID,k,FEL.Records[i].DateTime,FEL.Records[i].Source,s,d)
|
|
else if (k in [ekLogin,ekLogoff]) and (k<>Records[idx].Kind) then
|
|
Delete(idx);
|
|
end;}
|
|
if (k<>ekNone) and (Find(k,FEL.Records[i].DateTime)=-1) then
|
|
Add(FEL.Records[i].EventID,k,FEL.Records[i].DateTime,FEL.Records[i].Timestamp,FEL.Records[i].Source,s,d);
|
|
end;
|
|
end;
|
|
|
|
FEL.ClearRecords;
|
|
|
|
Sort;
|
|
il.Clear;
|
|
dl.Clear;
|
|
end;
|
|
|
|
procedure TMachineJournal.Save(const AFilename: string);
|
|
var
|
|
sl: TStringList;
|
|
r: TEventRecord;
|
|
begin
|
|
sl:=TStringList.Create;
|
|
try
|
|
sl.Add('ID;Kind;Timestamp;Source;Description;Details');
|
|
for r in FRecords do
|
|
sl.Add(Format('%d;%d;%s;%s;%s;%s',[r.ID,Integer(r.Kind),DateTimeToStr(r.Timestamp),r.Source,r.Description,r.Details]));
|
|
sl.SaveToFile(AFilename);
|
|
finally
|
|
sl.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TMachineJournal.Sort;
|
|
|
|
procedure QuickSort(ALo, AHi: integer);
|
|
var
|
|
Lo,Hi,Mid: Integer;
|
|
r: TEventRecord;
|
|
begin
|
|
repeat
|
|
Lo:=ALo;
|
|
Hi:=AHi;
|
|
Mid:=(Lo+Hi) div 2;
|
|
repeat
|
|
while CompareFiletime({$IFDEF FPC}@{$ENDIF}FRecords[Lo]._Timestamp,{$IFDEF FPC}@{$ENDIF}FRecords[Mid]._Timestamp)<0 do
|
|
Inc(Lo);
|
|
while CompareFiletime({$IFDEF FPC}@{$ENDIF}FRecords[Hi]._Timestamp,{$IFDEF FPC}@{$ENDIF}FRecords[Mid]._Timestamp)>0 do
|
|
Dec(Hi);
|
|
if Lo<=Hi then begin
|
|
if Lo<>Hi then begin
|
|
r:=FRecords[Lo];
|
|
FRecords[Lo]:=FRecords[Hi];
|
|
FRecords[Hi]:=r;
|
|
if Mid=Lo then
|
|
Mid:=Hi
|
|
else if Mid=Hi then
|
|
Mid:=Lo;
|
|
end;
|
|
Inc(Lo);
|
|
Dec(Hi);
|
|
end;
|
|
until Lo>Hi;
|
|
if ALo<Hi then
|
|
QuickSort(ALo,Hi);
|
|
ALo:=Lo;
|
|
until Lo>=AHi;
|
|
end;
|
|
|
|
begin
|
|
if Length(FRecords)>0 then
|
|
QuickSort(0,High(FRecords));
|
|
end;
|
|
|
|
end.
|