Beispiel #1
0
        /// <summary>
        /// Entry for report creation.  Will call CreateCSVReport or CreateXMLReport.
        /// </summary>
        /// <param name="reportFileBasePath">The report file base path.
        /// If specified, the directory for the report files is obtained from this path.
        /// If not specified, the Desktop directory is used.
        /// </param>
        private static void CreateReport(string reportFileBasePath = "")
        {
            Console.WriteLine($"{DateTime.Now.YMDHMSFriendly()} - {ObjectExtensions.CurrentMethodName()}");

            if (EventData.Count == 0)
            {
                Console.WriteLine($"{DateTime.Now.YMDHMSFriendly()} - {ObjectExtensions.CurrentMethodName()} Report not created due to no events collected.");
                return;
            }

            if (!string.IsNullOrWhiteSpace(reportFileBasePath))
            {
                reportFileBasePath = Path.GetDirectoryName(reportFileBasePath);
            }

            if (string.IsNullOrWhiteSpace(reportFileBasePath))
            {
                reportFileBasePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory, Environment.SpecialFolderOption.DoNotVerify), "EventLogParser");
                if (!Directory.Exists(reportFileBasePath))
                {
                    Directory.CreateDirectory(reportFileBasePath);
                }
            }

            if (ReportFormat == ReportFormat.CSV)
            {
                CreateCSVReport(reportFileBasePath);
            }
            else if (ReportFormat == ReportFormat.XML)
            {
                CreateXMLReport(reportFileBasePath);
            }
        }
Beispiel #2
0
 private void DeleteDatabaseObjects()
 {
     using (var entityManager = new CountriesEntityManager()) {
         try {
             var sqlCommand = new SqlCommand($"DELETE [Countries].[dbo].[Continent] WHERE ([Abbreviation] = '{continentAbbreviationCreate}');");
             entityManager.ExecuteScalarSql(sqlCommand);
             sqlCommand = new SqlCommand($"DELETE [Countries].[dbo].[Continent] WHERE ([Abbreviation] = '{continentAbbreviationUpdate}');");
             entityManager.ExecuteScalarSql(sqlCommand);
             sqlCommand = new SqlCommand($"DELETE [Countries].[dbo].[Continent] WHERE ([Abbreviation] = '{continentAbbreviationDelete}');");
             entityManager.ExecuteScalarSql(sqlCommand);
         }
         catch (Exception e) {
             Log.Error($"[ThreadID: {Thread.CurrentThread.ManagedThreadId}] {ObjectExtensions.CurrentMethodName()} {e.VerboseExceptionString()}");
         }
     }
 }
Beispiel #3
0
        protected List <ValidationResult> Get_Validation_Results <TEntity>(TEntity entity) where TEntity : class, IValidatableObject, IObjectWithState
        {
            var validationResults = entity.Validate(null).ToList();

            if (validationResults.Count > 0)
            {
                Log.Warn($"[ThreadID: {Thread.CurrentThread.ManagedThreadId}] {ObjectExtensions.CurrentMethodName()} Type: {entity.GetType().Name} Validation errors:");

                foreach (var result in validationResults)
                {
                    Log.Warn($"[ThreadID: {Thread.CurrentThread.ManagedThreadId}] {ObjectExtensions.CurrentMethodName()}  -Property(s): {result.MemberNames.ToList().ToDelimitedString()} Validation Error Message: {result.ErrorMessage}");
                }
            }

            return(validationResults);
        }
 private void DeleteDatabaseObjects()
 {
     using (var entityManager = new CountriesEntityManager()) {
         try {
             var sqlCommand = new SqlCommand($"DELETE [Countries].[dbo].[CallingCode] WHERE ([CallingCodeNumber] = {callingCodeNumberCreate});");
             entityManager.ExecuteScalarSql(sqlCommand);
             sqlCommand = new SqlCommand($"DELETE [Countries].[dbo].[CallingCode] WHERE ([CallingCodeNumber] = {callingCodeNumberUpdate});");
             entityManager.ExecuteScalarSql(sqlCommand);
             sqlCommand = new SqlCommand($"DELETE [Countries].[dbo].[CallingCode] WHERE ([CallingCodeNumber] = {callingCodeNumberUpdate2});");
             entityManager.ExecuteScalarSql(sqlCommand);
             sqlCommand = new SqlCommand($"DELETE [Countries].[dbo].[CallingCode] WHERE ([CallingCodeNumber] = {callingCodeNumberDelete});");
             entityManager.ExecuteScalarSql(sqlCommand);
         }
         catch (Exception e) {
             Log.Error($"[ThreadID: {Thread.CurrentThread.ManagedThreadId}] {ObjectExtensions.CurrentMethodName()} {e.VerboseExceptionString()}");
         }
     }
 }
