Powershell: Usual ScriptBlock scoping across module boundaries seems partially broken.

Created on 27 Mar 2017  路  2Comments  路  Source: PowerShell/PowerShell

Steps to reproduce

  • Create module.psm1 with content
Function With-Foo {
    param([ScriptBlock]$fn)
    $foo = 4
    &$fn
}
Export-ModuleMember -Function *
  • Create main.ps1 with content
Import-Module ".../module.psm1"
With-Foo { Write-Host $foo }
  • run main.ps1

Expected behavior

4

Actual behavior

<nothing>

Additional Info

  • If the function is defined in the same module as it is called, it works as expected.
  • If &$fn is replaced with &([ScriptBlock]::Create($fn.ToString())) the code also works across modules.

Environment data

> $PSVersionTable
Name                           Value
----                           -----
PSRemotingProtocolVersion      2.3
PSVersion                      6.0.0-alpha
GitCommitId                    v6.0.0-alpha.17
WSManStackVersion              3.0
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
PSEdition                      Core
CLRVersion
BuildVersion                   3.0.0.0
SerializationVersion           1.1.0.1
Issue-Discussion Resolution-Answered

Most helpful comment

A script block is associated with the SessionState that it was created in, and it will execute in that same SessionState, because the script block is very likely depending on the state (functions, variables and etc) of the SessionState it was created in.

In your example, With-Foo will run in the module SessionState while {write-host $foo} is defined in a different SessionState, and thus it won't be able to see the variables defined in the module SessionState when running within With-Foo.

Here is an example to demonstrate that:

PS:1> $m = New-Module -ScriptBlock {
>>     Function With-Foo {
>>         param([ScriptBlock]$fn)
>>         $foo = 4
>>         &$fn
>>     }
>> }
PS:2> & { $foo = 100; With-Foo { Write-Host $foo } }
100
PS:3>

To achieve what you expect, you can define With-Foo like this:

$m = New-Module -ScriptBlock {
    Function With-Foo {
        param([ScriptBlock]$fn)
        $foo = 4
        $newfn = $fn.Ast.GetScriptBlock() # create a new script block via the AST
        & $newfn
    }
}

Then you will get what you expect:

PS:1> $m = New-Module -ScriptBlock {
>>     Function With-Foo {
>>         param([ScriptBlock]$fn)
>>         $foo = 4
>>         $newfn = $fn.Ast.GetScriptBlock() # create a new script block via the AST
>>         & $newfn
>>     }
>> }
PS:2>
PS:2> With-Foo { Write-Host $foo }
4
PS:3>

All 2 comments

To clarify, the same issue occurs when using modules defined via New-Module.

$m = New-Module -ScriptBlock {
    Function With-Foo {
        param([ScriptBlock]$fn)
        $foo = 4
        &$fn
    }
}

With-Foo { Write-Host $foo }

will not output anything.

A script block is associated with the SessionState that it was created in, and it will execute in that same SessionState, because the script block is very likely depending on the state (functions, variables and etc) of the SessionState it was created in.

In your example, With-Foo will run in the module SessionState while {write-host $foo} is defined in a different SessionState, and thus it won't be able to see the variables defined in the module SessionState when running within With-Foo.

Here is an example to demonstrate that:

PS:1> $m = New-Module -ScriptBlock {
>>     Function With-Foo {
>>         param([ScriptBlock]$fn)
>>         $foo = 4
>>         &$fn
>>     }
>> }
PS:2> & { $foo = 100; With-Foo { Write-Host $foo } }
100
PS:3>

To achieve what you expect, you can define With-Foo like this:

$m = New-Module -ScriptBlock {
    Function With-Foo {
        param([ScriptBlock]$fn)
        $foo = 4
        $newfn = $fn.Ast.GetScriptBlock() # create a new script block via the AST
        & $newfn
    }
}

Then you will get what you expect:

PS:1> $m = New-Module -ScriptBlock {
>>     Function With-Foo {
>>         param([ScriptBlock]$fn)
>>         $foo = 4
>>         $newfn = $fn.Ast.GetScriptBlock() # create a new script block via the AST
>>         & $newfn
>>     }
>> }
PS:2>
PS:2> With-Foo { Write-Host $foo }
4
PS:3>
Was this page helpful?
0 / 5 - 0 ratings