powershellforeachget-wineventgetcontent

Where-Object Error When Passing Get-Content as Variable


First, my PS knowledge is very basic, so know that up front.

I'm working on a basic script to search EventIDs in archived .evtx files and kick out "reports". The Where-Object queries are in .txt files stored in .\AuditEvents\ folder. I'm trying to do a ForEach on the .txt files and pass each query to Get-WinEvent.

Here's an example of how the queries appear in the .txt files: {($_.ID -eq "11")}

The script is:

$ae = Get-ChildItem .\AuditEvents\

ForEach ($f in $ae) {
    $qs = Get-Content -Path .\AuditEvents\$f
    Get-WinEvent -Path .\AuditReview\*.evtx -MaxEvents 500 | Select-Object TimeCreated, ID, LogName, MachineName, ProviderName, LevelDisplayName, Message | Where-Object $qs | Out-GridView -Title $f.Name
    }

This is the error:

Where-Object : Cannot bind argument to parameter 'FilterScript' because it is null.
At C:\Users\######\Desktop\PSAuditReduction\PSAuditReduction.ps1:6 char:177
+ ... e, ProviderName, LevelDisplayName, Message | Where-Object $qs | Out-G ...
+                                                               ~~~
    + CategoryInfo          : InvalidData: (:) [Where-Object], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.WhereObjectCommand

Solution

  • Your symptom implies that $qs is $null, which in turn implies that file .\AuditEvents\$f is empty.

    However, even if it had content, you couldn't pass the resulting string as-is to the (positionally implied) -FilterScript parameter of Where-Object requires a script block ({ ... }).

    You must create a script block from the string explicitly, using [scriptblock]::Create().

    A simplified example:

    # Simulated input using a literal string instead of file input via Get-Content
    $qs = '{ 0 -eq $_ % 2 }'  # Sample filter: return $true for even numbers.
    
    # Remove the enclosing { and }, as they are NOT part of the code itself 
    # (they are only needed to define script-block *literals* in source code).
    # NOTE: If you control the query files, you can simplify them
    #       by omitting { and } to begin with, which makes this 
    #       -replace operation unnecessary. 
    $qs = $qs.Trim() -replace '^\{(.+)\}$', '$1'
    
    # Construct a script block from the string and pass it to Where-Object
    1..4 | Where-Object ([scriptblock]::Create($qs)) # -> 2, 4
    

    Note:


    Taking a step back:

    As Abraham Zinala points out, a much faster way to filter event-log entries is by using Get-WinEvent's -FilterHashtable parameter.

    This allows you to save hastable literals in your query files, which you can read directly into a hashtable with Import-PowerShellDataFile:

    # Create a file with a sample filter.
    '@{Path=".\AuditEvents\.*evtx";ID=11}' > sample.txt
    
    # Read the file into a hashtable...
    $hash = Import-PowerShellDataFile sample.txt
    
    # ... and pass it to Get-WinEvent
    Get-WinEvent -MaxEvents 500 -FilterHashtable $hash | ...