Beispiel #5
0
        private void DeleteDatabaseObjects()
        {
            using (var entityManager = new CountriesEntityManager()) {
                try {
                    var sqlCommand           = new SqlCommand($"SELECT [Id] FROM [Countries].[dbo].[Currency] WHERE ([Code] = '{currencyCodeCreate}');");
                    int currencyCodeCreateId = Convert.ToInt32(entityManager.ExecuteScalarSql(sqlCommand));
                    if (currencyCodeCreateId > 0)
                    {
                        sqlCommand = new SqlCommand($"DELETE [Countries].[dbo].[CountryCurrency] WHERE ([CurrencyId] = '{currencyCodeCreateId}');");
                        entityManager.ExecuteScalarSql(sqlCommand);
                    }
                    sqlCommand = new SqlCommand($"DELETE [Countries].[dbo].[Currency] WHERE ([Code] = '{currencyCodeCreate}');");
                    entityManager.ExecuteScalarSql(sqlCommand);

                    sqlCommand = new SqlCommand($"SELECT [Id] FROM [Countries].[dbo].[Currency] WHERE ([Code] = '{currencyCodeUpdate}');");
                    int currencyCodeUpdateId = Convert.ToInt32(entityManager.ExecuteScalarSql(sqlCommand));
                    if (currencyCodeUpdateId > 0)
                    {
                        sqlCommand = new SqlCommand($"DELETE [Countries].[dbo].[CountryCurrency] WHERE ([CurrencyId] = '{currencyCodeUpdateId}');");
                        entityManager.ExecuteScalarSql(sqlCommand);
                    }
                    sqlCommand = new SqlCommand($"DELETE [Countries].[dbo].[Currency] WHERE ([Code] = '{currencyCodeUpdate}');");
                    entityManager.ExecuteScalarSql(sqlCommand);

                    sqlCommand = new SqlCommand($"SELECT [Id] FROM [Countries].[dbo].[Currency] WHERE ([Code] = '{currencyCodeDelete}');");
                    int currencyCodeDeleteId = Convert.ToInt32(entityManager.ExecuteScalarSql(sqlCommand));
                    if (currencyCodeDeleteId > 0)
                    {
                        sqlCommand = new SqlCommand($"DELETE [Countries].[dbo].[CountryCurrency] WHERE ([CurrencyId] = '{currencyCodeDeleteId}');");
                        entityManager.ExecuteScalarSql(sqlCommand);
                    }
                    sqlCommand = new SqlCommand($"DELETE [Countries].[dbo].[Currency] WHERE ([Code] = '{currencyCodeDelete}');");
                    entityManager.ExecuteScalarSql(sqlCommand);
                }
                catch (Exception e) {
                    Log.Error($"[ThreadID: {Thread.CurrentThread.ManagedThreadId}] {ObjectExtensions.CurrentMethodName()} {e.VerboseExceptionString()}");
                }
            }
        }
