6

Suppose you define map_ps in map.ps1:

function map_ps{
    [CmdletBinding()]
    param([parameter(ValueFromPipeline=$true)]$InputObject,$sb,$ArgumentList)
    process{&$sb $ArgumentList}
}

Suppose you also define another function map_psm with identical implementation in a well-formed module called map.psm1:

function map_psm{
    [CmdletBinding()]
    param([parameter(ValueFromPipeline=$true)]$InputObject,$sb,$ArgumentList)
    process{&$sb $ArgumentList}
}

Calling each function with identical parameters does not yield the same result:

PS C:\> 1 | map_ps  -sb {"DollarBar:$_, Arg:$($args[0])"} -ArgumentList 2
DollarBar:1, Arg:2
PS C:\> 1 | map_psm -sb {"DollarBar:$_, Arg:$($args[0])"} -ArgumentList 2
DollarBar:, Arg:2

Why is $_ empty when the function is implement in a .psm1 but it isn't when the function is implemented in a .ps1?

alx9r
  • 3,675
  • 4
  • 26
  • 55

2 Answers2

5

Unless variable declared in global scope, functions/ScriptBlocks can not see variables declared in module different from its own module. As workaround, you can create ScriptBlocks thru [scriptblock]::Create, which create ScriptBlocks not bounded to any particular module:

function FunctionWithoutModule{
    [CmdletBinding()]
    param([parameter(ValueFromPipeline=$true)]$InputObject,$sb,$ArgumentList)
    process{
        $SomeVariable='SomeValue'
        &$sb $ArgumentList
    }
}
$Module=New-Module -ScriptBlock {
    function FunctionWithModule{
        [CmdletBinding()]
        param([parameter(ValueFromPipeline=$true)]$InputObject,$sb,$ArgumentList)
        process{
            $SomeVariable='SomeValue'
            &$sb $ArgumentList
        }
    }
}
$ScriptBlockWithoutModule={"DollarBar:$_, Arg:$($args[0]), SomeVariable:$SomeVariable"}
$ScriptBlockWithModule=$Module.NewBoundScriptBlock($ScriptBlockWithoutModule)
$ScriptBlockNotBoundToModule=[scriptblock]::Create($ScriptBlockWithoutModule)

1|FunctionWithoutModule -sb $ScriptBlockWithoutModule -ArgumentList 2
#DollarBar:1, Arg:2, SomeVariable:SomeValue
1|FunctionWithoutModule -sb $ScriptBlockWithModule -ArgumentList 2
#DollarBar:, Arg:2, SomeVariable:
1|FunctionWithoutModule -sb $ScriptBlockNotBoundToModule -ArgumentList 2
#DollarBar:1, Arg:2, SomeVariable:SomeValue
1|FunctionWithModule -sb $ScriptBlockWithoutModule -ArgumentList 2
#DollarBar:, Arg:2, SomeVariable:
1|FunctionWithModule -sb $ScriptBlockWithModule -ArgumentList 2
#DollarBar:1, Arg:2, SomeVariable:SomeValue
1|FunctionWithModule -sb $ScriptBlockNotBoundToModule -ArgumentList 2
#DollarBar:1, Arg:2, SomeVariable:SomeValue
user4003407
  • 21,204
  • 4
  • 50
  • 60
  • Thanks for this. I just tested it and the results are the same as your example even with the two functions inside a `.psm1`. – alx9r Feb 23 '15 at 18:49
  • Can you tell me how you learned this behavior? Testing? Do you have some documentation that you can tell me about? – alx9r Feb 23 '15 at 18:56
  • 2
    This behavior I find from testing. When PowerShell V2 with module concept arrived, I spend some time playing with it, even find some [bug](https://connect.microsoft.com/PowerShell/feedback/details/569606). And some part of knowledge was gotten from digging in with ILSpy. Even while answering this question, I actually find out that there is a difference between ScriptBlocks without module, and ScriptBlocks not bounded to any module (created thru `[scriptblock]::Create`). – user4003407 Feb 24 '15 at 05:59
2

I think this is a combination of Module scope and the scriptblock. Being in a module changes the way that local variables are used within the scriptblock ($_ is being used here inside a scriptblock to refer to a variable in the caller's scope).

Use GetNewClosure() on the Script Block

function map_psm{
    [CmdletBinding()]
    param([parameter(ValueFromPipeline=$true)]$InputObject,$sb,$ArgumentList)
    process{& $sb.GetNewClosure() $ArgumentList}
}

That should re-evaluate the scriptblock using the current value of the variables.

briantist
  • 45,546
  • 6
  • 82
  • 127
  • I just tested this. It does not work. With `GetNewClosure()` the output is still `DollarBar:, Arg:2`. – alx9r Feb 23 '15 at 18:28