Powershell: filtering out events from certain process

94 Views Asked by At

I have quite a usual for every sysadm in task: to audit access to certain folders on fileserver during last, e.g. 1 day. But in addition I need to rule out access attempts from certain processes.

What I wrote (part of code:

foreach ($FileServer in $Fileservers) {
    Write-Host -ForegroundColor Green "Checking events on $FileServer"
    $GweParams = @{
        Computername = $Fileserver
        LogName = ‘Security’
        FilterXPath = '*[System[EventID=4656 or EventID=4663]
                       and not (EventData[Data[@Name='ProcessName'] = 'C:\Windows\System32\fsdmhost.exe'])
                       and TimeCreated[timediff(@SystemTime) <= 360000]]'
    }
    $Events = Get-Eventlog @GweParams
    $Events
    $Events | export-csv -path C:\usr\logs.csv
}

But Powershell gives me plenty of errors, and even more, ISE visually splitting this code in quite unexpected blocks. It seems to me that I am missing some of quotes and brackets, but can't figure out where?

Could somebody help me with this? Or suggest some better code?

3

There are 3 best solutions below

0
Douda On

Taking Event ID 4656 as an example from your code, it looks like there is a specific field with process information. You can just filter out the events based on the process name such as :

$events = Get-EventLog -LogName Security
$filteredEvents = $events | Where-Object { $_.Process -ne "test.exe" }

Assuming the property available is called "Process". I don't have access to an example of this specific ID, but you can provide one like this

Get-EventLog -LogName Security | Where-Object { EventID -eq 4656 } | Select -First 1

Do not forget to remove any sensitive / PII info before answering. Happy to follow up after that

1
Darin On

There is more than one way to skin a cat, but there aren't any he will like.
Similarly, there is more than one way to filter events, but there aren't any I like.

Get-WinEvent:

No claims that this is the best way, just the way I figured out.

In the following code example of Get-WinEvent:

  1. You can optionally define a remote computer via the -ComputerName parameter.
    • Note: When connecting to a remote computer, you may receive a "The RPC server is unavailable" error, or other errors.
      • Fixing these errors is outside the scope of this question and answer.
  2. To filter on multiple properties, define a hash table of property names/keys and the desired value for each. In this example:
    • LogName: Set to "Security", limits to only events in the Security log.
    • ID: An array of Event IDs.
    • StartTime: Retrieve only events after this point of time.
  3. Several example lines for defining multiple versions of $StartTime are provided, all but one line is commented out, with tail comments explaining the line.
  4. The cmdlet Get-WinEvent pops out an alarming looking error message if it fails to find matching events.
    • This SO tip by briantist solves the problem by using a try/catch with -ErrorAction Stop.
  5. The matching events are saved in the $rawEvents variable.
  6. For more information, read the page on Get-WinEvent.

Setup our variables:

$StartTime = [DateTime]::now.AddMinutes(-2)        # Events in the last 2 minutes
#$StartTime = [DateTime]::now.AddDays(-2)          # Events in the last 2 Days
#$StartTime = [DateTime]::now.AddHours(-2)         # Events in the last 2 Hours
#$StartTime = [DateTime]::now.AddMilliseconds(-2)  # Events in the last 2 Milliseconds
#$StartTime = [DateTime]::now.AddMinutes(-2)       # Events in the last 2 Minutes
#$StartTime = [DateTime]::now.AddMonths(-2)        # Events in the last 2 Months
#$StartTime = [DateTime]::now.AddSeconds(-2)       # Events in the last 2 Seconds
#$StartTime = [DateTime]::Now.AddTicks(-2)         # Events in the last 2 Ticks
#$StartTime = [DateTime]::now.AddYears(-2)         # Events in the last 2 Years
$FilterHashtable = @{
   LogName   = 'Security';
   ID        = @(4624, 4670, 4672, 4699, 4985);
   StartTime = $StartTime
}

Safely execute Get-WinEvent without error, and exit if either no matchings events are found or other error:

try {
   # $rawEvents = Get-WinEvent -ComputerName "server.domain.com" -FilterHashtable $FilterHashtable -ErrorAction Stop
   $rawEvents = Get-WinEvent -FilterHashtable $FilterHashtable -ErrorAction Stop
}
catch [Exception] {
   if ($_.Exception -match "No events were found that match the specified selection criteria") {
      Write-Host "No events found";
   }
   exit
}

Part 2:

Did you really think we were done?

