powershellfunctionnew-item

PowerShell - string expansion inside a dynamically created function body?


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

Solution

  • 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.