Passing Functions to Foreach -Parallel Loop

So recently I came across a problem where I needed to specify a function within a Foreach -Parallel loop in PowerShell 7.

The issue was that you cannot use function declared outside of the scope of the Foreach -Parallel loop when using a script block object being passed through to the Foreach command.

This created a bit of a problem as you may need access to a command you have already declared. Most people end up duplicating any function declarations into the Script block object, which can be a little messy and hard to read if they are a lot of functions to replicate.

An example of this is below:

#First we declare the function in the local scope
function Test-Function {
    Param (
        [string]$name
    )
    Write-Host "Test: $name"
}

$scriptblock = {
    #As we can't access the local function we are having to redeclare the function in the scriptblock object.

    function Test-Function {
        Param (
            [string]$name
        )
        Write-Host "Test: $name"
    }
}

$array = @(1,2,3)

$array | foreach -Parallel $scriptblock -Throttlelimit 3

Whilst this works it is messy and requires you to code the function(s) twice in your script. (Or more if using the functions in multiple different script blocks)


Switch to Inline Script Block

We can however achieve the same result with far less code. When using a Scriptblock object with a remote command we are unable to reference any locally declared variables. This means we have to declare the functions and variables in the script block object.

If we change from using a script block object to an inline script block, this will enable us to utilise the $using: variables in the remote command.


What is a USING variable?

Think of the $using: variable as a special prefix that allows us to use locally defined variables in remote commands, for instance, Invoke-Command, Start-Job and Foreach -Parallel.

The usage of the $using: variable is as follows:

$using:<variable>

So if I have a $name variable declared in my local scope, and want to pass this to the remote command I could access it via

$name = "James"

Remote-Command
{
    $using:name
}

This is great as it allows us to reference local variables without having to obtain/define them specifically inside the remote command.


$using: for a function

In our case, we want to reference a function. This is a little different but, have no fear this can also be done using $using:!

First, we need to get the function definition and save it into a variable. We will later use this definition to create a new function.

Let’s create a new test function:

function Test-Function 
{
    param(
        [string]$item
    )
    Write-Host "This is test number $item"
}

Then we need to grab the new function definition into a variable:

$testfunction = ${function:Test-Function}.ToString()

The above line queries the PowerShell Function PSDrive to retrieve the function from memory. This means any function that we have declared locally in the session will be available.

This command will get the Test-Function function from the Function PSDrive and save the definition as a text string for use later.


Example Code

Now we have the function saved into a variable we can use it in the remote command inline script block.

In our case, we are going to use a Foreach -Parallel loop with the previously created test function. We will create a small array to use in the loop.

The full example script is below:

function Test-Function {
    param (
        [string]$item
    )
    Write-Host "This is test number $item"
}

$testfunction = ${function:Test-Function}.ToString()

$array = @(1, 2, 3)

$array | Foreach -Parallel {
    ${function:Test-Function} = $using:testfunction
    Test-Function -item $_
}

You will see that inside the inline script block, we are declaring a new function using the function definition we obtained earlier.

${function:Test-Function} = $using:testfunction

This creates a new function inside the remote command inline script using the $using:testfunction variable as the definition.

Now we have created the variable inside the remote command script block we are now able to use the newly defined function.

Now with this small function, it hardly seems worth it, but if you have the need to load several large predefined scripts this can help immensely. If you imagine you have several functions with 100+ lines that you have to duplicate, you can see the advantage of this method.

Testing

I will now quickly run the script to show that the local function is accessible without any issue.

You will see that the script will run without issue, and the Test-Function runs and outputs 3 Write-Host lines as we would expect.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s