68

I guess you can't just do this:

  $servicePath = $args[0]

  if(Test-Path -path $servicePath) <-- does not throw in here

  $block = {

        write-host $servicePath -foreground "magenta"

        if((Test-Path -path $servicePath)) { <-- throws here.

              dowork 
        }
  }

So how can I pass my variables to the scriptblock $block?

3
  • What will you do with your scriptblock? Use Invoke-Command or &? Commented May 2, 2013 at 21:06
  • 7
    If you plan to use & then you can do this: & { param($hello) $hello } -hello world Commented May 2, 2013 at 21:09
  • @LarsTruijens - I am planning to do the Invoke-Command -Session Commented May 3, 2013 at 14:25

8 Answers 8

71

Keith's answer also works for Invoke-Command, with the limit that you can't use named parameters. The arguments should be set using the -ArgumentList parameter and should be comma separated.

$sb = {
    param($p1,$p2)
    $OFS=','
    "p1 is $p1, p2 is $p2, rest of args: $args"
}
Invoke-Command $sb -ArgumentList 1,2,3,4

Also see here and here.

Sign up to request clarification or add additional context in comments.

Comments

46

A scriptblock is just an anonymous function. You can use $args inside the scriptblock as well as declare a param block, for example

$sb = {
  param($p1, $p2)
  $OFS = ','
  "p1 is $p1, p2 is $p2, rest of args: $args"
}
& $sb 1 2 3 4
& $sb -p2 2 -p1 1 3 4

4 Comments

Right, but don't closures traditionally capture variables as well? I'd assume that $servicePath above would be captured.
Not in PowerShell. If you run the scriptblock in the current runspace, then yeah, those variables are picked up. But that is just a dynamic scoping feature. Try this with the scriptblock for Start-Job, where the scriptblock is serialized to another PowerShell process for execution and you will see no automatically captured variables.
If I wanted to run this same function as a new on-screen Powershell instance, is there any way to pass the arguments in? I'd have thought it would just be something like start powershell $sb(1,2).
20

For anyone reading in 2020 who wants to use local variables in a remote session script block, starting in Powershell 3.0 you can use local variables directly in the scriptblock with the "$Using" scope modifier. Example:

$MyLocalVariable = "C:\some_random_path\"
acl = Invoke-Command -ComputerName REMOTEPC -ScriptBlock {Get-Acl $Using:MyLocalVariable}

Found in example 9 of https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/invoke-command?view=powershell-7

1 Comment

I think this is the cleanest look, since our typical use-case for scriptblocks has variables set previously within the same function or script, not being passed in externally.
13

BTW, if using the script block to run in a separate thread (multi threaded):

$ScriptBlock = {
    param($AAA,$BBB) 
    return "AAA is $($AAA) and BBB is $($BBB)"
}

$AAA = "AAA"
$BBB = "BBB1234"    
$null = Start-Job $ScriptBlock -ArgumentList $AAA,$BBB

then yields:

$null = Start-Job $ScriptBlock -ArgumentList $AAA,$BBB    
Get-Job | Receive-Job
AAA is AAA and BBB is BBB1234

Comments

5

By default PowerShell won't capture variables for a ScriptBlock. You can explicitly capture by calling GetNewClosure() on it, however:

$servicePath = $args[0]

if(Test-Path -path $servicePath) <-- does not throw in here

$block = {

    write-host $servicePath -foreground "magenta"

    if((Test-Path -path $servicePath)) { <-- no longer throws here.

          dowork 
    }
}.GetNewClosure() <-- this makes it work

4 Comments

Note that GetNewClosure() doesn't appear to work with Start-Job.
@IanKemp Interesting, do you know any more as to why it doesn't? Does it fail as the original question describes, or with a different error?
@ChrisRDonnelly Seems that the variables' values simply don't get copied into the variables in the ScriptBlock, so those inside the block end up having the default value (null).
It does only work with Global variables. If you have a script block and another script block inside it, the variable defined inside first - parent script block will become Local and GetNewClosure() will not capture and make it available inside the second - child script block. Need to make it Global, e.g.: function parentScriptBlock(myParam) { myVar = 3; $Global:myVar = $myVar; $Global:myParam = $myParam; childScriptBlock = { echo $myParam }.GetNewClosure(); # use the script block here }
3

Three example syntax:

$a ={ 
  param($p1, $p2)
  "p1 is $p1"
  "p2 is $p2"
  "rest of args: $args"
}
//Syntax 1:
Invoke-Command $a -ArgumentList 1,2,3,4 //PS> "p1 is 1, p2 is 2, rest of args: 3 4"
//Syntax 2:
&$a -p2 2 -p1 1 3      //PS> "p1 is 1, p2 is 2, rest of args: 3"
//Syntax 3:
&$a 2 1 3              //PS> "p1 is 2, p2 is 1, rest of args: 3"

Comments

3

Other possibility:

$a ={ 
    param($p1, $p2)
    "p1 is $p1"
    "p2 is $p2"
    "rest of args: $args"
};
$a.invoke(1,2,3,4,5)

Comments

1

I know this article is a bit dated, but I wanted to throw this out as a possible alternative. Just a slight variation of the previous answers.

$foo = {
    param($arg)

    Write-Host "Hello $arg from Foo ScriptBlock" -ForegroundColor Yellow
}

$foo2 = {
    param($arg)

    Write-Host "Hello $arg from Foo2 ScriptBlock" -ForegroundColor Red
}


function Run-Foo([ScriptBlock] $cb, $fooArg){

    #fake getting the args to pass into callback... or it could be passed in...
    if(-not $fooArg) {
        $fooArg = "World" 
    }
    #invoke the callback function
    $cb.Invoke($fooArg);

    #rest of function code....
}

Clear-Host

Run-Foo -cb $foo 
Run-Foo -cb $foo 

Run-Foo -cb $foo2
Run-Foo -cb $foo2 -fooArg "Tim"

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.