locked
EventLog - ConvertFrom-String or indexing into data returned from audit job? RRS feed

  • Question

  • So I stumbled across this fine article: https://social.technet.microsoft.com/Forums/ie/en-US/42f8e6a3-4304-4215-b521-d611e3216e1c/eventlog-convertfromstring?forum=winserverpowershell where someone had the same idea I had to use a template to parse the message data returned and found jrv's excellent suggestion to simply index into the properties and return the appropriate data based on the array index of the Properties field.  EXCELLENT!  Except... it didn't work (quite like I planned).

    I used Invoke-Command to get the data from all of my forest AD servers and set up my job as follows:

    $user = 'targetuser111'
    $forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
    $forestservers = $Forest.Sites | % { $_.Servers } | Select -ExpandProperty Name
    
    $qxpath = "*[System[TimeCreated[timediff(@SystemTime) <= 86400000]] and EventData[Data[@Name='TargetUserName']='$user']]"
    
    $adauditJob = Invoke-Command -ComputerName $forestservers -ScriptBlock {Get-WinEvent -LogName Security -FilterXPath $args[0]} -ArgumentList $($qxpath) -JobName "ADAudit" -AsJob
    $eventdata = Get-Job ADAudit | Wait-Job | Receive-Job -ErrorAction SilentlyContinue
    if($eventdata) {$eventdata}

    Except I couldn't index into the variable to get the value of the field "Account Name".  (Field 6 I believe)

    When I run 

    $eventdata[0].Properties[0]

    I get a return of:

    System.Diagnostics.Eventing.Reader.EventProperty

    I think I have tracked it down to the fact that the data returned is converted from EventProperty to String and is inaccessible therafter.  

    When I run on my local machine: 

    $testdata = Get-WinEvent -LogName Security -FilterXPath $qxpath

    (Using the same xpath query above) the data type returned LOOKS like the same data... its just missing a field from the end where it records the computer name where the data was returned from, however the data is very different:

    $eventdata[0].GetType() Returns:

    sPublic IsSerial Name                                     BaseType                                                                                                                                  
    -------- -------- ----                                     --------                                                                                                                                  
    True     True     PSObject                                 System.Object 

    $testdata[0].GetType() Returns:

    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     False    EventLogRecord                           System.Diagnostics.Eventing.Reader.EventRecord

    Is there any way to mine the data in the "Messages" field after parallel processing the request to all domain controllers or will I have to gather the data individually and compile it myself?

    Thanks!

    Wednesday, January 3, 2018 12:17 AM

