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:
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>"
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.
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.
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))
}
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
<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>
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:\CustomProvidermc -css Namespace CustomProvider.man
rc CustomProvider.rc
csc /target:library /unsafe /win32res:CustomProvider.res CustomProvider.cs
wevtutil im CustomProvider.man
You'll now see the custom provider in the Windows Event Viewer
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With