4

I normally do the following to invoke a script block containing $_:

$scriptBlock = { $_ <# do something with $_ here #> }
$theArg | ForEach-Object $scriptBlock

In effect, I am creating a pipeline which will give $_ its value (within the Foreach-Object function invocation).

However, when looking at the source code of the LINQ module, it defines and uses the following function to invoke the delegate:

# It is actually surprisingly difficult to write a function (in a module)
# that uses $_ in scriptblocks that it takes as parameters. This is a strange
# issue with scoping that seems to only matter when the function is a part
# of a module which has an isolated scope.
# 
# In the case of this code:
# 1..10 | Add-Ten { $_ + 10 }
#
# ... the function Add-Ten must jump through hoops in order to invoke the
# supplied scriptblock in such a way that $_ represents the current item
# in the pipeline.
#
# Which brings me to Invoke-ScriptBlock.
# This function takes a ScriptBlock as a parameter, and an object that will
# be supplied to the $_ variable. Since the $_ may already be defined in
# this scope, we need to store the old value, and restore it when we are done.
# Unfortunately this can only be done (to my knowledge) by hitting the
# internal api's with reflection. Not only is this an issue for performance,
# it is also fragile. Fortunately this appears to still work in PowerShell
# version 2 through 3 beta.
function Invoke-ScriptBlock {
[CmdletBinding()]

    param (
        [Parameter(Position=1,Mandatory=$true)]
        [ScriptBlock]$ScriptBlock,

        [Parameter(ValueFromPipeline=$true)]
        [Object]$InputObject
    )

    begin {
            # equivalent to calling $ScriptBlock.SessionState property:
            $SessionStateProperty = [ScriptBlock].GetProperty('SessionState',([System.Reflection.BindingFlags]'NonPublic,Instance'))
            $SessionState = $SessionStateProperty.GetValue($ScriptBlock, $null)
        }
    }
    process {
        $NewUnderBar = $InputObject
        $OldUnderBar = $SessionState.PSVariable.GetValue('_')
        try {
            $SessionState.PSVariable.Set('_', $NewUnderBar)
            $SessionState.InvokeCommand.InvokeScript($SessionState, $ScriptBlock, @())
        }
        finally {
            $SessionState.PSVariable.Set('_', $OldUnderBar)
        }
    }
}

This strikes me as a bit low-level. Is there a recommended, safe way of doing this?

4
  • 2
    Alternative is to add a param block on the fly: $ScriptBlock = [scriptblock]::Create("param(`$_)$ScriptBlock") Commented Mar 9, 2016 at 23:32
  • Mathias' comment should be an answer. This is exactly how I would have handled it. Commented Mar 10, 2016 at 0:30
  • 4
    How does this differ from $ScriptBlock.InvokeWithContext($null, (New-Object PSVariable '_',$value), $null) --- just asking. Commented Mar 10, 2016 at 13:36
  • 2
    blogs.msdn.com/b/sergey_babkins_blog/archive/2014/10/30/… Commented Mar 10, 2016 at 15:53

1 Answer 1

3

You can invoke scriptblocks with the ampersand. No need to use Foreach-Object.

$scriptblock = {## whatever}
& $scriptblock

@(1,2,3) | % { & {write-host $_}}

To pass parameters:

$scriptblock = {write-host $args[0]}
& $scriptblock 'test'

$scriptBlock = {param($NamedParam) write-host $NamedParam}
& $scriptBlock -NamedParam 'test'

If you're going to be using this inside of Invoke-Command, you could also usin the $using construct.

$test = 'test'
$scriptblock = {write-host $using:test}
Sign up to request clarification or add additional context in comments.

5 Comments

Please read the question again. I already know that. I am talking about special script-blocks which contain $_ variable.
I did read your question and tested it. Regardless of the scriptblock containing the pipeline variable, it works the same. I've updated the answer.
It will not work, if you have to jump between modules. link
On my computer, & { $_ } 10 returns nothing but & { param($x) $x } 10 correctly returns the value 10. I am asking about the recommended invoking of pipeline variable-ed ($_) script-blocks.
I see. You are asking about passing parameters into scriptblocks? That's a different story altogether. No need to downvote my answer based on your misunderstanding of the terminology. I've updated my answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.