Search code examples
powershellxpathget-winevent

EventLogPropertySelector Not Returning Extended Data From Event Object In PowerShell


My short term goal is to gather event IDs 40 and 42 with provider Microsoft-Windows-TerminalServices-LocalSessionManager from the log named Microsoft-Windows-TerminalServices-**LocalSessionManager/Operational then sort them based on the Session/SessionID. While I could try and parse the message property, I'm trying to use the EventLogRecord's GetPropertyValues method which takes a System.Diagnostics.Eventing.Reader.EventLogPropertySelector parameter. Also, the session ID shows up first labeled "Session" for event 40's message and shows up second labeled "SessionID" for event 41's message.

Name MemberType Definition
GetPropertyValues Method System.Collections.Generic.IList[System.Object]

I've see that in 2018 Peter-Core and Mathias R. Jessen have both given answer on how to do this. Following their examples, I looked at the event provider's event template.

(Get-WinEvent -ListProvider Microsoft-Windows-TerminalServices-LocalSessionManager).Events|Where-object{@(40,42) -contains $_.ID}

Id          : 40
Version     : 0
LogLink     : System.Diagnostics.Eventing.Reader.EventLogLink
Level       : System.Diagnostics.Eventing.Reader.EventLevel
Opcode      : System.Diagnostics.Eventing.Reader.EventOpcode
Task        : System.Diagnostics.Eventing.Reader.EventTask
Keywords    : {}
Template    : <template xmlns="http://schemas.microsoft.com/win/2004/08/events">
<data name="Session" inType="win:UInt32" outType="xs:unsignedInt"/>
<data name="Reason" inType="win:UInt32" outType="xs:unsignedInt"/>
</template>

Description : Session %1 has been disconnected, reason code %2

Id          : 42
Version     : 0
LogLink     : System.Diagnostics.Eventing.Reader.EventLogLink
Level       : System.Diagnostics.Eventing.Reader.EventLevel
Opcode      : System.Diagnostics.Eventing.Reader.EventOpcode
Task        : System.Diagnostics.Eventing.Reader.EventTask
Keywords    : {}
Template    : <template xmlns="http://schemas.microsoft.com/win/2004/08/events">
                <data name="User" inType="win:UnicodeString" outType="xs:string"/>
                <data name="SessionID" inType="win:UInt32" outType="xs:unsignedInt"/>
              </template>

Description : End session arbitration:

              User: %1
              Session ID: %2

As a test, I created the following to try and get the Session ID from the event ID 42:

$strComputerName='PMV-SC01'
<# For those wanting to build xmlFilters try something like the following as it allow you to create them more dynamically
$xmlFilterBase=[xml]'<QueryList><Query Id="0"></Query></QueryList>'
## in a loop ##
$xmlSelect=$xmlFilter.CreateElement("Select")
$xmlSelect.SetAttribute("Path","$Logname")
$xmlSelect.set_InnerText("*[System[$($ProviderName)TimeCreated[@SystemTime>='$StartTime' and @SystemTime<='$EndTime']]]")
$xmlFilter.QueryList.Query.AppendChild($xmlSelect)|Out-Null
$xmlSelect=$Null
## end loop ##
#>
#I used the filter below to simplify the testing.
$xmlFilter=@"
<QueryList><Query Id="0" Path="Microsoft-Windows-TerminalServices-LocalSessionManager/Operational"><Select Path="Microsoft-Windows-TerminalServices-LocalSessionManager/Operational">*[System[TimeCreated[@SystemTime&gt;='2021-06-28T22:00:00.000Z' and @SystemTime&lt;='2021-06-28T23:00:00.000Z']]]</Select></Query></QueryList>
"@
$WinEvents=Get-WinEvent -ComputerName $strComputerName -FilterXml $xmlFilter -Oldest|Where-object{$_.Id -eq 42}
$WinEvents.count
# output is 10
$SelectorStrings= [String[]]@(
'Event/EventData/Data[@name="SessionID"]'
)
$PropertySelector= [System.Diagnostics.Eventing.Reader.EventLogPropertySelector]::new($SelectorStrings)
$WinEvents|ForEach-Object{$TheSessionIds=@();$TheSessionIds+=$_.GetPropertyValues($PropertySelector)}
$TheSessionIds.count
#output is 1
# All of the follow also return nothing:
$WinEvents[1].GetPropertyValues($PropertySelector)
$WinEvents.GetPropertyValues($PropertySelector)
$1WinEvent=$WinEvents[0]
$1WinEvent.GetPropertyValues($PropertySelector)

There is no error message. The GetPropertyValues() method just returns nothing. PowerShell Version: 5.1.14393.2457

Windows Edition Version OS Build
Windows Server 2016 Standard 1607 14393.2457

(Get-WindowsVersion credit)

My long term goal is generating something like following output. The request on this post is help getting values back from the GetPropertyValues method. I'd also like to use this method in other projects.

SessionID User TimeCreated ID Code
12 MYDOMAIN\User.Name 2021-06-28 07:35:54.4321 42
12 2021-06-28 15:35:54.4321 40 0
13 MYDOMAIN\Other.User 2021-06-28 11:12:44.1234 42
13 2021-06-28 13:01:12.5678 40 0

Solution

  • If you examine a sample XML record you can see the path to the node is not under /EventData/Data. It took some fiddling but this worked perfect for me.

    $xmlFilter = @"
    <QueryList>
      <Query Id="0" Path="Microsoft-Windows-TerminalServices-LocalSessionManager/Operational">
        <Select Path="Microsoft-Windows-TerminalServices-LocalSessionManager/Operational">*[System[(EventID=40 or EventID=42)]]</Select>
      </Query>
    </QueryList>
    "@
    
    $SelectorStrings= [String[]]@(
        'Event/UserData/EventXML/User',
        'Event/UserData/EventXML/Session',
        'Event/UserData/EventXML/Reason'
    )
    
    Get-WinEvent -FilterXml $xmlFilter | ForEach-Object {
    
        switch ($_.id){
            40 {$SelectorStrings[1] = 'Event/UserData/EventXML/Session'}
            42 {$SelectorStrings[1] = 'Event/UserData/EventXML/SessionID'}
        }
    
        $PropertySelector= [System.Diagnostics.Eventing.Reader.EventLogPropertySelector]::new($SelectorStrings)
    
        $user,$sessionID,$reason = $_.GetPropertyValues($PropertySelector)
    
        [PSCustomObject]@{
            SessionID   = $sessionID
            User        = $user
            TimeCreated = $_.TimeCreated
            ID          = $_.ID
            Code        = $reason
        }
    
    }