Consider the following arbitrary function and test cases:
Function Foo-MyBar {
Param(
[Parameter(Mandatory=$false)]
[ScriptBlock] $Filter
)
if (!$Filter) {
$Filter = { $true }
}
#$Filter = $Filter.GetNewClosure()
Get-ChildItem "$env:SYSTEMROOT" | Where-Object $Filter
}
##################################
$private:pattern = 'T*'
Get-Help Foo-MyBar -Detailed
Write-Host "`n`nUnfiltered..."
Foo-MyBar
Write-Host "`n`nTest 1:. Piped through Where-Object..."
Foo-MyBar | Where-Object { $_.Name -ilike $private:pattern }
Write-Host "`n`nTest 2:. Supplied a naiive -Filter parameter"
Foo-MyBar -Filter { $_.Name -ilike $private:pattern }
In Test 1, we pipe the results of Foo-MyBar
through a Where-Object
filter, which compares the objects returned to a pattern contained in a private-scoped variable $private:pattern
. In this case, this correctly returns all the files/folders in C:\ which start with the letter T
.
In Test 2, we pass the same filtering script directly as a parameter to Foo-MyBar
. However, by the time Foo-MyBar
gets to running the filter, $private:pattern
is not in scope, and so this returns no items.
I understand why this is the case -- because the ScriptBlock passed to Foo-MyBar
is not a closure, so does not close over the $private:pattern
variable and that variable is lost.
I note from comments that I previously had a flawed third test, which tried to pass {...}.GetNewClosure(), but this does not close over private-scoped variables -- thanks @PetSerAl for helping me clarify that.
The question is, how does Where-Object
capture the value of $private:pattern
in Test 1, and how do we achieve the same behaviour in our own functions/cmdlets?
(Preferably without requiring the caller to have to know about closures, or know to pass their filter script as a closure.)
I note that, if I uncomment the $Filter = $Filter.GetNewClosure()
line inside Foo-MyBar
, then it never returns any results, because $private:pattern
is lost.
(As I said at the top, the function and parameter are arbitrary here, as a shortest-form reproduction of my real problem!)
GetNewClosure
does not captureprivate
variables. – Amphitropous$private:pattern
is being closed over, but I can't recreate in a fresh window. Question stands, though, given Test 1 still works as described. – AfforestRemove-Variable pattern
reset it -- I must have defined$pattern
at some point earlier. I'll update to remove reference to Test 3 – AfforestFoo-MyBar
in separate module. – Amphitropous