How to pass a custom function inside a ForEach-Object -Parallel How to pass a custom function inside a ForEach-Object -Parallel powershell powershell

How to pass a custom function inside a ForEach-Object -Parallel


The solution isn't quite as straightforward as one would hope:

function CustomFunction {    Param ($A)    "[$A]"}# Get the function's definition *as a string*$funcDef = $function:CustomFunction.ToString()"Apple", "Banana", "Grape"  | ForEach-Object -Parallel {    # Define the function inside this thread...    $function:CustomFunction = $using:funcDef    # ... and call it.    CustomFunction $_}

Note: This answer contains an analogous solution for using a script block from the caller's scope in a ForEach-Object -Parallel script block.

  • This approach is necessary, because - aside from the current location (working directory) and environment variables (which apply process-wide) - the threads that ForEach-Object -Parallel creates do not see the caller's state, notably neither with respect to variables nor functions (and also not custom PS drives and imported modules).

    • Update: js2010's helpful answer shows a more straightforward solution that passes a System.Management.Automation.FunctionInfo, obtained via Get-Command, which can be invoked directly with &. The only caveat is that the original function should be side-effect-free, i.e. should operate solely based on parameter or pipeline inputs, without relying on the caller's state, notably its variables, as that could lead to thread-safety issues. The stringification technique above implicitly prevents any problematic references to the caller's state, because the function body is rebuilt in each thread's context.
  • As of PowerShell 7.1, an enhancement is being discussed on GitHub to support copying the caller's state to the threads on demand, which would make the caller's functions available.

Note that making do without the aux. $funcDef variable and trying to redefine the function with $function:CustomFunction = $using:function:CustomFunction is tempting, but $function:CustomFunction is a script block, and the use of script blocks with the $using: scope specifier is explicitly disallowed.

$function:CustomFunction is an instance of namespace variable notation, which allows you to both get a function (its body as a [scriptblock] instance) and to set (define) it, by assigning either a [scriptblock] or a string containing the function body.


I just figured out another way using get-command, which works with the call operator. $a ends up being a FunctionInfo object. EDIT: I'm told this isn't thread safe, but I don't understand why.

function hi { 'hi' }$a = get-command hi1..3 | foreach -parallel { & $using:a }hihihi