ben
ben

Reputation: 1491

How do I pass a scriptblock as one of the parameters in start-job

I'm trying to create a background job, which executes a scriptblock. I need to pass this scriptblock in as a parameter, but I can't seem to get the syntax to work. The scriptblock is being converted to a string somewhere along the way.

It works fine when I pass the script block to a local function, but not through start-job

The following syntax works:

function LocalFunction
{
    param (
        [parameter(Mandatory=$true)]
        [ScriptBlock]$ScriptBlock
    )

    &$ScriptBlock | % { echo "got $_" }
}

LocalFunction -ScriptBlock { echo "hello" }

This outputs "got hello" as expected.

But the following fails:

$job = start-job -argumentlist { echo "hello" } -scriptblock {
    param (
        [parameter(Mandatory=$true)]
        [ScriptBlock]$ScriptBlock
    )
    &$ScriptBlock | % { echo "got $_" }
}
start-sleep -s 1
receive-job $job

The error it returns is

Receive-Job : Cannot process argument transformation on parameter 'ScriptBlock'. Cannot convert the " echo "hello" " value of type "System.String" to type "System.Management.Automation.ScriptBlock".

So if I'm reading the error right, it appears that -argumentlist is somehow forcing its arguments into strings.

Upvotes: 25

Views: 37171

Answers (5)

jrobiii
jrobiii

Reputation: 131

So if your desire is to insert an inline scriptblock, then Shay's solution (as noted) is probably the best. On the other hand if you simply want to pass a scriptblock as a parameter consider using a variable of type scriptblock and then passing that as the value of the -ScriptBlock parameter.

function LocalFunction
{
    param (
        [parameter(Mandatory=$true)]
        [ScriptBlock]$ScriptBlock
    )

    &$ScriptBlock | % { echo "got $_" }
}

[scriptblock]$sb = { echo "hello" }

LocalFunction -ScriptBlock $sb

Upvotes: 1

awright18
awright18

Reputation: 2333

Looks like this works today.

function LocalFunction
{
    param
    (
        [scriptblock] $block
    )

    $block.Invoke() | % { "got $_" }
}

LocalFunction { "Hello"} 

Upvotes: 6

Erich Cottom
Erich Cottom

Reputation: 11

You have to read it in as a string and then convert it to a scriptblock.

In powershell v1 you can do this:

$ScriptBlock = $executioncontext.invokecommand.NewScriptBlock($string)

And in powershell v2 you can do this:

$ScriptBlock = [scriptblock]::Create($string)

So your code would look like this:

function LocalFunction
{
    param (
        [parameter(Mandatory=$true)]
        $ScriptBlock
    )

    $sb = [scriptblock]::Create($ScriptBlock)

    $sb | % { echo "got $_" }
}

LocalFunction -ScriptBlock "echo 'hello'"

The '[scriptblock]::Create($ScriptBlock)' will place the curly braces around the string for you creating the script block.

Found the info here http://get-powershell.com/post/2008/12/15/ConvertTo-ScriptBlock.aspx

Upvotes: 1

James Kovacs
James Kovacs

Reputation: 11651

Based on my experiments, PowerShell is parsing -ArgumentList, which is an Object[], as a string, even when you pass in a script block. The following code:

$job = start-job -scriptblock { $args[0].GetType().FullName } -argumentlist { echo "hello" }
start-sleep -s 1
receive-job $job

results in the following output:

System.String

As far as I know, the only solution here is Shay's, though you don't need to pass in the -ArgumentList as a string as PowerShell will parse your script block as a string in this case.

Upvotes: 4

Shay Levy
Shay Levy

Reputation: 126722

Here's one way to solve this, pass the scriptblock code as a string, then create a scriptblock from the string inside the job and execute it

Start-Job -ArgumentList "write-host hello"  -scriptblock {

    param (
        [parameter(Mandatory=$true)][string]$ScriptBlock
    )

    & ([scriptblock]::Create($ScriptBlock))

} | Wait-Job | Receive-Job

Upvotes: 23

Related Questions