I'm trying to dynamically generate functions in PowerShell that are intended to act similarly to command aliases in bash etc.
In essence the functions are supposed to be generated from a string array that specify the alias names.
e.g.:
$commands = "build", "rebuild", "clean", "analyze"
I figured out how to dynamically create functions using 'New-Item -Path function: ..
' however I'm unable to get a variable inside the function body ("$funcname") to expand to its current value. Instead it always adds $funcname to the function body which then reads the value that remains in the variable at runtime later.
How can I force $funcname to expand to its value at declaration time of the function body?
Here is the example:
$commands = "build", "rebuild", "clean", "analyze"
foreach($cmd in $commands)
{
$funcname="$cmd"
$body = { echo "$funcname" @args } # instead of 'echo' a script will be called
New-Item -Path function: -Name "$funcname" -Value $body -Force
}
Output:
> build "test"
analyze
test
> rebuild "test"
analyze
test
...
This is the function items content and the reason why it behaves that way:
> $function:build
echo $funcname @args
PowerShell variables are dynamically scoped, meaning variable resolution is deferred until runtime - meaning the value of $funcname
, when evaluated, will always be the last value assigned to it (in this case "analyze"
).
To get around this, construct a closure:
$commands = "build", "rebuild", "clean", "analyze"
foreach($cmd in $commands)
{
$funcname="$cmd"
$body = { echo "$funcname" @args }.GetNewClosure()
New-Item -Path function: -Name "$funcname" -Value $body -Force
}
GetNewClosure()
will capture the value of $funcname
from the calling scope and store it in a dynamic module which is then attached to the scriptblock. When you later invoke the script block, the variable resolution routine will run into the dynamic-module-stored copy of $funcname
and resolve the expected value.