Pass arguments to a scriptblock in powershell
Asked Answered
S

8

67

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?

Shopper answered 2/5, 2013 at 20:33 Comment(3)
What will you do with your scriptblock? Use Invoke-Command or &?Deyo
If you plan to use & then you can do this: & { param($hello) $hello } -hello worldBisk
@LarsTruijens - I am planning to do the Invoke-Command -SessionShopper
D
69

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.

Deyo answered 3/5, 2013 at 16:57 Comment(0)
D
44

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
Delarosa answered 3/5, 2013 at 3:22 Comment(4)
Right, but don't closures traditionally capture variables as well? I'd assume that $servicePath above would be captured.Diaster
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.Delarosa
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).Remnant
Also see documentation for $args and about script-blocks.Janka
K
18

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

Kobold answered 19/5, 2020 at 19:57 Comment(1)
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.Assr
D
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
Desdee answered 25/5, 2017 at 20:12 Comment(0)
E
4

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
Excruciating answered 16/10, 2017 at 18:40 Comment(4)
Note that GetNewClosure() doesn't appear to work with Start-Job.Angloirish
@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?Excruciating
@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).Angloirish
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 }Rimple
M
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"
Miniaturize answered 5/11, 2020 at 14:20 Comment(0)
N
2

Other possibility:

$a ={ 
    param($p1, $p2)
    "p1 is $p1"
    "p2 is $p2"
    "rest of args: $args"
};
$a.invoke(1,2,3,4,5)
Natural answered 26/2, 2021 at 15:8 Comment(0)
J
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"
Judicature answered 7/2, 2017 at 18:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.