1

I have a complex PowerShell function which I would like to run another threads.

However, If I'm right, the function cannot be accessed in the scriptblock. I want to avoid to copy every related function next to it.

Is there any way or method to call the function in the scriptblock?

function Function1 {
    Param()
    Process {
        $param1 = "something"
        $pool = [RunspaceFactory]::CreateRunspacePool(1, [int]$env:NUMBER_OF_PROCESSORS + 1)
        $pool.ApartmentState = "MTA"
        $pool.Open()
        $runspaces = @()

        $scriptblock = {
            Param (
                [Object] [Parameter(Mandatory = $true)] $param1
            )
            Complex_Function -param1 $param1
        }

        1..10 | ForEach-Object {
            $runspace = [PowerShell]::Create()
            $null = $runspace.AddScript($scriptblock)
            $null = $runspace.AddArgument($param1)
            $runspace.RunspacePool = $pool
            $runspaces += [PSCustomObject]@{ Pipe = $runspace; Status = $runspace.BeginInvoke() }
        }

        while ($runspaces.Status -ne $null) {
            $completed = $runspaces | Where-Object { $_.Status.IsCompleted -eq $true }
            foreach ($runspace in $completed) {
                $runspace.Pipe.EndInvoke($runspace.Status)
                $runspace.Status = $null
            }
        }

        $pool.Close()
        $pool.Dispose()
    }
}

function Complex_Function {
    Param(
        [Object] [Parameter(Mandatory = $true)] $param1
    )
    Process {
        #several function calls 
    }
}
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
tomas
  • 67
  • 1
  • 9
  • You could pass the `complex_function` itself to your scriptblock. You could give it a try: https://stackoverflow.com/questions/22129621/powershell-pass-function-as-a-parameter – Clijsters Aug 13 '18 at 09:11
  • How can I pass it with AddArgument function? It also has some arguments as well. – tomas Aug 13 '18 at 09:29
  • 1
    I think you can using something like this `Invoke-Command -ScriptBlock ${Function:Complex_Function} -ArgumentList 'arg1','arg2'`. I found a blog about this [here](http://duffney.io/RunLocalFunctionsRemotely) – Theo Aug 13 '18 at 10:19

1 Answers1

2

I think the code found in this blog post is probably what you're looking for:

Function ConvertTo-Hex {
    Param([int]$Number)
    '0x{0:x}' -f $Number
}

#Get body of function
$Definition = Get-Content Function:\ConvertTo-Hex -ErrorAction Stop

#Create a sessionstate function entry
$SessionStateFunction = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry 
    -ArgumentList 'ConvertTo-Hex', $Definition

#Create a SessionStateFunction

$InitialSessionState.Commands.Add($SessionStateFunction)

 #Create the runspacepool by adding the sessionstate with the custom function

$RunspacePool = [runspacefactory]::CreateRunspacePool(1,5,$InitialSessionState,$Host)

Do something similar with your Complex_Function and (I guess) every other function you need and they should be usable by your runspaces.

edit You asked in comments how to gather all functions. The path function:/ can be traversed and searched like a directory, so get-chiditem function:/ gets all currently-defined functions.

In experimenting with this, it seems as though functions that are defined within the current script, or from dot-sourced scripts, have an empty Source property. Play around with this. It should lead to what you want.

$InitialSessionState = [initialsessionstate]::Create()

Get-ChildItem function:/ | Where-Object Source -like "" | ForEach-Object {
    $functionDefinition = Get-Content "Function:\$($_.Name)"
    $sessionStateFunction = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry `
        -ArgumentList $_.Name, $functionDefinition 
    $InitialSessionState.Commands.Add($sessionStateFunction)
}
veefu
  • 2,820
  • 1
  • 19
  • 29
  • Thank you, it is working! Do you know how can I load everything into the session? I have a lots of looped functions and it can take a long time to import everything. – tomas Aug 13 '18 at 12:03
  • 1
    @tomas I updated the answer with something you can experiment with. I'm not sure if there's an easy way to get *everything*. – veefu Aug 13 '18 at 14:03
  • Thanks again. I also started to write a very similar code like this but unfortunately something is missing (function or module) and the process exit immediately. However I found that there is a `$pool.InitialSessionState.ImportPSModule` function which can use to import another module (even the current one) into a runspace. Maybe it can be the solution. – tomas Aug 13 '18 at 14:30