All replies

  • Don't use Invoke=Command.  Get-WinEvent takes a computername parameter for remoting and does not cause marshaling issues.

    Your job is executing on the remote system.  Why?  You cannot run GEt-Job remotely.


    \_(ツ)_/

    Wednesday, January 3, 2018 12:30 AM
  • Thanks jrv - the hope was you were on still!

    The Invoke-Command was to utilize parallel processing across all DC (I just recently learned Invoke-Command can execute across many systems in parallel).  Get-Job pulls it all back together.   I would have just used Get-WinEvent, but it cannot process an array value for -ComputerName on its own.  Hence the method to my madness.

    Logins are processed across the domain and the data is not consistent across all DCs.  In this particular case I'm looking for activity by a particular user.  My thought was to gather the data on all activity for 1 user from all DCs, then sift through the data when it is brought back, except the beautiful object is crushed and turned into plain string text. 

    Further it was an interesting lead in to converting types - something I'm still really new at.

    Wednesday, January 3, 2018 12:40 AM
  • Get-WinEvent can be run like that but will behave differently.

    To use jobs you will need to be sure the jobs are returning data and that you are reverencing the data correctly.  You example seems odd.  Perhps you have other errors?

    This is how I would do this:

    $user = 'targetuser111'
    $sb = {
        $qxpath = "*[System[TimeCreated[timediff(@SystemTime) <= 86400000]] and EventData[Data[@Name='TargetUserName']='$($args[0])']]"
        Get-WinEvent -LogName Security -FilterXPath $qxpath
    }
    $forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() $forestservers = $Forest.Sites | ForEach-Object { $_.Servers } | Select-Object -ExpandProperty Name $adauditJob = Invoke-Command -ComputerName $forestservers -ScriptBlock $sb -ArgumentList $user -AsJob $eventdata = $adauditJob | Wait-Job | Receive-Job


    \_(ツ)_/



    • Edited by jrv Wednesday, January 3, 2018 1:04 AM
    Wednesday, January 3, 2018 1:00 AM
  • This is even cleaner and easier to understand.

    $user = 'targetuser111'
    $sb = {
        $qxpath = "*[System[TimeCreated[timediff(@SystemTime) <= 86400000]] and EventData[Data[@Name='TargetUserName']='$($args[0])']]"
        Get-WinEvent -LogName Security -FilterXPath $qxpath
    }
    $servers = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites.Servers.Name
     
    Invoke-Command -ComputerName $servers -ScriptBlock $sb -ArgumentList $user -AsJob |
        Wait-Job | Receive-Job
    


    \_(ツ)_/


    • Edited by jrv Wednesday, January 3, 2018 1:04 AM
    Wednesday, January 3, 2018 1:04 AM
  • That is significantly cleaner code jrv - thanks for condensing it.

    However, the crux of the problem still remains, the data returned by running the command via Invoke-Command changes the datatype for the individual events from Name=EventLogRecord ,BaseType=System.Diagnostics.Eventing.Reader.EventRecord to Name=PSObject, BaseType=SystemObject

    Example:

    $user = 'targetuser111'
    $sb = {
        $qxpath = "*[System[TimeCreated[timediff(@SystemTime) <= 86400000]] and EventData[Data[@Name='TargetUserName']='$($args[0])']]"
        Get-WinEvent -LogName Security -FilterXPath $qxpath
    }
    $server = "SINGLE-DC"
    
    $testdata = Get-WinEvent -ComputerName $server -LogName Security -FilterXPath $qxpath -Credential $cred
    
    $eventdata = Invoke-Command -ComputerName $server -ScriptBlock $sb -ArgumentList $user -Credential $cred

    Output of $testdata[0].GetType() and $eventdata[0].GetType() is not the same, and results in the commands $testdata[0].Properties[0] returning the expected data and $eventdata[0].Properties[0] returning the string "System.Diagnostics.Eventing.Reader.EventProperty"

    What I am trying to understand (and is past my current skill set) is:

    1) Why the data that is normally EventLogRecord gets converted to PSObject by an Invoke-Command?  This is both the magic of PowerShell and the sausage grinder - data goes in one way and comes out another.  I don't know how to predict when this is going to happen and I'd like a nudge in the right direction on how to figure it out, if possible.

    2) If there is a better way to run the Get-WinEvent in parallel and preserve the data as EventLogRecord to allow for better parsing using the Properties field?

    Hope that better explains what I'm attempting to do and the results I'm getting.

    Thanks again for the guidance!

    Wednesday, January 3, 2018 3:58 PM
  • Like I posted earlier.  When remotely returning objects many are converted.

    Don't use Invoke command or extract the required properties remotely and return custom objects.

    $sb = {
        $qxpath = "*[System[TimeCreated[timediff(@SystemTime) <= 86400000]] and EventData[Data[@Name='TargetUserName']='$($args[0])']]"
        Get-WinEvent -LogName Security -FilterXPath $qxpath |
            Select ID,@{n='TargertUser';e={$_.Properties[1].Value}}
    }
    $server = 'PC702'
    $user = 'user01'
    Invoke-Command -ComputerName $server -ScriptBlock $sb -ArgumentList $user #-Credential $cred
    


    \_(ツ)_/

    Wednesday, January 3, 2018 4:15 PM
  • I'm glad the moderator posted the answer as the Proposed Answer - I had lost the thread and had a similar solution to the one that jrv proposed. (For rendering the property on the remote side and returning a custom object).  I utilized code I found elsewhere (related to lockout events) and came up with the following:

    $user = 'adusername'
    $DCs = (Get-ADDomainController -Filter *).Hostname
    $eventdata = ""
    $sb = {
        
        $qxpath = "*[System[TimeCreated[timediff(@SystemTime) <= 86400000]] and EventData[Data[@Name='TargetUserName']='$($args[0])']]"
        
    
            Try {
                $Events = Get-WinEvent -LogName Security -FilterXPath $qxpath -ErrorAction Stop
    
                ForEach ($Event in $Events) {
                    # Convert the event to XML
                    $eventXML = [xml]$Event.ToXml()
                    # Iterate through each one of the XML message properties
                    For ($i=0; $i -lt $eventXML.Event.EventData.Data.Count; $i++) {
                        # Append these as object properties
                        Add-Member -InputObject $Event -MemberType NoteProperty -Force `
                            -Name  $eventXML.Event.EventData.Data[$i].name `
                            -Value $eventXML.Event.EventData.Data[$i].'#text'
                    }
                }
    
                $Events | Select-Object *
            }
            Catch {
                If ($_.Exception -like "*No events were found that match the specified selection criteria*") {
                    Write-Warning "[$(hostname)] No events found"
                } Else {
                    $_
                }
            }
        
        }
    
        # Clear out the local job queue
        Get-Job ADAudit | Remove-Job
        
        Invoke-Command -ComputerName $DCs -ScriptBlock $sb -ArgumentList $user -AsJob -JobName "ADAudit" | Out-Null
        $eventdata = Get-Job ADAudit | Wait-Job | Receive-Job -ErrorAction SilentlyContinue
    
        # Clean up the local job queue
        Get-Job ADAudit | Remove-Job

    I wanted to post it for the community in case anyone else was looking for similar work.  I liked the elegance of iterating through all the properties and capturing them - I'd rather have the data and not need it than have to look for it and alter the query later... just my preference. 

    Thanks jrv for the nudge on processing the data remotely!



    Monday, January 29, 2018 2:16 PM