How to store an object in the Windows Event Log?
Asked Answered
E

1

13

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.

Elicia answered 24/4, 2017 at 12:6 Comment(2)
I suppose the issue would be that what you are doing does not match the defined schema maybe? Does it have to be XML you store? You could easy work JSON or some other text based object structure in there as well. I don't know XML that well but perhaps you can make it ignore the schema? Perhaps this has nothing to do with it in which case , my bad.Cattleya
It doesn't have to be XML but it's the standard used by the Windows Event Log. Is we opt to use something else it might be that the message is not properly displayed in the GUI. The XML is normally only visible in the XML detail pane of an Event.Elicia
R
6

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

Razor answered 28/4, 2017 at 22:2 Comment(1)
Thx for the detailed explanation.When trying to stay away from Visio I tried method 1. This works great but it doesn't seem to be possible to store the property name with the property value. For example 'MyKey1' = 'MyValue1' as indicated in your last screenshot. The goal here is to retrieve the info back as an object later on.Elicia

© 2022 - 2024 — McMap. All rights reserved.