PowerShell – Pass FilterScript to Where-Object as variable

powershell

I'm trying to get a Where-Object FilterScript scriptblock into a variable, but I have had no luck far. As a simple example of what I'm trying to do:

$test = @('one','two','three')
$filter = '$_ -eq "one"'
$test | Where-Object -FilterScript { $filter }

This doesn't seem to work; I get back all elements. I've also tried:

$filter = { $_ -eq "one" }
$filter = [scriptblock]::Create($_ -eq 'one')

These all return:

one
two
three

However, replacing the variable with the actual filter works as expected:

$test | Where-Object -FilterScript { $_ -eq "one" }

This returns only "one", without "two" and "three". Does anyone know how I can make this work? I was originally trying it on Core v7.0.3, but tested on Windows PowerShell v5.1 as well.

Best Answer

The curly braces after -FilterScript are messing you up, once the variable contains a ScriptBlock you don't need those braces anymore. If you have them there, it's a scriptblock of a variable containing a scriptblock.

$foo = "hi"
$foo | gm     (TypeName: String)
{$foo} | gm   (TypeName: ScriptBlock)

If you add [scriptblock] to your $filter variable to strongly type the variable, it complains about the formatting until you add the braces when you define your variable. This is a great way to ensure the object you're feeding Where-Object matches the expected type for its -FilterScript parameter which is ScriptBlock

It will work without the strong type being forced once the braces are in the variable definition, which causes it to become a ScriptBlock, but I'd leave the type in there so the engine complains if somebody removes the braces and doesn't realize they're changing your variable type in the process.

$test = @('one','two','three')
[scriptblock]$filter = { $_ -eq "one" }
$test | Where-Object -FilterScript $filter
one