Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I can't find a way to pass the function. Just variables.

Any ideas without putting the function inside the ForEach loop?

function CustomFunction {
    Param (
        $A
    )
    Write-Host $A
}

$List = "Apple", "Banana", "Grape" 
$List | ForEach-Object -Parallel {
    Write-Host $using:CustomFunction $_
}

enter image description here

like image 892
smark91 Avatar asked Apr 17 '20 13:04

smark91


People also ask

Can I use forEach on object?

JavaScript's Array#forEach() function lets you iterate over an array, but not over an object. But you can iterate over a JavaScript object using forEach() if you transform the object into an array first, using Object. keys() , Object. values() , or Object.

Can you forEach inside forEach?

Due to operator precedence, you cannot put braces around the inner foreach loop. This is structured very much like the nested for loop. The outer foreach is iterating over the values in bvec , passing them to the inner foreach , which iterates over the values in avec for each value of bvec .

How do I change the value of a forEach?

The following code will change the values you desire: var arr = ["one","two","three"]; arr. forEach(function(part, index) { arr[index] = "four"; }); alert(arr);

How to ForEach Object in PowerShell?

PowerShell ForEach Object 1 ForEach-Object#N#[-InputObject <PSObject>] [-Begin <script block which runs before processing any input>] [-Process]... 2 ForEach-Object#N#[-InputObject <PSObject>] [-MemberName] <String>#N#[-ArgumentList <List of the argument object to... 3 ForEach-Object More ...

What is the use of ForEach-Object command?

This command allows us to perform something or to do something after all input processed by the command. -InputObject: It defines the object for input to the command ForEach-Object. Our ForEach-Object command will run the script block for every object input. -MemberName: It defines the property that we are going to use to get the method call.

What is the use of-inputobject and-membername in Python?

-InputObject: It defines the object for input to the command ForEach-Object. Our ForEach-Object command will run the script block for every object input. -MemberName: It defines the property that we are going to use to get the method call.

Is it possible to loop through a collection with ForEach-Object?

But that is only part of the confusion. For people trying to come to grips with looping through a collection, adding the Foreach-Object cmdlet into the mixture is just asking for trouble. Or asking for help. And that is what I am going to provide today; I'll attempt to demystify part of the equation.


2 Answers

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

# Sample custom function.
function Get-Custom {
  Param ($A)
  "[$A]"
}

# Get the function's definition *as a string*
$funcDef = ${function:Get-Custom}.ToString()

"Apple", "Banana", "Grape"  | ForEach-Object -Parallel {
  # Define the function inside this thread...
  ${function:Get-Custom} = $using:funcDef
  # ... and call it.
  Get-Custom $_
}

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

  • Note: If your function were defined in a module that is placed in one of the locations known to the module-autoloading feature, your function calls would work as-is with ForEach-Object -Parallel, without extra effort - but each thread would incur the cost of (implicitly) importing the module.

  • The above 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 instance, 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 in GitHub issue #12240 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:Get-Custom} = ${using:function:Get-Custom} is tempting, but ${function:Get-Custom} is a script block, and the use of script blocks with the $using: scope specifier is explicitly disallowed.

  • However, ${function:Get-Custom} = ${using:function:Get-Custom} would work with Start-Job; see this answer for an example.

  • It would also work with Start-ThreadJob, where you could even do & ${using:function:Get-Custom} $_, because ${using:function:Get-Custom} is preserved as a script block (unlike with Start-Job, where it is deserialized as a string, which is itself surprising behavior - see GitHub issue #11698). However, it is unclear whether this behavior is supported by design, because it is subject to the same potential cross-thread issues noted above.

${function:Get-Custom} 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.

like image 84
mklement0 Avatar answered Oct 10 '22 05:10

mklement0


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 hi
1..3 | foreach -parallel { & $using:a }

hi
hi
hi
like image 5
js2010 Avatar answered Oct 10 '22 05:10

js2010