So, you want to filter events based on the ProcessName property. Sounds easy enough, until you discover there is no such property! No problem, all you have to do is call the ToXml() method of each event, drill down to the .Event.EventData.Data array returned by that method, check each element in the array for a Name property containing "ProcessName", return that element's #text property, which can't be accessed in a normal way because it starts with a #, and then do a comparison on it to see if it is what you want.

Or, you could use the following code which adds to each Event on the pipeline an EventData member containing all members defined by the xml the ToXml() method provided. This does cause a performance hit, but it didn't seem all that noticeable when compared already existing slowness of Get-WinEvent.

$cookedEvents = $rawEvents |
   Select-Object -Property *, @{Name = 'EventData'; Expression = {
      $return = [PSCustomObject]::new()
      foreach ($obj in ([xml]$_.ToXml()).Event.EventData.Data) {
         Add-Member -InputObject $return -MemberType NoteProperty -Name $obj.Name -Value $obj.'#text'
      }
      $return
   }
}

Now, thanks to the newly added EventData, it is smooth sailing from this point forward and no more problems - right? Right? Oh, right! Not all events will necessarily include the ProcessName property in the xml, so, to play it safe, we probably should filter out events missing this property.

Same code as before, just one more line to filter out a missing property:

$cookedEvents = $rawEvents |
   Select-Object -Property *, @{Name = 'EventData'; Expression = {
      $return = [PSCustomObject]::new()
      foreach ($obj in ([xml]$_.ToXml()).Event.EventData.Data) {
         Add-Member -InputObject $return -MemberType NoteProperty -Name $obj.Name -Value $obj.'#text'
      }
      $return
   }
} | Where-Object { Get-Member -InputObject $_.EventData -Name 'ProcessName' -MemberType Properties }

So, you want all events by all processes except those with a ProcessName of "C:\Windows\System32\fsdmhost.exe". The following line should do it!:

$results = $cookedEvents | Where-Object { $_.EventData.ProcessName -ne 'C:\Windows\System32\fsdmhost.exe' }
0
GregoryA On

All right, with your help, some investigations and few cother answers to other topics I managed to create two scripts for my linking:

$ErrorActionPreference = "SilentlyContinue"
Clear-Host
#List of fileservers:
$FileServers = @(
    'Server1',
    'Server2',
    'Server3',
    'Server4')

#Query settings:
$Query = @"
<QueryList>
  <Query Id="0" Path="Security">
    <Select Path="Security">*[System[(EventID=4656) or (EventID=4663)]]
     and *[System[TimeCreated[timediff(@SystemTime) &lt;= 86400000]]]</Select>
    <Suppress Path="Security">*[EventData[Data[@Name='ProcessName'] and (Data='C:\Windows\System32\fsdmhost.exe') ]]</Suppress>
  </Query>
</QueryList>
"@
#before is a query for last day from time script has been run.

#$Begin = '2024-02-01T00:00:00.0000000Z'
#$End = '2024-02-02T13:00:00.0000000Z'
#Time format: YYYY-MM-DD  t HH:MM:SS.0000000Z for milliseconds

#$Query = @"
#<QueryList>
#  <Query Id="0" Path="Security">
#    <Select Path="Security">
#    *[[System[(EventID=4656) or (EventID=4663)]]
#    and
#    *[System[TimeCreated[@SystemTime>='" + $Begin + "' and @SystemTime<='" + $End + "']]]
#    </Select>
#    <Suppress Path="Security">*[EventData[Data[@Name='ProcessName'] and (Data='C:\Windows\System32\fsdmhost.exe') ]]</Suppress>
#  </Query>
#</QueryList>
#"@

#before all commented - two additional variables and query for checking events from Begin-date to End-date.

#Parameters we need in output:
$properties = @(
    @{n='TimeStamp';e={$_.TimeCreated}}
    @{n='Object';e={$_.Properties[6].Value}},
    @{n='User';e={$_.Properties[1].Value}},
    @{n='Process';e={$_.Properties[15].Value}}
    )

#Script itslef
foreach ($FileServer in $Fileservers) {
Write-Host -ForegroundColor Green "Checking events on $FileServer"
#cycling servers

$events = Get-WinEvent -ComputerName $FileServer -FilterXml $Query

ForEach ($Event in $Events) {
    # Convert the event to XML
    $eventXML = [xml]$Event.ToXml()
}

#final export to c:\usr\security.csv
$Events | Select-Object $properties | Export-CSV C:\usr\security.csv -Append

}

I believe, it could be useful for others.