Beispiel #6
0
        private static void GetEventsFromXml(string file)
        {
            Console.WriteLine($"{DateTime.Now.YMDHMSFriendly()} - {ObjectExtensions.CurrentMethodName()} File: {file ?? "NULL"}");

            if (string.IsNullOrWhiteSpace(file))
            {
                throw new ArgumentNullException("file");
            }
            if (!File.Exists(file))
            {
                throw new FileNotFoundException($"File not found: {file}");
            }

            try {
                var rootElement   = XElement.Parse(File.ReadAllText(file));
                var eventElements = rootElement
                                    .Elements()
                                    .Where(x => x.Name.LocalName == "Event")
                                    .ToList();

                Console.WriteLine($"{DateTime.Now.YMDHMSFriendly()} - {ObjectExtensions.CurrentMethodName()} File: {file} Xml Events: {eventElements.Count}");

                foreach (var eventElement in eventElements)
                {
                    var eventRecordXml = eventElement.ToString(SaveOptions.DisableFormatting);
                    GetEventFromXml(eventRecordXml);
                }
            }
            catch (Exception e) {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"Error parsing events from file: {file}");
                Console.WriteLine(e.VerboseExceptionString());
                Console.ResetColor();
                throw;
            }
        }
Beispiel #7
0
        private static void GetEvents(
            string filePath,
            string computerFqdn,
            string logName,
            IReadOnlyList <int> eventIds,
            IReadOnlyList <int> eventIdToSuppress,
            DateTime startDate,
            DateTime endDate)
        {
            Console.WriteLine("{0} - {1} FilePath: {2} Computer: {3} LogName: {4} EventIds: {5} EventIdsToSuppress: {6} Start Date: {7} End Date: {8}",
                              DateTime.Now.YMDHMSFriendly(), ObjectExtensions.CurrentMethodName(),
                              !string.IsNullOrWhiteSpace(filePath) ? filePath : "N/A",
                              !string.IsNullOrWhiteSpace(computerFqdn) ? computerFqdn : "N/A",
                              !string.IsNullOrWhiteSpace(logName) ? logName : "N/A",
                              (eventIds.Count > 0) ? eventIds.ToDelimitedString() : "<All Events>",
                              (eventIdToSuppress.Count > 0) ? eventIdToSuppress.ToDelimitedString() : "<No Events>",
                              startDate.YMDFriendly(), endDate.YMDFriendly());

            long eventsProcessed  = 0;
            var  stopwatch        = Stopwatch.StartNew();
            var  eventReadTimeout = TimeSpan.FromMinutes(5);

            if (!string.IsNullOrWhiteSpace(filePath))
            {
                if (!filePath.EndsWith(".EVTX", StringComparison.OrdinalIgnoreCase))
                {
                    Console.WriteLine($"{DateTime.Now.YMDHMSFriendly()} - {ObjectExtensions.CurrentMethodName()} File: {filePath} if not .XML, file must be an .EVTX file type. Exiting.");
                    return;
                }
            }

            try {
                #region Construct event query filter
                var path = !string.IsNullOrWhiteSpace(logName)
                    ? logName
                    : filePath;

                var eventIdsFilter = new StringBuilder();
                if (eventIds.Count > 0)
                {
                    eventIdsFilter.Append("(");
                    for (int index = 0; index < eventIds.Count; index++)
                    {
                        eventIdsFilter.Append($"EventID={eventIds[index]}");
                        if (index < eventIds.Count - 1)
                        {
                            eventIdsFilter.Append(" or ");
                        }
                    }
                    eventIdsFilter.Append(") and ");
                }

                var query = $"*[System[{eventIdsFilter.ToString()}TimeCreated[@SystemTime&gt;='{startDate.YMDFriendly()}T00:00:00.000Z' and @SystemTime&lt;'{endDate.Add(TimeSpan.FromDays(1)).YMDFriendly()}T00:00:00.000Z']]]";

                var suppressionQuery = string.Empty;
                if (eventIdToSuppress.Count > 0)
                {
                    var eventIdsToSuppressFilter = new StringBuilder();
                    eventIdsToSuppressFilter.Append("(");
                    for (int index = 0; index < eventIdToSuppress.Count; index++)
                    {
                        eventIdsToSuppressFilter.Append($"EventID={eventIdToSuppress[index]}");
                        if (index < eventIdToSuppress.Count - 1)
                        {
                            eventIdsToSuppressFilter.Append(" or ");
                        }
                    }
                    eventIdsToSuppressFilter.Append(")");

                    suppressionQuery = $"<Suppress Path=\"{path}\">*[System[{eventIdsToSuppressFilter.ToString()}]]</Suppress>";
                }

                var fullQuery = new StringBuilder();

                fullQuery.Append("<QueryList>");
                fullQuery.Append($"<Query Id=\"0\" Path=\"{path}\">");
                fullQuery.Append($"<Select Path=\"{path}\">{query}</Select>");
                if (!string.IsNullOrWhiteSpace(suppressionQuery))
                {
                    fullQuery.Append(suppressionQuery);
                }
                fullQuery.Append("</Query>");
                fullQuery.Append("</QueryList>");

                Console.WriteLine($"{DateTime.Now.YMDHMSFriendly()} - {ObjectExtensions.CurrentMethodName()} Event query: {fullQuery}");
                #endregion

                EventLogQuery   eventLogQuery = null;
                EventLogSession session       = null;

                try {
                    if (!string.IsNullOrWhiteSpace(computerFqdn))
                    {
                        eventLogQuery         = new EventLogQuery(logName, PathType.LogName, fullQuery.ToString());
                        session               = new EventLogSession(computerFqdn);
                        eventLogQuery.Session = session;
                    }
                    else
                    {
                        eventLogQuery = new EventLogQuery(filePath, PathType.FilePath, fullQuery.ToString());
                    }

                    eventLogQuery.ReverseDirection = EventLogQueryReverseDirection;

                    using (var eventLogReader = new EventLogReader(eventLogQuery)) {
                        EventRecord eventRecord    = null;
                        string      eventRecordXml = string.Empty;

                        do
                        {
                            eventRecord    = null;
                            eventRecordXml = string.Empty;

                            #region Read event from event log
                            try {
                                eventRecord = eventLogReader.ReadEvent(eventReadTimeout);
                                if (eventRecord == null)
                                {
                                    break;
                                }
                                eventsProcessed++;
                                eventRecordXml = eventRecord.ToXml();
                                if (string.IsNullOrWhiteSpace(eventRecordXml))
                                {
                                    continue;
                                }
                                if (GetEventFromXml(eventRecordXml) != 0)
                                {
                                    continue;
                                }
                            }
                            catch (XmlException e) {
                                Console.ForegroundColor = ConsoleColor.Red;
                                Console.WriteLine($"Error parsing event record Xml:{eventRecordXml ?? "NULL"}");
                                Console.WriteLine(e.VerboseExceptionString());
                                Console.ResetColor();
                                continue;
                            }
                            catch (EventLogException e) {
                                if (Regex.IsMatch(e.Message, "The array bounds are invalid", RegexOptions.IgnoreCase))
                                {
                                    continue;
                                }
                                if (Regex.IsMatch(e.Message, "The data area passed to a system call is too small", RegexOptions.IgnoreCase))
                                {
                                    continue;
                                }
                                throw;
                            }
                            #endregion

                            #region Log statistics
                            if (eventsProcessed % 5000 == 0)
                            {
                                Console.WriteLine($"{DateTime.Now.YMDHMSFriendly()} Events processed: {eventsProcessed}");
                            }
                            #endregion
                        } while (eventRecord != null);
                    }
                }
                finally {
                    if (session != null)
                    {
                        try {
                            session.Dispose();
                        }
                        catch { }
                    }
                }
            }
            catch (Exception e) {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("Error parsing events");
                Console.WriteLine(e.VerboseExceptionString());
                Console.ResetColor();
                throw;
            }
        }
