Reputation: 29934
It appears that PowerShell closures do not capture the definition of functions:
PS C:\> function x() { Write-Host 'original x' }
PS C:\> function x-caller-generator() { return { Write-host 'Calling x!'; x }.GetNewClosure() }
PS C:\> $y = x-caller-generator
PS C:\> & $y
Calling x!
original x
PS C:\> function x() { Write-Host 'new x' }
PS C:\> & $y
Calling x!
new x
Is there any way to capture the definition of a function?
What I'm actually experiencing is that I create a closure, but when my closure is executed, the function is out of scope somehow. (It's some strangeness the psake module for build scripting is doing.) Something like this:
PS C:\> function closure-maker () {
>> function x() { Write-Host 'x!' }
>>
>> return { Write-host 'Calling x'; x }.GetNewClosure()
>> }
>>
PS C:\> $y = closure-maker
PS C:\> & $y
Calling x
The term 'x' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:3 char:39
+ return { Write-host 'Calling x'; x <<<< }.GetNewClosure()
+ CategoryInfo : ObjectNotFound: (x:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
Note: Using PowerShell 2.0, but interested in 3.0 answers if there's something new.
Upvotes: 7
Views: 1473
Reputation: 186
A small correction will let the first example work:
clear
function x() { Write-Host 'original x' }
function x-caller-generator() {
$xFunc = $function:x; # instead of Get-Item
return { Write-host 'Calling x!'; & $xFunc }.GetNewClosure()
}
$y = x-caller-generator
& $y
function x() { Write-Host 'new x' }
& $y
Output:
Calling x!
original x
Calling x!
original x
PowerShell has too many looks alike which actually behave differently. You can get a function object by using $function prefix. One might think it works the same way as the Get-Item, but it does not...
Upvotes: 0
Reputation: 29934
Well, I found something that works for simple functions at least. We can use Get-Item
to get an object describing the function and then pull the original script off that. Like this:
function x-caller-generator() {
$xFunc = [ScriptBlock]::Create((Get-Item function:x).Definition)
return { Write-host 'Calling x!'; & $xFunc }.GetNewClosure()
}
If the function is never redefined (like in my example where my function is out of scope), we can avoid pulling off the definition and just use the function object directly:
function closure-maker () {
function x() { Write-Host 'x!' }
$xFunc = Get-Item function:x
return { Write-host 'Calling x'; & $xFunc }.GetNewClosure()
}
This second method will not work if the function is redefined (at least in the same scope as the original function) before the closure is executed. The object is apparently dynamic; it tracks the current definition.
I seriously doubt this would work with a function that references other user defined functions that could also be out of scope, but my use case didn't require that.
Sample output:
Creating script block
PS C:\> function x() { Write-Host 'original x' }
PS C:\> function x-caller-generator() { $xFunc = [ScriptBlock]::Create((Get-Item function:x).Definition); return { Write-host 'Calling x!'; & $xFunc }.GetNewClosure() }
PS C:\> $y = x-caller-generator
PS C:\> & $y
Calling x!
original x
PS C:\> function x() { Write-Host 'new x' }
PS C:\> & $y
Calling x!
original x
Using function object
PS C:\> function closure-maker () {
>> function x() { Write-Host 'x!' }
>>
>> $xFunc = Get-Item function:x
>> return { Write-host 'Calling x'; & $xFunc }.GetNewClosure()
>> }
>>
PS C:\> $y = closure-maker
PS C:\> & $y
Calling x
x!
Trying to use object with first example does not work:
PS C:\> function x() { Write-Host 'original x' }
PS C:\> function x-caller-generator() { $xFunc = Get-Item function:x; return { Write-host 'Calling x!'; & $xFunc }.GetNewClosure() }
PS C:\> $y = x-caller-generator
PS C:\> & $y
Calling x!
original x
PS C:\> function x() { Write-Host 'new x' }
PS C:\> & $y
Calling x!
new x
Upvotes: 5