Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to store an object in the Windows Event Log?

Recently we've added the option to all our scripts to log their messages in the Windows Event Log. This works great for short messages, but we can't seem to find a way to save events in a structured way so we can later create objects with them.

An example of an event that can store multiple object proprties: Service Control Manager

How is this done with PowerShell?

We've tried the following as described here but with no luck:

Write-EventLog -LogName HCScripts -Source 'Test (Brecht)' -EventId 4 -Message "<Data Name=""MyKey1"">MyValue1</Data>"

enter image description here

In this post there are other options described but we can't seem to figure out how to do it properly.

Reading the events is done with:

Function Get-WinEventDataHC {
    Param (
        [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
        [System.Diagnostics.Eventing.Reader.EventLogRecord[]]$Event
    )

    Process {
        foreach ($E in $Event){
            $XML = [XML]$E.ToXml()

            # Some events use other nodes, like 'UserData' on Applocker events...
            $XMLData = $null
            if ($XMLData = @($XML.Event.EventData.Data)){
                For ($i=0; $i -lt $XMLData.count; $i++){
                    $Params = @{
                        InputObject       = $E
                        NotePropertyName  = $EventXML.Event.EventData.Data[$i].Name
                        NotePropertyValue = $EventXML.Event.EventData.Data[$i].’#text’
                    }
                    Add-Member @Params
                }
            }

            $E
        }
    }
}
Get-WinEvent -ProviderName 'Test (Brecht)' | Select-Object -First 1 | Get-WinEventDataHC | fl *

Thank you for your help.

like image 317
DarkLite1 Avatar asked Apr 24 '17 12:04

DarkLite1


1 Answers

I have found two possible solutions to the question "How is this done with PowerShell?". The first involves a custom PowerShell method and utilizing system assemblies to write to an event log. The second involves implementing a custom provider. It should be noted, this doesn't store XML in the <Data> node. It stores data in independent elements.

Method 1: Custom PowerShell Function

This methodology comes form an article written by Kevin Holman His explanation is outstanding. I duplicated the code here so the answer here will be complete.

  1. Define the event log and source you want to log too, load the System.Diagnostics.EventLog assembly, and finally create a function CreateParamEvent that will write to an event log with specific parameters.

    #Define the event log and your custom event source
    $evtlog = "Application"
    $source = "MyEventSource"
    
    #Load the event source to the log if not already loaded.  This will fail if the event source is already assigned to a different log.
    if ([System.Diagnostics.EventLog]::SourceExists($source) -eq $false) {
        [System.Diagnostics.EventLog]::CreateEventSource($source, $evtlog)
    }
    
    #function to create the events with parameters
    function CreateParamEvent ($evtID, $param1, $param2, $param3)
      {
        $id = New-Object System.Diagnostics.EventInstance($evtID,1); #INFORMATION EVENT
        #$id = New-Object System.Diagnostics.EventInstance($evtID,1,2); #WARNING EVENT
        #$id = New-Object System.Diagnostics.EventInstance($evtID,1,1); #ERROR EVENT
        $evtObject = New-Object System.Diagnostics.EventLog;
        $evtObject.Log = $evtlog;
        $evtObject.Source = $source;
        $evtObject.WriteEvent($id, @($param1,$param2,$param3))
      }
    
  2. The next step is to setup the parameters you'd like to write to the log and call the function.

    #These are just examples to pass as parameters to the event
    $hostname = "computername.domain.net"
    $timestamp = (get-date)
    
    #Command line to call the function and pass whatever you like
    CreateParamEvent 1234 "The server $hostname was logged at $timestamp" $hostname $timestamp 
    

Method 2: Custom Event Provider

This methodology comes form an article written by Daniel Gordon I've reduced some of the complexity of his example and provided source and instructions in this GitHub Repository

  1. The key piece of data you need to provide is an Event Provider Manifest. This manifest contains the details of the new event provider. And, most importantly, the custom payload of the event. The element in this file that is critical is the <templates> element. It defines the fields that will ultimately turn into <Data> elements in your event payload.
    <?xml version="1.0" encoding="UTF-8"?>
    <instrumentationManifest xsi:schemaLocation="http://schemas.microsoft.com/win/2004/08/events eventman.xsd"
        xmlns="http://schemas.microsoft.com/win/2004/08/events"
        xmlns:win="http://manifests.microsoft.com/win/2004/08/windows/events"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:xs="http://www.w3.org/2001/XMLSchema" 
         xmlns:trace="http://schemas.microsoft.com/win/2004/08/events/trace">
        <instrumentation>
            <events>
                <provider name="CustomProvider"
                     symbol="CustomProvider"
                     guid="{10ABB82A-BB5A-45FF-A7D6-D7369B235DD8}"
                     resourceFileName="C:\CustomProvider\CustomProvider.dll"
                     messageFileName="C:\CustomProvider\CustomProvider.dll">  
                    <events>
                        <event symbol="CustomEvent" value="10000" version="1" channel="CustomProvider/Log" template="CustomTemplate" />
                    </events>
                    <levels/>
                    <tasks/>
                    <opcodes/>
                    <channels>
                        <channel name="CustomProvider/Log" value="0x10" type="Operational" enabled="true" />
                    </channels>
                    <templates>
                        <template tid="CustomTemplate">
                            <data name="MyKey1" inType="win:UnicodeString" outType="xs:string" />
                        </template>
                    </templates>
                </provider>
            </events>
        </instrumentation>
        <localization/>
     </instrumentationManifest>
  1. Once the manifest is created, we need to compile and install the Provider on the computer. I saved my manifest as CustomProvider.man in C:\CustomProvider\. If you don't follow this convention, you'll have to update the paths in the CustomProvider.man. Once saved, Open a Visual Studio Command Prompt as Administrator and cd to C:\CustomProvider

enter image description here

  1. Compile the manifest by executing: mc -css Namespace CustomProvider.man

enter image description here

  1. Create the resource file by executing: rc CustomProvider.rc

enter image description here

  1. Compile the source: csc /target:library /unsafe /win32res:CustomProvider.res CustomProvider.cs

enter image description here

  1. Register the provider by executing. wevtutil im CustomProvider.man

enter image description here

  1. You'll now see the custom provider in the Windows Event Viewer

    enter image description here

  2. To write to the log, open a Windows Powershell prompt and execute

    New-WinEvent -ProviderName CustomProvider -Id 10000 -Payload @("MyValue1") 
    

    then refresh the event log and you'll see the event.

enter image description here

enter image description here

like image 52
Grady G Cooper Avatar answered Sep 20 '22 15:09

Grady G Cooper