Beispiel #8
0
        /// <summary>
        /// Primary entry for application execution.
        /// </summary>
        /// <param name="filePath">The event log evtx file to parse.</param>
        /// <param name="computerFqdn">The computer to process event logs (if not processing a file).</param>
        /// <param name="eventLogName">The event log name (Required if computerFqdn is specified). Example: Security</param>
        /// <param name="eventIds">Optional.  List of event Ids for the query filter.</param>
        /// <param name="startDate">The start date for the query filter.</param>
        /// <param name="endDate">The end date for the query filter.</param>
        /// <param name="format">The report format (CSV or XML).</param>
        public static void DoWork(
            string filePath,
            string computerFqdn,
            string eventLogName,
            IReadOnlyList <int> eventIds,
            IReadOnlyList <int> eventIdsToSuppress,
            DateTime startDate,
            DateTime endDate,
            ReportFormat reportFormat = ReportFormat.CSV)
        {
            #region Validation
            if (string.IsNullOrWhiteSpace(filePath) && string.IsNullOrWhiteSpace(computerFqdn))
            {
                throw new ArgumentException("Must specify either file or computerFqdn.");
            }
            if (!string.IsNullOrWhiteSpace(filePath) && !string.IsNullOrWhiteSpace(computerFqdn))
            {
                throw new ArgumentException("Must specify only file or computerFqdn.");
            }
            if (!string.IsNullOrWhiteSpace(computerFqdn) && string.IsNullOrWhiteSpace(eventLogName))
            {
                throw new ArgumentException("Must specify eventLogName with computerFqdn.");
            }
            if (eventIds == null)
            {
                throw new ArgumentNullException(nameof(eventIds));
            }
            if (startDate > endDate)
            {
                throw new ArgumentOutOfRangeException($"{nameof(startDate)} must be less than {nameof(endDate)}.");
            }

            if (!Enum.IsDefined(typeof(ReportFormat), reportFormat))
            {
                throw new ArgumentOutOfRangeException($"Invalid report format: {reportFormat}");
            }
            else
            {
                ReportFormat = reportFormat;
            }
            #endregion

            Console.WriteLine($"{DateTime.Now.YMDHMSFriendly()} - {ObjectExtensions.CurrentMethodName()}");

            var stopwatch = Stopwatch.StartNew();

            try {
                Initialize();
                if (!string.IsNullOrWhiteSpace(filePath) && filePath.EndsWith(".XML", StringComparison.OrdinalIgnoreCase))
                {
                    GetEventsFromXml(filePath);
                }
                else
                {
                    GetEvents(filePath, computerFqdn, eventLogName, eventIds, eventIdsToSuppress, startDate, endDate);
                }
                CreateReport(filePath);
            }
            catch (Exception e) {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("Error parsing events");
                Console.WriteLine(e.VerboseExceptionString());
                Console.ResetColor();
            }
            finally {
                Console.WriteLine($"{DateTime.Now.YMDHMSFriendly()} - {ObjectExtensions.CurrentMethodName()} Finished.  Time required: {stopwatch.Elapsed} Memory used: {Process.GetCurrentProcess().PeakWorkingSet64.ToString("N0")} Processor time: {Process.GetCurrentProcess().TotalProcessorTime}");

                // 755,000 events and XML report statistics:
                // Time required: 00:14:05.4639642 Memory used: 6,572,908,544 Processor time: 00:06:15.7812500

                // 755,000 events and CSV report statistics:
                // Time required: 00:07:15.7816481 Memory used: 6,426,861,568 Processor time: 00:05:21.7187500
            }
        }
