Reputation: 699
I'm a Powershell beginner, although not a programming n00b. I'm trying to create an IDisposable/RAII-style failsafe pattern, sort of like in:
http://www.sbrickey.com/Tech/Blog/Post/IDisposable_in_PowerShell
So I have:
Function global:FailSafeGuard
{
param (
[parameter(Mandatory=$true)] [ScriptBlock] $execute,
[parameter(Mandatory=$true)] [ScriptBlock] $cleanup
)
Try { &$execute }
Finally { &$cleanup }
}
I'm trying to use it to perform a bunch of tasks in a different directory, using Push-Location on the way in and Pop-Location on the way out. So I have:
Function global:Push-Location-FailSafe
{
param (
$location,
[ScriptBlock] $execute
)
FailSafeGuard {
Push-Location $location;
&$execute
} { Pop-Location }
}
I find that the $execute param in Push-Location-FailSafe collides with the $execute param in the FailSafe function.
Push-Location-FailSafe "C:\" {dir}
The expression after '&' in a pipeline element produced an invalid object. It must result in a command name, script block or CommandInfo object.
At C:\TEMP\b807445c-1738-49ff-8109-18db972ab9e4.ps1:line:20 char:10
+ &$ <<<< execute
The reason I think it's a name-collision is that if I rename $execute to $execute2 in Push-Location-FailSafe, it works fine:
Push-Location-FailSafe "C:\" {dir}
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 2011-08-18 21:34 cygwin
d---- 2011-08-17 01:46 Dell
[snip]
What's wrong in my understanding of parameters?
Upvotes: 1
Views: 564
Reputation: 54911
Your problem is with scriptblocks and how they handle variables. Variables inside a scriptblock doesn't expand until they are executed. Because of this you are hitting a loop. Let me show you:
When you call Push-Location-Failsafe
method your variable is like this:
[DBG]: PS C:\>> (Get-Variable execute).Value
dir
But then you call your inner function FailSafeGuard
, your $execute
variable changes to this:
[DBG]: PS C:\>> (Get-Variable execute).Value
Push-Location $location;
& $execute
Now when you're try { }
block starts executing, it begins to expand the variables. When it expands $execute
it will get look like this:
Try {
Push-Location $location;
& $execute
}
Then it expands $execute
again. Your try block is now:
Try {
Push-Location $location;
& {
Push-Location $location;
& $execute
}
}
And you got yourself an infinite loop caused by recursion. To fix this, you can expand your $execute
variable inside a string, that you then create a scriptblock out of. Like this:
Function global:Push-Location-FailSafe
{
param (
$location,
[ScriptBlock] $execute
)
FailSafeGuard ([ScriptBlock]::Create("
Push-Location $location;
& $execute")) { Pop-Location }
}
Be aware that this particular solution will have a problem when $execute
includes variables inside. e.g.: $execute = { $h = dir }
because it will try to expand $h when it creates the scriptblock.
An easier and better way to solve it is just to use different variablenames so there's no collision in the first place :-)
Upvotes: 1