Here is a full implementation. It goes through the logs looking for successful and failed logon including RDP.. adds them to the console windows and also saving them to the database. It then starts re-checking every 5 seconds from the last time it did the full check.. It is also able to extract the key-value pair from the event xml. You may customize as you see fit.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.Eventing.Reader;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Principal;
using System.Data;
using System.Text.RegularExpressions;
using System.Data.SqlClient;
using System.Runtime.InteropServices.ComTypes;
namespace Amsoft_Event_Monitor
{
internal class Program
{
static void Main(string[] args)
{
if (!IsRunningAsAdmin())
{
RelaunchAsAdmin();
return;
}
try
{
// Create DataTable for storing event data
DataTable dt = CreateDataTable();
// Start reading events
var query = new EventLogQuery("Security", PathType.LogName, "*[System[(EventID=4624 or EventID=4625)]]");
using (EventLogReader reader = new EventLogReader(query))
{
EventRecord ev;
while ((ev = reader.ReadEvent()) != null)
{
// Get XML representation of the event log
string xml = ev.ToXml();
var parsedValues = ParseEventWithRegex(xml);
// Add RecordId to the parsed values
parsedValues["RecordId"] = ev.RecordId?.ToString() ?? "-";
// Check if RecordId exists in the database
if (!RecordIdExists(parsedValues["RecordId"]))
{
// Insert the extracted values into the database
InsertEventToDatabase(parsedValues);
// Display event details in the console
DisplayEventDetails(parsedValues);
}
else
{
Console.WriteLine($"Record with RecordId {parsedValues["RecordId"]} already exists, skipping...");
// Display event details in the console
DisplayEventDetails(parsedValues);
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
// Get the current date and time
DateTime startDate = DateTime.Now;
while (true)
{
ReStartAgain(startDate);
}
// Prevent the console from closing immediately
//Console.WriteLine("Press any key to exit...");
//Console.ReadKey();
}
static void ReStartAgain(DateTime startDate)
{
try
{
System.Threading.Thread.Sleep(5000);
// Create DataTable for storing event data
DataTable dt = CreateDataTable();
// Start reading events
//var query = new EventLogQuery("Security", PathType.LogName, "*[System[(EventID=4624 or EventID=4625)]]");
// Create a query with a date filter
var query = new EventLogQuery("Security", PathType.LogName, $"*[System[(EventID=4624 or EventID=4625)]] and *[System[TimeCreated[@SystemTime >= '{startDate:yyyy-MM-ddTHH:mm:ss.fffZ}']]]");
using (EventLogReader reader = new EventLogReader(query))
{
EventRecord ev;
while ((ev = reader.ReadEvent()) != null)
{
// Get XML representation of the event log
string xml = ev.ToXml();
var parsedValues = ParseEventWithRegex(xml);
// Add RecordId to the parsed values
parsedValues["RecordId"] = ev.RecordId?.ToString() ?? "-";
// Check if RecordId exists in the database
if (!RecordIdExists(parsedValues["RecordId"]))
{
// Insert the extracted values into the database
InsertEventToDatabase(parsedValues);
// Display event details in the console
DisplayEventDetails(parsedValues);
}
else
{
Console.WriteLine($"Record with RecordId {parsedValues["RecordId"]} already exists, skipping...");
// Display event details in the console
DisplayEventDetails(parsedValues);
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
}
// Check if the application is running with elevated permissions
static bool IsRunningAsAdmin()
{
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
{
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
}
static void RelaunchAsAdmin()
{
string exeName = Process.GetCurrentProcess().MainModule.FileName;
ProcessStartInfo startInfo = new ProcessStartInfo(exeName)
{
UseShellExecute = true,
Verb = "runas"
};
try
{
Process.Start(startInfo);
}
catch (System.ComponentModel.Win32Exception)
{
Console.WriteLine("Administrator permissions are required to run this application.");
}
Environment.Exit(0);
}
// Create the DataTable to hold event data
static DataTable CreateDataTable()
{
DataTable dt = new DataTable();
dt.Columns.Add("RecordId", typeof(string)); // Add RecordId column
dt.Columns.Add("Security ID", typeof(string));
dt.Columns.Add("Account Name", typeof(string));
dt.Columns.Add("Account Domain", typeof(string));
dt.Columns.Add("Logon ID", typeof(string));
dt.Columns.Add("Logon Type", typeof(string));
dt.Columns.Add("Failure Reason", typeof(string));
dt.Columns.Add("Status", typeof(string));
dt.Columns.Add("Caller Process ID", typeof(string));
dt.Columns.Add("Caller Process Name", typeof(string));
dt.Columns.Add("Workstation Name", typeof(string));
dt.Columns.Add("Source Network Address", typeof(string));
dt.Columns.Add("Source Port", typeof(string));
dt.Columns.Add("Logon Process", typeof(string));
dt.Columns.Add("Authentication Package", typeof(string));
dt.Columns.Add("Key Length", typeof(string));
return dt;
}
// Check if the RecordId already exists in the database
static bool RecordIdExists(string recordId)
{
// Example connection string for SQL Server Authentication
string connectionString = "Server=127.0.0.1; Database=MyEventMonitor; User Id=AdminMyMon; Password=your_password";
using (SqlConnection conn = new SqlConnection(connectionString))
{
string query = "SELECT COUNT(*) FROM EventLogs WHERE RecordId = @RecordId";
SqlCommand cmd = new SqlCommand(query, conn);
cmd.Parameters.AddWithValue("@RecordId", recordId);
conn.Open();
int count = (int)cmd.ExecuteScalar();
return count > 0;
}
}
// Insert the event data into the database
static void InsertEventToDatabase(Dictionary<string, string> eventDetails)
{
string connectionString = "Server=127.0.0.1; Database=MyEventMonitor; User Id=AdminMyMon; Password=your_password";
using (SqlConnection conn = new SqlConnection(connectionString))
{
string query = @"INSERT INTO EventLogs (RecordId, SecurityID, AccountName, AccountDomain, LogonID, LogonType, FailureReason, Status, CallerProcessID, CallerProcessName, WorkstationName, SourceNetworkAddress, SourcePort, LogonProcess, AuthenticationPackage, KeyLength)
VALUES (@RecordId, @SecurityID, @AccountName, @AccountDomain, @LogonID, @LogonType, @FailureReason, @Status, @CallerProcessID, @CallerProcessName, @WorkstationName, @SourceNetworkAddress, @SourcePort, @LogonProcess, @AuthenticationPackage, @KeyLength)";
SqlCommand cmd = new SqlCommand(query, conn);
cmd.Parameters.AddWithValue("@RecordId", eventDetails["RecordId"]);
cmd.Parameters.AddWithValue("@SecurityID", eventDetails["Security ID"]);
cmd.Parameters.AddWithValue("@AccountName", eventDetails["Account Name"]);
cmd.Parameters.AddWithValue("@AccountDomain", eventDetails["Account Domain"]);
cmd.Parameters.AddWithValue("@LogonID", eventDetails["Logon ID"]);
cmd.Parameters.AddWithValue("@LogonType", eventDetails["Logon Type"]);
cmd.Parameters.AddWithValue("@FailureReason", eventDetails["Failure Reason"]);
cmd.Parameters.AddWithValue("@Status", eventDetails["Status"]);
cmd.Parameters.AddWithValue("@CallerProcessID", eventDetails["Caller Process ID"]);
cmd.Parameters.AddWithValue("@CallerProcessName", eventDetails["Caller Process Name"]);
cmd.Parameters.AddWithValue("@WorkstationName", eventDetails["Workstation Name"]);
cmd.Parameters.AddWithValue("@SourceNetworkAddress", eventDetails["Source Network Address"]);
cmd.Parameters.AddWithValue("@SourcePort", eventDetails["Source Port"]);
cmd.Parameters.AddWithValue("@LogonProcess", eventDetails["Logon Process"]);
cmd.Parameters.AddWithValue("@AuthenticationPackage", eventDetails["Authentication Package"]);
cmd.Parameters.AddWithValue("@KeyLength", eventDetails["Key Length"]);
conn.Open();
cmd.ExecuteNonQuery();
}
Console.WriteLine($"Event with RecordId {eventDetails["RecordId"]} inserted into the database.");
}
// Parse event log XML using regex to extract relevant data
static Dictionary<string, string> ParseEventWithRegex(string xml)
{
Dictionary<string, string> details = new Dictionary<string, string>();
// Use regex patterns to capture key values from XML
details["Security ID"] = RegexMatch(xml, @"<Data Name='TargetUserSid'>(.+?)<\/Data>");
details["Account Name"] = RegexMatch(xml, @"<Data Name='TargetUserName'>(.+?)<\/Data>");
details["Account Domain"] = RegexMatch(xml, @"<Data Name='TargetDomainName'>(.+?)<\/Data>");
details["Logon ID"] = RegexMatch(xml, @"<Data Name='TargetLogonId'>(.+?)<\/Data>");
details["Logon Type"] = RegexMatch(xml, @"<Data Name='LogonType'>(.+?)<\/Data>");
details["Failure Reason"] = RegexMatch(xml, @"<Data Name='FailureReason'>(.+?)<\/Data>");
details["Status"] = RegexMatch(xml, @"<Data Name='Status'>(.+?)<\/Data>");
details["Caller Process ID"] = RegexMatch(xml, @"<Data Name='ProcessId'>(.+?)<\/Data>");
details["Caller Process Name"] = RegexMatch(xml, @"<Data Name='ProcessName'>(.+?)<\/Data>");
details["Workstation Name"] = RegexMatch(xml, @"<Data Name='WorkstationName'>(.+?)<\/Data>");
details["Source Network Address"] = RegexMatch(xml, @"<Data Name='IpAddress'>(.+?)<\/Data>");
details["Source Port"] = RegexMatch(xml, @"<Data Name='IpPort'>(.+?)<\/Data>");
details["Logon Process"] = RegexMatch(xml, @"<Data Name='LogonProcessName'>(.+?)<\/Data>");
details["Authentication Package"] = RegexMatch(xml, @"<Data Name='AuthenticationPackageName'>(.+?)<\/Data>");
details["Key Length"] = RegexMatch(xml, @"<Data Name='KeyLength'>(.+?)<\/Data>");
return details;
}
// Display event details in the console
static void DisplayEventDetails(Dictionary<string, string> eventDetails)
{
Console.WriteLine("RecordId: " + eventDetails["RecordId"]); // Display RecordId
Console.WriteLine("Security ID: " + eventDetails["Security ID"]);
Console.WriteLine("Account Name: " + eventDetails["Account Name"]);
Console.WriteLine("Account Domain: " + eventDetails["Account Domain"]);
Console.WriteLine("Logon ID: " + eventDetails["Logon ID"]);
Console.WriteLine("Logon Type: " + eventDetails["Logon Type"]);
Console.WriteLine("Failure Reason: " + eventDetails["Failure Reason"]);
Console.WriteLine("Status: " + eventDetails["Status"]);
Console.WriteLine("Caller Process ID: " + eventDetails["Caller Process ID"]);
Console.WriteLine("Caller Process Name: " + eventDetails["Caller Process Name"]);
Console.WriteLine("Workstation Name: " + eventDetails["Workstation Name"]);
Console.WriteLine("Source Network Address: " + eventDetails["Source Network Address"]);
Console.WriteLine("Source Port: " + eventDetails["Source Port"]);
Console.WriteLine("Logon Process: " + eventDetails["Logon Process"]);
Console.WriteLine("Authentication Package: " + eventDetails["Authentication Package"]);
Console.WriteLine("Key Length: " + eventDetails["Key Length"]);
Console.WriteLine(new string('-', 80));
}
// Helper method to extract value using regex
static string RegexMatch(string input, string pattern)
{
var match = Regex.Match(input, pattern);
return match.Success ? match.Groups[1].Value.Trim() : "-";
}
}
}
the use of memory might be high and I don't want to cause any issues on the devices where this solution will get deployed.
Meaning the user will use the application while its moving logs? I'd just use a Task to handle the work.. – BlakeyEventLog
:new EventLog("Security")
. Also, if it runs as a service, I would copy the existing Logs during the first run (on background), then hook theEntryWrittenEventHandler
withinEventLog
in your service to handle newly created logs. – BlakeyEntryWrittenEventHandler
ofnew EventLog("Security")
all newly written security events will be passed your handler. This doesn't change the fact that on the first run you'll need to iterate through all existing items though. – Blakey