Hi @SplunkExplorer, I had a bit of fun with this today. Splunk ingests Windows event log events using a modular input, splunk-winevtlog.exe, which stream events in xml mode to stdout. The event str...
See more...
Hi @SplunkExplorer, I had a bit of fun with this today. Splunk ingests Windows event log events using a modular input, splunk-winevtlog.exe, which stream events in xml mode to stdout. The event stream looks like this: <stream>
<event stanza="WinEventLog://...">
<time>...</time>
<data>...</data>
<source>...</source>
<sourcetype>...</source>
<index>...</index>
</event>
<event>
...
</event>
<event>
...
</event>
...
</stream> The schema and output behavior are documented at https://dev.splunk.com/enterprise/docs/developapps/manageknowledge/custominputs/modinputsscript/#XML-mode. The startup path for splunk-winevtlog.exe is stored in %SPLUNK_HOME%\bin\scripts\splunk-winevt.log.path. With knowledge of these two things in hand, we can begin the work of writing a wrapper for splunk-winevtlog.exe that transforms the output of the command. I've written the first interation of a PowerShell 5.1 / .NET Framework 4.0 script to read the output of splunk-winevtlog.exe, modify the data and sourcetype elements, and write the new stream to stdout. Before writing the script, though, the translation from WinEventLog and XmlWinEventLog to JSON must be defined. In the case of WinEventLog, it's fairly straightforward to skip the timestamp and convert the key-value pairs into JSON keys. The following input (truncated for brevity): 12/03/2023 08:28:32 PM
LogName=Security
EventCode=4688
EventType=0
ComputerName=host1
SourceName=Microsoft Windows security auditing.
Type=Information
RecordNumber=123
Keywords=Audit Success
TaskCategory=Process Creation
OpCode=Info
Message=A new process has been created.
Creator Subject:
Security ID: host1\user becomes: {"LogName":"Security","EventCode":"4688","EventType":"0","ComputerName":"host1","SourceName":"Microsoft Windows security auditing.","Type":"Information","RecordNumber":"123","Keywords":"Audit Success","TaskCategory":"Process Creation","OpCode":"Info","Message":"A new process has been created.\nCreator Subject:\n\tSecurity ID:\t\thost1\\user"} In the case of XmlWinEventLog, we need to choose an object format, as compound elements, element values, and attributes don't translate directly arrays and keys. For the example, I handle EventData as an array, attributes as keys, and element values as a key named Value. Empty elements, <Foo/> are dropped. Elements with empty values, <Foo></Foo>, are retained. The following input (truncated for brevity): <Event xmlns='http://schemas.microsoft.com/win/2004/08/events/event'><System><Provider Name='Microsoft-Windows-Security-Auditing' Guid='{54849625-5478-4994-a5ba-3e3b0328c30d}'/><EventID>4688</EventID><Version>2</Version><Level>0</Level><Task>13312</Task><Opcode>0</Opcode><Keywords>0x8020000000000000</Keywords><TimeCreated SystemTime='2023-12-04T00:28:32.0000000Z'/><EventRecordID>1234</EventRecordID><Correlation/><Execution ProcessID='1234' ThreadID='1234'/><Channel>Security</Channel><Computer>host1</Computer><Security/></System><EventData><Data Name='SubjectUserSid'>host1\user</Data></EventData></Event> becomes: {"Event":{"System":{"Provider":{"Name":"Microsoft-Windows-Security-Auditing","Guid":"{54849625-5478-4994-a5ba-3e3b0328c30d}"},"EventID":{"Value":"4688"},"Version":{"Value":"2"},"Level":{"Value":"0"},"Task":{"Value":"13312"},"Opcode":{"Value":"0"},"Keywords":{"Value":"0x8020000000000000"},"TimeCreated":{"SystemTime":"2023-12-04T00:28:32.0000000Z"},"EventRecordID":{"Value":"123"},"Execution":{"ProcessID":"1234","ThreadID":"1234"},"Channel":{"Value":"Security"},"Computer":{"Value":"host1"}},"EventData":[{"Name":"SubjectUserSid","Value":"host1\\user"}]}} %SPLUNK_HOME%\bin\scripts\splunk-winevt.log.path $SPLUNK_HOME\bin\scripts\splunk-winevtlog.cmd %SPLUNK_HOME%\bin\scripts\splunk-winevtlog.cmd @"%SPLUNK_HOME%\bin\splunk-winevtlog.exe" %* | "%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy RemoteSigned -File "%SPLUNK_HOME%\bin\scripts\ConvertTo-JsonWinEventLog.ps1" %SPLUNK_HOME%\bin\scripts\ConvertTo-JsonWinEventLog.ps1 Add-Type -AssemblyName "System.Web"
$xmlReaderSettings = New-Object -TypeName "System.Xml.XmlReaderSettings"
$xmlReaderSettings.ConformanceLevel = [System.Xml.ConformanceLevel]::Fragment
$xmlReaderSettings.IgnoreWhitespace = $true
$xmlStreamReader = [System.Xml.XmlReader]::Create([System.Console]::In, $xmlReaderSettings)
$xmlWriterSettings = New-Object -TypeName "System.Xml.XmlWriterSettings"
$xmlWriterSettings.ConformanceLevel = [System.Xml.ConformanceLevel]::Fragment
$xmlWriterSettings.Indent = $true
$xmlWriterSettings.IndentChars = ""
$xmlWriter = [System.Xml.XmlTextWriter]::Create([System.Console]::Out, $xmlWriterSettings)
while ($xmlStreamReader.Read()) {
switch -Exact ($xmlStreamReader.NodeType) {
"Element" {
switch ($xmlStreamReader.Name) {
"event" {
# expected fragment:
#
# <event stanza="WinEventLog://...">
# <time>...</time>
# <data>...</data>
# <source>...</source>
# <sourcetype>...</sourcetype>
# <index>...</index>
# </event>
# write the <event> element
$xmlWriter.WriteStartElement($xmlStreamReader.Name)
# write the stanza attribute
if ($xmlStreamReader.HasAttributes) {
while ($xmlStreamReader.MoveToNextAttribute()) {
$xmlWriter.WriteAttributeString($xmlStreamReader.Name, $xmlStreamReader.Value)
}
$result = $xmlStreamReader.MoveToElement()
}
# read and write the <time> element
$result = $xmlStreamReader.Read()
$xmlWriter.WriteStartElement($xmlStreamReader.Name)
$result = $xmlStreamReader.Read()
$xmlWriter.WriteValue($xmlStreamReader.Value)
$result = $xmlStreamReader.Read()
$xmlWriter.WriteEndElement()
# read and store the <data> element
$result = $xmlStreamReader.Read()
$result = $xmlStreamReader.Read()
$data = $xmlStreamReader.Value
$result = $xmlStreamReader.Read()
# read and store the <source> element
$result = $xmlStreamReader.Read()
$result = $xmlStreamReader.Read()
$source = $xmlStreamReader.Value
$result = $xmlStreamReader.Read()
# read and store the <sourcetype> element
$result = $xmlStreamReader.Read()
$result = $xmlStreamReader.Read()
$sourcetype = $xmlStreamReader.Value
$result = $xmlStreamReader.Read()
# modify and write the <data> element based on the <sourcetype> value
# modify the sourcetype value
if ($sourcetype.startsWith("WinEventLog:")) {
$json = "{"
$stringReader = New-Object -TypeName "System.IO.StringReader" @($data)
# skip timestamp
$result = $stringReader.ReadLine()
while ($line = $stringReader.ReadLine()) {
$keyvalue = $line.Split("=", 2)
$key = $keyvalue[0]
$value = $keyvalue[1]
switch ($key) {
"Message" {
$json += "`"" + [System.Web.HttpUtility]::JavaScriptStringEncode($key) + "`":`"" + [System.Web.HttpUtility]::JavaScriptStringEncode($value) + [System.Web.HttpUtility]::JavaScriptStringEncode($stringReader.ReadToEnd()) + "`","
}
default {
$json += "`"" + [System.Web.HttpUtility]::JavaScriptStringEncode($key) + "`":`"" + [System.Web.HttpUtility]::JavaScriptStringEncode($value) + "`","
}
}
}
$json += "}"
$data = $json.Replace(",]", "]").Replace(",}", "}")
$sourcetype = "JsonWinEventlog"
}
elseif ($sourcetype.startsWith("XmlWinEventLog:")) {
$json = "{`"Event`":{"
$stringReader = New-Object -TypeName "System.IO.StringReader" @($data)
$xmlEventReader = [System.Xml.XmlReader]::Create($stringReader, $xmlReaderSettings)
$result = $xmlEventReader.MoveToContent()
while ($xmlEventReader.Read()) {
switch -Exact ( $xmlEventReader.NodeType) {
"Element" {
if ($xmlEventReader.HasAttributes -or -not $xmlEventReader.IsEmptyElement) {
switch -Exact ($xmlEventReader.Name) {
"EventData" {
$json += "`"" + [System.Web.HttpUtility]::JavaScriptStringEncode($xmlEventReader.Name) + "`":["
}
"Data" {
$json += "{"
while ($xmlEventReader.MoveToNextAttribute()) {
$json += "`"" + [System.Web.HttpUtility]::JavaScriptStringEncode($xmlEventReader.Name) + "`":`"" + [System.Web.HttpUtility]::JavaScriptStringEncode($xmlEventReader.Value) + "`","
}
$result = $xmlEventReader.MoveToElement()
}
default {
$json += "`"" + [System.Web.HttpUtility]::JavaScriptStringEncode($xmlEventReader.Name) + "`":{"
if ($xmlEventReader.HasAttributes) {
while ($xmlEventReader.MoveToNextAttribute()) {
$json += "`"" + [System.Web.HttpUtility]::JavaScriptStringEncode($xmlEventReader.Name) + "`":`"" + [System.Web.HttpUtility]::JavaScriptStringEncode($xmlEventReader.Value) + "`","
}
$result = $xmlEventReader.MoveToElement()
}
if ($xmlEventReader.IsEmptyElement) {
$json += "},"
}
}
}
}
}
"Text" {
$json += "`"Value`":`"" + [System.Web.HttpUtility]::JavaScriptStringEncode($xmlEventReader.Value) + "`""
}
"EndElement" {
if ($xmlEventReader.Name -eq "EventData") {
$json += "],"
}
else {
$json += "},"
}
}
}
}
$json += "}"
$data = $json.Replace(",]", "]").Replace(",}", "}")
$sourcetype = "JsonXmlWinEventlog"
}
# write the <data> element
$xmlWriter.WriteStartElement("data")
$xmlWriter.WriteValue($data)
$xmlWriter.WriteEndElement()
# write the <source> element
$xmlWriter.WriteStartElement("source")
$xmlWriter.WriteValue($source)
$xmlWriter.WriteEndElement()
# write the <sourcetype> element>
$xmlWriter.WriteStartElement("sourcetype")
$xmlWriter.WriteValue($sourcetype)
$xmlWriter.WriteEndElement()
# continue
}
default {
$xmlWriter.WriteStartElement($xmlStreamReader.Name)
if ($xmlStreamReader.HasAttributes) {
while ($xmlStreamReader.MoveToNextAttribute()) {
$xmlWriter.WriteAttributeString($xmlStreamReader.Name, $xmlStreamReader.Value)
}
$result = $xmlStreamReader.MoveToElement()
}
}
}
}
"Text" {
$xmlWriter.WriteValue($xmlStreamReader.Value)
}
"EndElement" {
$xmlWriter.WriteEndElement()
}
}
$xmlWriter.Flush()
} Sample inputs.conf with renderXml = false: [WinEventLog://Security]
checkpointInterval = 5
current_only = 0
disabled = 0
start_from = oldest
renderXml = false Sample inputs.conf with renderXml = true: [WinEventLog://Security]
checkpointInterval = 5
current_only = 0
disabled = 0
start_from = oldest
renderXml = true
suppress_text = true
suppress_sourcename = true
suppress_keywords = true
suppress_type = true
suppress_task = true
suppress_opcode = true Tying everything together generates events with either sourcetype=JsonWinEventLog or sourcetype=JsonXmlWinEventLog depending on the original sourcetype. As @PickleRick noted, your next task would be re-creating the knowledge objects provided by Splunk Add-on for Windows. Do try this at home! But don't try it in production without sufficient testing. If you're looking for a simpler solution, there's at least one very popular third-party product that handles transformations like this in manageable pipelines.