Beispiel #9
0
        private static void CreateXMLReport(string reportFileBasePath)
        {
            Console.WriteLine($"{DateTime.Now.YMDHMSFriendly()} - {ObjectExtensions.CurrentMethodName()}");

            #region Validation
            if (string.IsNullOrWhiteSpace(reportFileBasePath))
            {
                throw new ArgumentNullException("reportFileBasePath");
            }
            #endregion

            foreach (var kvpEventData in EventData)
            {
                Console.WriteLine($" - EventId: {kvpEventData.Key} Events: {kvpEventData.Value.Count}");

                var rootElement = new XElement($"ArrayOfEventId{kvpEventData.Key.Replace("/", "-").Replace("+", "-")}");

                var firstEvent = DateTime.MaxValue;
                var lastEvent  = DateTime.MinValue;

                foreach (var eventBase in kvpEventData.Value)
                {
                    if (eventBase.DateTimeUTC < firstEvent)
                    {
                        firstEvent = eventBase.DateTimeUTC;
                    }
                    if (eventBase.DateTimeUTC > lastEvent)
                    {
                        lastEvent = eventBase.DateTimeUTC;
                    }

                    #region Element values for all events/lines
                    var eventChildElement = new XElement("Event");
                    eventChildElement.Add(new XElement("EventId", eventBase.EventId));
                    eventChildElement.Add(new XElement("EventRecordId", eventBase.EventRecordId));
                    eventChildElement.Add(new XElement("EventSourceMachine", eventBase.EventSourceMachine ?? "NULL"));
                    eventChildElement.Add(new XElement("DateTimeUTC", eventBase.DateTimeUTC.YMDHMSFriendly()));
                    eventChildElement.Add(new XElement("Channel", eventBase.Channel));
                    eventChildElement.Add(new XElement("Level", eventBase.Level));
                    #endregion

                    #region Get element values unique for the event
                    foreach (var elementName in EventColumns[eventBase.EventKey].Keys)
                    {
                        var elementValue = "N/A";

                        if ((eventBase.EventDataNameElements != null) && (eventBase.EventDataNameElements.Count > 0))
                        {
                            var columnElement = eventBase.EventDataNameElements
                                                .Where(x => x.Attributes()
                                                       .Any(y =>
                                                            (y != null) && (y.Name != null) && !string.IsNullOrWhiteSpace(y.Name.LocalName) &&
                                                            string.Equals(y.Name.LocalName, "Name", StringComparison.OrdinalIgnoreCase) &&
                                                            !string.IsNullOrWhiteSpace(y.Value) &&
                                                            string.Equals(y.Value, elementName, StringComparison.OrdinalIgnoreCase)))
                                                .FirstOrDefault();

                            if ((columnElement != null) && (columnElement.Value != null))
                            {
                                elementValue = columnElement.Value.Trim();
                                if (string.IsNullOrWhiteSpace(elementValue))
                                {
                                    elementValue = "N/A";
                                }
                            }
                        }

                        eventChildElement.Add(new XElement(elementName, elementValue));
                    }
                    #endregion

                    rootElement.Add(eventChildElement);
                } // foreach (var eventBase in kvpEventData.Value) {

                #region Create report for the specific event id
                var eventDescription = string.Empty;
                if (EventDescriptions.ContainsKey(kvpEventData.Key))
                {
                    eventDescription = $"-{EventDescriptions[kvpEventData.Key].Description}";
                }

                var reportFileName = $"Event-{kvpEventData.Key.Replace("/","-")}{eventDescription}-{firstEvent.YMDHMFriendly().Replace(":", "-").Replace(" ", "-")}-{lastEvent.YMDHMFriendly().Replace(":", "-").Replace(" ", "-")}.xml";
                var reportFilePath = Path.Combine(reportFileBasePath, reportFileName);

                var xDocument = new XDocument(new XDeclaration("1.0", "UTF-8", string.Empty), rootElement);

                try {
                    Console.WriteLine($"{DateTime.Now.YMDHMSFriendly()} - {ObjectExtensions.CurrentMethodName()} Writing: {rootElement.Elements().Count()} events to file: {reportFilePath}");
                    xDocument.Save(reportFilePath);
                }
                catch (Exception e) {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine("Error parsing events");
                    Console.WriteLine(e.VerboseExceptionString());
                    Console.ResetColor();
                }
                #endregion
            } // foreach (var kvpEventData in EventData) {
        }
