Share via


Example code for Event Listening

This topic lists the entire code for this example.

Code

Imports System
Imports System.Diagnostics.Eventing.Reader
Imports System.Collections
Imports System.Data.SqlClient
Imports System.Data
Imports System.Xml
Imports System.Xml.XPath
Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary

Namespace EventLogSample

    ''' <summary>
    ''' An example that illustrates writing the EventLog to a SQL table or an XML file.
    ''' </summary>
    Public Class EventListenerProgram

        ''' <summary>
        ''' Event data set used to store incoming events.
        ''' </summary>
        Dim eventDs As New DataSet()

        ''' <summary>
        ''' The in-memory mapping between the column name and Xml Xpath leading to value.
        ''' </summary>
        Dim nameToXPathMapping As New Hashtable()

        Dim bookMarkToStartFrom As EventBookmark = Nothing
        Dim fsBookmark As FileStream

        Dim sqlConn As SqlConnection

        ''' <summary>
        ''' Count of how many events have been saved
        ''' </summary>
        Dim savedEventCount As Integer = 0

        ''' <summary>
        ''' Loads the Name vs XPath from the settings into memory for easy retrieval.
        ''' Name is used as key.
        ''' Xpath is used as Value.
        ''' </summary>
        ''' <param name="SqlTableDefinition">The string Name=XPath mapping to EventXml (Example: ProviderName=Event/System/Provider/@Name,...).</param>
        ''' <param name="names">The Hashtable to load the mapping to.</param>
        Private Sub LoadNameToXPathHash(ByVal SqlTableDefinition As String, ByRef names As Hashtable)

            Dim sqlColumns() As String = SqlTableDefinition.Split(",")
            names.Clear()

            For Each sqlColumnExpr As String In sqlColumns

                Dim Pivot As Integer = sqlColumnExpr.IndexOf("=") + 1
                Dim Len As Integer = sqlColumnExpr.Length

                Dim Name As String = sqlColumnExpr.Substring(0, Pivot - 1).Trim()
                Dim Value As String = sqlColumnExpr.Substring(Pivot, Len - Pivot).Trim()

                Try
                    names.Add(Name, Value)

                Catch dupName As DuplicateNameException

                    Console.WriteLine( _
                    "ERROR! Attempted to use ColumnName {0} twice," & _
                    " which is not allowed. Edit the Configurations' SqlTableDefinition setting " & _
                    "to remove duplicate names.", Name)
                    Throw dupName
                End Try
            Next
        End Sub
        ''' <summary>
        ''' Attempts to resume the previous run, through a bookmark, or starts fresh.
        ''' </summary>
        Sub EventListenerProgram()

            If Not Settings.Default.XmlMode Then
                sqlConn = New SqlConnection(Settings.Default.SqlConnectionString)
                sqlConn.Open()
            End If
            Try
                If System.IO.File.Exists("bookmark.stream") Then

                    fsBookmark = New FileStream("bookmark.stream", FileMode.Open)
                    Dim formatter As New BinaryFormatter()
                    bookMarkToStartFrom = formatter.Deserialize(fsBookmark)

                Else
                    fsBookmark = New FileStream("bookmark.stream", FileMode.Create)
                End If

            Catch e As Exception

                Console.WriteLine( _
                "Could not load or create bookmark on disk directory: " & _
                "{0} error: {1}.", System.IO.Directory.GetCurrentDirectory(), e.Message)
                bookMarkToStartFrom = Nothing
            End Try
        End Sub

        ''' <summary>
        ''' Close the bookmark.
        '' </summary>
        Protected Overrides Sub Finalize()

            fsBookmark.Close()
        End Sub
        ''' <summary>
        ''' Creates and executes T-SQL statement to create a schema based on the Name=Xpath mapping from the config file.
        ''' </summary>
        ''' <param name="sqlConn">The active Sql connection to use.</param>
        ''' <param name="SqlTableDefinition"></param>
        Private Sub CreateSqlSchemaDefinition()

            Dim sqlComnd As SqlCommand = sqlConn.CreateCommand()

            sqlComnd.CommandText = String.Format("USE [{0}]", Settings.Default.DatabaseName) & Environment.NewLine & _
                                   "IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Event]') AND type in (N'U'))" & _
                                   Environment.NewLine & "DROP TABLE [dbo].[Event]" & Environment.NewLine & _
                                   "CREATE TABLE [dbo].[Event](" & Environment.NewLine

            For Each de As DictionaryEntry In nameToXPathMapping
                sqlComnd.CommandText += String.Format( _
                "[{0}] [nvarchar](max) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,", de.Key) & Environment.NewLine
            Next

            sqlComnd.CommandText += "[EventXml] [xml] NULL" & Environment.NewLine
            sqlComnd.CommandText += ") ON [PRIMARY]"

            sqlComnd.ExecuteNonQuery()

            ClearBookmark()
        End Sub

        ''' <summary>
        ''' Runs a comparison between DB schema and the name-To-Xpath mapping.
        ''' </summary>
        ''' <param name="eventDs">The filled in DataSet, from DB.</param>
        ''' <returns>true if DB schema does not match, else false.</returns>
        Private Function DoesDbSchemaNeedRecreating(ByVal eventDs As DataSet) As Boolean

            Dim DbSchemaColumnCount As Integer = eventDs.Tables("Event").Columns.Count

            ' Db Schema always has one additional column for the complete Event Xml
            If Not nameToXPathMapping.Count = DbSchemaColumnCount - 1 Then
                Return True
            End If

            For Each dc As DataColumn In eventDs.Tables("Event").Columns

                If 0 <> (String.Compare(dc.ColumnName, "EventXml", True) AndAlso _
                    Not nameToXPathMapping.ContainsKey(dc.ColumnName)) Then
                    Return True
                End If
            Next

            Return False
        End Function

        ''' <summary>
        ''' Runs a query to check if a database exists on a SQL server.
        ''' </summary>
        ''' <param name="sqlConn">An active sql connection.</param>
        ''' <param name="TableName">The table name to check on.</param>
        ''' <returns>true if table exists, false if it doesn't exist, exception on error.</returns>
        Private Function DoesDbTableExist(ByVal TableName As String) As Boolean

            Dim eventDA As New SqlDataAdapter( _
            String.Format("SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[{0}]')", _
            TableName), sqlConn)
            Dim mySqlCommandBuilder As New SqlCommandBuilder(eventDA)

            If Not eventDA.SelectCommand.ExecuteScalar() Is Nothing Then
                Return True
            End If

            Return False
        End Function

        ''' <summary>
        ''' Initializes program state. Specifically, connects to SQL to fill and/or re/create in 
        ''' the schema for the DataSet. When in "XML mode" simply creates the schema in the dataset.
        ''' </summary>
        ''' <returns>true upon success else false and errorcode is set.</returns>
        Private Function LoadSchemaAndEvents() As Boolean

            eventDs.DataSetName = "Events"

            If Not Settings.Default.XmlMode Then

                Dim eventDA As New SqlDataAdapter("select * from Event", sqlConn)
                Dim mySqlCommandBuilder As New SqlCommandBuilder(eventDA)
                Dim DbEventTableCreated As Boolean = DoesDbTableExist("Event")

                If DbEventTableCreated Then

                    eventDA.MissingSchemaAction = System.Data.MissingSchemaAction.AddWithKey
                    eventDA.FillSchema(eventDs, System.Data.SchemaType.Source, "Event")

                    'check to see if schema in DB is in sync with schema in the config file (SqlTableDefinition)
                    If DoesDbSchemaNeedRecreating(eventDs) Then

                        Dim selection As ConsoleKey = ConsoleKey.N

                        ' Schema is out of sync so we need to delete the table and re-create it in the Db
                        ' Ask user if it is O.K. to delete the old schema
                        Console.WriteLine("Schema in configuration is out of sync with the SQL database, " & _
                        "the Event table needs to be recreated.")
                        Console.WriteLine("WARNING: All existing rows/data in database {0} " & _
                        "from the Event Table will be deleted. Proceed? " & _
                        "(At the console press y/n)", Settings.Default.DatabaseName)

                        Do
                            selection = Console.ReadKey().Key

                            If selection = ConsoleKey.Y Then

                                CreateSqlSchemaDefinition()

                            ElseIf selection = ConsoleKey.N Then

                                Console.WriteLine("Schema not in sync, cannot continue.")
                                Environment.ExitCode = 87
                                Return False

                            Else

                                Console.WriteLine("")
                                Console.WriteLine("Invalid input, expected (y)es or (n)o, try again...")
                            End If

                        Loop While Not selection = ConsoleKey.Y
                    End If

                Else

                    ' DB schema doesn't exist, create new one
                    CreateSqlSchemaDefinition()
                End If

                ' Load schema into the DataSet
                eventDA.FillSchema(eventDs, System.Data.SchemaType.Source, "Event")

            Else

                ' In XML mode load schema into DataSet, XML will be saved straight out of eventDs dataSet
                Dim dt As DataTable = eventDs.Tables.Add("Event")

                For Each de As DictionaryEntry In nameToXPathMapping

                    dt.Columns.Add(de.Key, GetType(String))
                Next

                dt.Columns.Add("EventXml")
            End If

            Return True
        End Function


        ''' <summary>
        ''' Helps resolve XPath to an actual value in an event.
        ''' </summary>
        ''' <param name="xPathToRun">The xpath to use to get the value.</param>
        ''' <param name="arrivedEventXml">The Xml of the event to get the value from.</param>
        ''' <returns>the value if it is found, else null.</returns>
        Private Shared Function RunXPathOnDocument(ByVal xPathToRun As String, ByVal arrivedEventXml As String) As Object

            Dim xmlDoc As New XmlDocument()

            ' remove 'default' xmlns as it only complicates things here.
            arrivedEventXml = arrivedEventXml.Replace( _
            "xmlns='https://schemas.microsoft.com/win/2004/08/events/event'", "")

            xmlDoc.LoadXml("<?xml version=""1.0"" encoding=""utf-16"" standalone=""yes""?>" & Environment.NewLine & _
                arrivedEventXml)

            Dim selectedNode As XmlNode = xmlDoc.SelectSingleNode(xPathToRun)

            If Not selectedNode Is Nothing Then

                Dim Value As String = selectedNode.Value  ' if it's an attribute

                If selectedNode.ChildNodes.Count = 1 AndAlso _
                     selectedNode.FirstChild.NodeType = XmlNodeType.Text Then  ' it's an element text-value

                    Value = selectedNode.FirstChild.Value
                End If

                Return Value
            End If

            Return Nothing
        End Function

        ''' <summary>
        ''' Given the XML for an event, loads the event into a row-format and adds it to the eventDataSet.
        ''' </summary>
        ''' <param name="EventXml">Event XML to load into DataSet.</param>
        ''' <param name="eventDataSet">The dataset to load event into.</param>
        Private Sub AddEventIntoDataSet(ByVal EventXml As String, ByRef eventDataSet As DataSet)

            Dim newRow As DataRow = eventDataSet.Tables("Event").NewRow()

            For Each de As DictionaryEntry In nameToXPathMapping

                newRow(de.Key) = RunXPathOnDocument(de.Value, EventXml)
            Next

            newRow("EventXml") = EventXml
            eventDataSet.Tables("Event").Rows.Add(newRow)
        End Sub

        ''' <summary>
        ''' Given a new event saves it to XML or DB, based on the current mode.
        ''' </summary>
        ''' <param name="evtRec">The new event to save.</param>
        Private Sub SaveEvent(ByVal evtRec As EventRecord)

            Dim eventXml As String = evtRec.ToXml()

            If Not Settings.Default.XmlMode Then

                Console.WriteLine("New event, log: {0} event#: {1} to Sql Db: {2} ...", _
                    evtRec.LogName, savedEventCount, Settings.Default.DatabaseName)

                ' if this function throws the exception will appear on the main thread, 
                ' however because it's a callstack the source call-stack will appear to be some unknown address
                Dim theSqlDataAdapter As New SqlDataAdapter("select * from Event", sqlConn)
                Dim theSqlCommandBuilder As New SqlCommandBuilder(theSqlDataAdapter)
                theSqlDataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
                theSqlDataAdapter.InsertCommand = theSqlCommandBuilder.GetInsertCommand()

                AddEventIntoDataSet(eventXml, eventDs)

                ' store only relevant changes in memory...
                eventDs = eventDs.GetChanges()

                If 0 = savedEventCount Mod Settings.Default.BatchSize Then  ' save in batches

                    theSqlDataAdapter.Update(eventDs, "Event")
                    SaveBookmark(evtRec.Bookmark)
                End If

            Else

                If (String.IsNullOrEmpty(Settings.Default.DatabaseName)) Then
                    Throw New ArgumentException( _
                        String.Format("Setting SqlDatabaseName in App.config is null or empty, " & _
                        "it is used as the name for the XML file to save events to, please give " & _
                        "it a value."))
                End If

                ' Use the Database name as the name of the XML file to save to, get the filename only to save in the same dir.
                Dim XmlFilename As String = System.IO.Path.GetFileName(Settings.Default.DatabaseName) & ".xml"

                Console.WriteLine("New event, log: {0} event#: {1} to Xml {2}", _
                    evtRec.LogName, savedEventCount, XmlFilename)

                AddEventIntoDataSet(eventXml, eventDs)
                eventDs.WriteXml(XmlFilename)

                SaveBookmark(evtRec.Bookmark)
            End If

            savedEventCount = savedEventCount + 1
        End Sub

        ''' <summary>
        ''' Resets the bookmark.
        ''' </summary>
        Private Sub ClearBookmark()
            bookMarkToStartFrom = Nothing
        End Sub

        ''' <summary>
        ''' Saves/Serializes a bookmark to the target file.
        ''' </summary>
        ''' <param name="BmFileName">The filepath to save to.</param>
        ''' <param name="evtBookmark">The bookmark to save.</param>
        Private Sub SaveBookmark(ByVal evtBookmark As EventBookmark)

            Dim formatter As New BinaryFormatter()
            fsBookmark.Seek(0, SeekOrigin.Begin)
            formatter.Serialize(fsBookmark, evtBookmark)
        End Sub


        ''' <summary>
        ''' Called when a subscription is active and a qualified event has been received.
        ''' </summary>
        ''' <param name="sender"></param>
        ''' <param name="e">Contains information about the event.</param>
        Public Sub reader_EventRecordWritten(ByVal sender As Object, ByVal e As EventRecordWrittenEventArgs)

            Dim evtRec As EventRecord = e.EventRecord

            If Not e.EventException Is Nothing Then
                Environment.ExitCode = -1
                Console.WriteLine("Error! Unexpected failure to retrieve event {0}. This event will not be saved.", _
                    e.EventException.ToString())

            Else
                SaveEvent(evtRec)
            End If

        End Sub


        ''' <summary>
        ''' Loads a App.config and based on it subscribes to event log to write events
        ''' either into XML file or SQL database.
        ''' </summary>
        ''' <param name="args">None, all settings in the App.config</param>
        ''' <returns>0 on success, 87 on invalid input, -1 on system error.</returns>
        Public Shared Function Main(ByVal args() As String) As Integer

            Dim logWatcher As EventLogWatcher = Nothing
            Dim session As New EventLogSession()
            Dim source As New EventLogQuery( _
                Settings.Default.LogPath, PathType.LogName, Settings.Default.EventQuery)
            Dim program As New EventListenerProgram()
            Try

                program.LoadNameToXPathHash(Settings.Default.SqlTableDefinition, _
                    program.nameToXPathMapping)

                If (program.LoadSchemaAndEvents()) Then

                    If Not program.bookMarkToStartFrom Is Nothing Then

                        Console.WriteLine("Found a bookmark file at '{0}\bookmark.stream' will use it for this run.", _
                            System.IO.Directory.GetCurrentDirectory())
                    End If
                    logWatcher = New EventLogWatcher(source, _
                        program.bookMarkToStartFrom, Settings.Default.ReadExistingEvents)
                    AddHandler logWatcher.EventRecordWritten, AddressOf program.reader_EventRecordWritten
                    logWatcher.Enabled = True

                    Console.WriteLine("Started subscription on channel {0}", _
                        Settings.Default.LogPath)

                    If Settings.Default.SubscribeForFutureEvents Then

                        Console.WriteLine("Press any key to stop subscription & exit ...")
                        Console.ReadKey()
                    End If
                End If

            Catch e As InvalidCastException

                Console.WriteLine("Failed to cast from one type to another, this may be caused " & _
                   "by incorrectly typed parameters, or other program failure.")
                Console.WriteLine(e.ToString())
                Environment.ExitCode = 87

            Catch e As SqlException

                Console.WriteLine("Sql exception (below), may be caused because server " & _
                    "could not be contacted.")
                Console.WriteLine(e.ToString())
                Environment.ExitCode = 87

            Finally

                logWatcher.Enabled = False
                program.sqlConn.Close()
                logWatcher.Dispose()
            End Try

            Console.WriteLine("Saved {0} events.", program.savedEventCount)

            Return Environment.ExitCode
        End Function
    End Class
End Namespace
using System;
using System.Diagnostics.Eventing.Reader;
using System.Collections;
using System.Data.SqlClient;
using System.Data;
using System.Xml;
using System.Xml.XPath;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace EventLogSample
{
    /// <summary>
    /// An example that illustrates writing the EventLog to a SQL table or an XML file.
    /// </summary>
    class EventListenerProgram
    {
        /// <summary>
        /// Event data set used to store incoming events.
        /// </summary>
        DataSet eventDs = new DataSet();

        /// <summary>
        /// The in-memory mapping between the column name and Xml Xpath leading to value.
        /// </summary>
        Hashtable nameToXPathMapping = new Hashtable();

       EventBookmark bookMarkToStartFrom = null;
       FileStream fsBookmark;

       SqlConnection sqlConn;

        /// <summary>
        /// Count of how many events have been saved
        /// </summary>
        int savedEventCount = 0;
        #region app_and_db_setup
    /// <summary>
        /// Loads the Name vs XPath from the settings into memory for easy retrieval.
        /// Name is used as the key.
        /// Xpath is used as the value.
        /// </summary>
        /// <param name="SqlTableDefinition">The string Name=XPath mapping to EventXml (Example: ProviderName=Event/System/Provider/@Name,...).</param>
        /// <param name="names">The Hashtable to load the mapping to.</param>
        internal void LoadNameToXPathHash(string SqlTableDefinition, ref Hashtable names)
        {
            string[] sqlColumns = SqlTableDefinition.Split(',');
            names.Clear();

            foreach (string sqlColumnExpr in sqlColumns)
            {
                int Pivot = sqlColumnExpr.IndexOf('=') + 1;
                int Len = sqlColumnExpr.Length;

                string Name = sqlColumnExpr.Substring(0, Pivot - 1).Trim();
                string Value = sqlColumnExpr.Substring(Pivot, Len - Pivot).Trim();

                try
                {
                    names.Add(Name, Value);
                }
                catch (DuplicateNameException dupName)
                {
                    Console.WriteLine("ERROR! Attempted to use ColumnName {0} twice, which is not allowed. Edit the Configurations' SqlTableDefinition setting to remove duplicate names.", Name);
                    throw dupName;
                }
            }
        }
        /// <summary>
        /// Attempts to resume the previous run, through a bookmark, or starts fresh.
        /// </summary>
        EventListenerProgram()
        {
            if (!Settings.Default.XmlMode)
            {
                sqlConn = new SqlConnection(Settings.Default.SqlConnectionString);
                sqlConn.Open();
            }

            try
            {
                if (System.IO.File.Exists("bookmark.stream"))
                {
                    fsBookmark = new FileStream("bookmark.stream", FileMode.Open);
                    BinaryFormatter formatter = new BinaryFormatter();
                    bookMarkToStartFrom = (EventBookmark)formatter.Deserialize(fsBookmark);
                }
                else
                {
                    fsBookmark = new FileStream("bookmark.stream", FileMode.Create);
                }

               
            }
            catch (Exception e)
            {
                Console.WriteLine("Could not load or create bookmark on disk directory: {0} error: {1}.", System.IO.Directory.GetCurrentDirectory(), e.Message);
                bookMarkToStartFrom = null;
            }
        }

        /// <summary>
        /// Close the bookmark.
        /// </summary>
        ~EventListenerProgram()
        {
            fsBookmark.Close();
        }
        /// <summary>
        /// Creates and executes T-SQL statement to create a schema based on the Name=Xpath mapping from the config file.
        /// </summary>
        /// <param name="sqlConn">The active Sql connection to use.</param>
        /// <param name="SqlTableDefinition"></param>
        internal void CreateSqlSchemaDefinition()
        {
            SqlCommand sqlComnd = sqlConn.CreateCommand();

            sqlComnd.CommandText = String.Format("USE [{0}]\n", Settings.Default.DatabaseName) +
                                   "IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Event]') AND type in (N'U'))\n" +
                                   "DROP TABLE [dbo].[Event]\n" +
                                   "CREATE TABLE [dbo].[Event](\n";

            foreach (DictionaryEntry de in nameToXPathMapping)
            {
                sqlComnd.CommandText += String.Format("[{0}] [nvarchar](max) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,\n", de.Key);
            }

            sqlComnd.CommandText += "[EventXml] [xml] NULL\n";
            sqlComnd.CommandText += ") ON [PRIMARY]";

            sqlComnd.ExecuteNonQuery();

            ClearBookmark();
        }
        /// <summary>
        /// Runs a comparison between DB schema and the name-To-Xpath mapping.
        /// </summary>
        /// <param name="eventDs">The filled in DataSet, from DB.</param>
        /// <returns>true if DB schema does not match, else false.</returns>
        internal bool DoesDbSchemaNeedRecreating(DataSet eventDs)
        {
            int DbSchemaColumnCount = eventDs.Tables["Event"].Columns.Count;

            // Db Schema always has one additional column for the complete Event Xml
            if (nameToXPathMapping.Count != DbSchemaColumnCount - 1)
            {
                return true;
            }

            foreach (DataColumn dc in eventDs.Tables["Event"].Columns)
            {
                if (0 != String.Compare(dc.ColumnName, "EventXml", true) &&
                    !nameToXPathMapping.ContainsKey(dc.ColumnName))
                    return true;
            }

            return false;
        }
        /// <summary>
        /// Runs a query to check if a database exists on a SQL server.
        /// </summary>
        /// <param name="sqlConn">An active sql connection.</param>
        /// <param name="TableName">The table name to check on.</param>
        /// <returns>true if table exists, false if it doesn't exist, exception on error.</returns>
        internal bool DoesDbTableExist(string TableName)
        {
            SqlDataAdapter eventDA = new SqlDataAdapter(String.Format("SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[{0}]')", TableName), sqlConn);
            SqlCommandBuilder mySqlCommandBuilder = new SqlCommandBuilder(eventDA);

            if (null != eventDA.SelectCommand.ExecuteScalar())
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Initializes program state. Specifically, connects to SQL to fill and/or re/create in 
        /// the schema for the DataSet. When in "XML mode" simply creates the schema in the dataset.
        /// </summary>
        /// <returns>true upon success else false and errorcode is set.</returns>
        internal bool LoadSchemaAndEvents()
        {
            eventDs.DataSetName = "Events";

            if (!Settings.Default.XmlMode)
            {
                SqlDataAdapter eventDA = new SqlDataAdapter("select * from Event", sqlConn);
                SqlCommandBuilder mySqlCommandBuilder = new SqlCommandBuilder(eventDA);
                bool DbEventTableCreated = DoesDbTableExist("Event");

                if (DbEventTableCreated)
                {
                    eventDA.MissingSchemaAction = System.Data.MissingSchemaAction.AddWithKey;
                    eventDA.FillSchema(eventDs, System.Data.SchemaType.Source, "Event");

                    //check to see if schema in DB is in sync with schema in the config file (SqlTableDefinition)
                    if (DoesDbSchemaNeedRecreating(eventDs))
                    {
                        ConsoleKey selection = ConsoleKey.N;

                        //Schema is out of sync so we need to delete the table and re-create it in the Db
                        //Ask user if it is O.K. to delete the old schema
                        Console.WriteLine("Schema in configuration is out of sync with the SQL database, the Event table needs to be recreated.");
                        Console.WriteLine("WARNING: All existing rows/data in database {0} from the Event Table will be deleted. Proceed? (At the console press y/n)", Settings.Default.DatabaseName);

                        do
                        {
                            selection = Console.ReadKey().Key;

                            if (selection == ConsoleKey.Y)
                            {
                                CreateSqlSchemaDefinition();
                            }
                            else if (selection == ConsoleKey.N)
                            {
                                Console.WriteLine("Schema not in sync, cannot continue.");
                                Environment.ExitCode = 87;
                                return false;
                            }
                            else
                            {
                                Console.WriteLine("");
                                Console.WriteLine("Invalid input, expected (y)es or (n)o, try again...");
                            }
                        }
                        while (selection != ConsoleKey.Y);
                    }
                }
                else
                {
                    //DB schema doesn't exist, create new one
                    CreateSqlSchemaDefinition();
                }

                //Load schema into the DataSet
                eventDA.FillSchema(eventDs, System.Data.SchemaType.Source, "Event");
            }
            else
            {
                //In XML mode load schema into DataSet, XML will be saved straight out of eventDs dataSet
                DataTable dt = eventDs.Tables.Add("Event");

                foreach (DictionaryEntry de in nameToXPathMapping)
                {
                    dt.Columns.Add((string)de.Key, typeof(string));
                }

                dt.Columns.Add("EventXml");
            }

            return true;
        }
        #endregion
        #region incoming_event_handler
        /// <summary>
        /// Helps resolve XPath to an actual value in an event.
        /// </summary>
        /// <param name="xPathToRun">The xpath to use to get the value.</param>
        /// <param name="arrivedEventXml">The Xml of the event to get the value from.</param>
        /// <returns>the value if it is found, else null.</returns>
        internal static object RunXPathOnDocument(string xPathToRun, string arrivedEventXml)
        {
            XmlDocument xmlDoc = new XmlDocument();

            //remove 'default' xmlns as it only complicates things here.
            arrivedEventXml = arrivedEventXml.Replace("xmlns='https://schemas.microsoft.com/win/2004/08/events/event'", "");

            xmlDoc.LoadXml("<?xml version=\"1.0\" encoding=\"utf-16\" standalone=\"yes\"?>\n" + 
                                arrivedEventXml);

            XmlNode selectedNode = xmlDoc.SelectSingleNode(xPathToRun);

            if (selectedNode != null)
            {
                string Value = selectedNode.Value; //if it's an attribute

                if (selectedNode.ChildNodes.Count == 1 &&
                     selectedNode.FirstChild.NodeType == XmlNodeType.Text) //it's an element text-value
                {
                    Value = selectedNode.FirstChild.Value;
                }

                return Value;
            }

            return null;
        }
        /// <summary>
        /// Given the XML for an event, loads the event into a row-format and adds it to the eventDataSet.
        /// </summary>
        /// <param name="EventXml">Event XML to load into DataSet.</param>
        /// <param name="eventDataSet">The dataset to load event into.</param>
        internal void AddEventIntoDataSet(string EventXml, ref DataSet eventDataSet)
        {
            DataRow newRow = eventDataSet.Tables["Event"].NewRow();

            foreach (DictionaryEntry de in nameToXPathMapping)
            {
                newRow[(string)de.Key] = RunXPathOnDocument((string)de.Value, EventXml);
            }

            newRow["EventXml"] = EventXml;
            eventDataSet.Tables["Event"].Rows.Add(newRow);
        }

        /// <summary>
        /// Given a new event saves it to XML or DB, based on the current mode.
        /// </summary>
        /// <param name="evtRec">The new event to save.</param>
        internal void SaveEvent(EventRecord evtRec)
        {
            string eventXml = evtRec.ToXml();

            if (!Settings.Default.XmlMode)
            {
                Console.WriteLine("New event, log: {0} event#: {1} to Sql Db: {2} ...", evtRec.LogName, savedEventCount, Settings.Default.DatabaseName);

                //if this function throws the exception will appear on the main thread, 
                //however because it's a callstack the source call-stack will appear to be some unknown address
                SqlDataAdapter theSqlDataAdapter = new SqlDataAdapter("select * from Event", sqlConn);
                SqlCommandBuilder theSqlCommandBuilder = new SqlCommandBuilder(theSqlDataAdapter);
                theSqlDataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
                theSqlDataAdapter.InsertCommand = theSqlCommandBuilder.GetInsertCommand();

                AddEventIntoDataSet(eventXml, ref eventDs);

                //store only relevant changes in memory...
                eventDs = eventDs.GetChanges();

                if (0 == savedEventCount % Settings.Default.BatchSize) //save in batches
                {
                    theSqlDataAdapter.Update(eventDs, "Event");
                    SaveBookmark(evtRec.Bookmark);
                }
            }
            else
            {
                if (string.IsNullOrEmpty(Settings.Default.DatabaseName))
                {
                    throw new ArgumentException(String.Format("Setting SqlDatabaseName in App.config is null or empty, it is used as the name for the XML file to save events to, please give it a value."));
                }

                //Use the Database name as the name of the XML file to save to, get the filename only to save in the same dir.
                string XmlFilename = System.IO.Path.GetFileName(Settings.Default.DatabaseName) + ".xml";

                Console.WriteLine("New event, log: {0} event#: {1} to Xml {2}", evtRec.LogName, savedEventCount, XmlFilename);

                AddEventIntoDataSet(eventXml, ref eventDs);
                eventDs.WriteXml(XmlFilename);

                SaveBookmark(evtRec.Bookmark);
            }

            savedEventCount++;
        }

        /// <summary>
        /// Resets the bookmark.
        /// </summary>
        internal void ClearBookmark()
        {
            bookMarkToStartFrom = null;
        }

        /// <summary>
        /// Saves/Serializes a bookmark to the target file.
        /// </summary>
        /// <param name="BmFileName">The filepath to save to.</param>
        /// <param name="evtBookmark">The bookmark to save.</param>
        internal void SaveBookmark(EventBookmark evtBookmark)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            fsBookmark.Seek(0, SeekOrigin.Begin);
            formatter.Serialize(fsBookmark, evtBookmark);
        }
        /// <summary>
        /// Called when a subscription is active and a qualified event has been received.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e">Contains information about the event.</param>
        public void reader_EventRecordWritten(object sender, EventRecordWrittenEventArgs e)
        {
            EventRecord evtRec = e.EventRecord;
                if (e.EventException != null)
                {
                    Environment.ExitCode = -1;
                    Console.WriteLine("Error! Unexpected failure to retrieve event {0}. This event will not be saved.", e.EventException.ToString());
                }
                else
                {
                    SaveEvent(evtRec);
                }
            }
        
        #endregion

        /// <summary>
        /// Loads a App.config and based on it subscribes to event log to write events
        /// either into XML file or SQL database.
        /// </summary>
        /// <param name="args">None, all settings in the App.config</param>
        /// <returns>0 on success, 87 on invalid input, -1 on system error.</returns>
        static int Main(string[] args)
        {
            EventLogWatcher logWatcher = null;
            EventLogSession session = new EventLogSession();
            EventLogQuery source = new EventLogQuery(Settings.Default.LogPath, PathType.LogName, Settings.Default.EventQuery);
            EventListenerProgram program = new EventListenerProgram();
            try
            {
                program.LoadNameToXPathHash(Settings.Default.SqlTableDefinition, ref program.nameToXPathMapping);

                if (program.LoadSchemaAndEvents())
                {
                    if (program.bookMarkToStartFrom != null)
                    {
                        Console.WriteLine("Found a bookmark file at '{0}\bookmark.stream' will use it for this run.", System.IO.Directory.GetCurrentDirectory());
                    }
                    logWatcher = new EventLogWatcher(source, program.bookMarkToStartFrom, Settings.Default.ReadExistingEvents);
                    logWatcher.EventRecordWritten += new EventHandler<EventRecordWrittenEventArgs>(program.reader_EventRecordWritten);
                    logWatcher.Enabled = true;

                    Console.WriteLine("Started subscription on channel {0}", Settings.Default.LogPath);
                    
                    if (Settings.Default.SubscribeForFutureEvents)
                    {
                        Console.WriteLine("Press any key to stop subscription & exit ...");
                        Console.ReadKey();
                    }
                }
            }
            catch (InvalidCastException e)
            {
                Console.WriteLine("Failed to cast from one type to another, this may be caused by incorrectly typed parameters, or other program failure.");
                Console.WriteLine(e.ToString());
                Environment.ExitCode = 87;
            }
            catch (SqlException e)
            {
                Console.WriteLine("Sql exception (below), may be caused because server could not be contacted.");
                Console.WriteLine(e.ToString());
                Environment.ExitCode = 87;
            }
            finally
            {
                logWatcher.Enabled = false;
                program.sqlConn.Close();
                logWatcher.Dispose();
            }

            Console.WriteLine("Saved {0} events.", program.savedEventCount);

            return Environment.ExitCode;
        }
    }
}

Compiling the Code

This example requires references to the System.dll and System.Core.dll files. Additionally, it references the System.Data.dll and System.Xml.dll to manipulate the event XML and write out the data to a SQL database.

See Also

Concepts

How to: Listen for Events and Store Them in a SQL Database
Setting up a SQL Database and Writing Event Data to the Database
Setting up An Event Listener and an Event Subscription

Send comments about this topic to Microsoft.

Copyright © 2007 by Microsoft Corporation. All rights reserved.