Beispiel #10
0
        private static void CreateCSVReport(string reportFileBasePath)
        {
            Console.WriteLine($"{DateTime.Now.YMDHMSFriendly()} - {ObjectExtensions.CurrentMethodName()}");

            #region Validation
            if (string.IsNullOrWhiteSpace(reportFileBasePath))
            {
                throw new ArgumentNullException("reportFileBasePath");
            }
            #endregion

            foreach (var kvpEventData in EventData)
            {
                Console.WriteLine($" - EventId: {kvpEventData.Key} Events: {kvpEventData.Value.Count}");

                var lines      = new List <string>();
                var headerLine = new StringBuilder();

                #region Column headers for all events
                headerLine.Append("EventId,");
                headerLine.Append("EventRecordId,");
                headerLine.Append("EventSourceMachine,");
                headerLine.Append("DateTimeUTC,");
                headerLine.Append("Channel,");
                headerLine.Append("Level,");
                #endregion

                foreach (var kvpColumnName in EventColumns[kvpEventData.Key])
                {
                    headerLine.Append($"{kvpColumnName.Key},");
                }

                lines.Add(headerLine.ToString());

                var firstEvent = DateTime.MaxValue;
                var lastEvent  = DateTime.MinValue;

                foreach (var eventBase in kvpEventData.Value)
                {
                    if (eventBase.DateTimeUTC < firstEvent)
                    {
                        firstEvent = eventBase.DateTimeUTC;
                    }
                    if (eventBase.DateTimeUTC > lastEvent)
                    {
                        lastEvent = eventBase.DateTimeUTC;
                    }

                    var line = new StringBuilder();
                    #region Column values for all events/lines
                    line.Append($"{eventBase.EventId},");
                    line.Append($"{eventBase.EventRecordId},");
                    line.Append($"{eventBase.EventSourceMachine ?? string.Empty},");
                    line.Append($"{eventBase.DateTimeUTC.YMDHMSFFFFFFFFriendly()},");
                    line.Append($"{eventBase.Channel},");
                    line.Append($"{eventBase.Level},");
                    #endregion

                    #region Get column values for the line
                    foreach (var columnName in EventColumns[eventBase.EventKey].Keys)
                    {
                        var columnValue = "N/A,";
                        if ((eventBase.EventDataNameElements != null) && (eventBase.EventDataNameElements.Count > 0))
                        {
                            var columnElement = eventBase.EventDataNameElements
                                                .Where(x => x.Attributes()
                                                       .Any(y =>
                                                            (y != null) && (y.Name != null) && !string.IsNullOrWhiteSpace(y.Name.LocalName) &&
                                                            string.Equals(y.Name.LocalName, "Name", StringComparison.OrdinalIgnoreCase) &&
                                                            !string.IsNullOrWhiteSpace(y.Value) &&
                                                            string.Equals(y.Value, columnName, StringComparison.OrdinalIgnoreCase)))
                                                .FirstOrDefault();

                            if ((columnElement != null) && (columnElement.Value != null))
                            {
                                columnValue = columnElement.Value.Trim();
                            }
                        }

                        line.Append($"\"{columnValue}\",");
                    }

                    #endregion

                    lines.Add(line.ToString());
                } // foreach (var eventBase in kvpEventData.Value) {

                #region Create report for the specific event id
                var eventDescription = string.Empty;
                if (EventDescriptions.ContainsKey(kvpEventData.Key))
                {
                    eventDescription = $"-{EventDescriptions[kvpEventData.Key].Description}";
                }

                var reportFileName = $"EventId-{kvpEventData.Key.Replace("/","-")}{eventDescription}-{firstEvent.YMDHMFriendly().Replace(":", "-").Replace(" ", "-")}-{lastEvent.YMDHMFriendly().Replace(":", "-").Replace(" ", "-")}.csv";
                var reportFilePath = Path.Combine(reportFileBasePath, reportFileName);

                try {
                    Console.WriteLine($"{DateTime.Now.YMDHMSFriendly()} - {ObjectExtensions.CurrentMethodName()} Writing: {lines.Count} lines to file: {reportFilePath}");
                    File.WriteAllLines(reportFilePath, lines, Encoding.UTF8);
                }
                catch (Exception e) {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine("Error parsing events");
                    Console.WriteLine(e.VerboseExceptionString());
                    Console.ResetColor();
                }
                #endregion
            } // foreach (var kvpEventData in EventData) {
        }