Pass function as a parameter

33,283

Solution 1

Is this what you need?

function A{
    Param($functionToCall)
    Write-Host "I'm calling : $functionToCall"

    #access the function-object like this.. Ex. get the value of the StartPosition property
    (Get-Item "function:$functionToCall").ScriptBlock.StartPosition

}

function B{
    Write-Host "Function B"
}

Function C{
    write-host "Function C"
}


PS> a -functionToCall c

I'm calling : c


Content     : Function C{
                  write-host "Function C"
              }
Type        : Position
Start       : 307
Length      : 43
StartLine   : 14
StartColumn : 1
EndLine     : 16
EndColumn   : 2

Solution 2

I'm not sure this is the best, but:

function A{
    Param([scriptblock]$FunctionToCall)
    Write-Host "I'm calling $($FunctionToCall.Invoke(4))"
}

function B($x){
    Write-Output "Function B with $x"
}

Function C{
    Param($x)
    Write-Output "Function C with $x"
}

PS C:\WINDOWS\system32> A -FunctionToCall $function:B
I'm calling Function B with 4

PS C:\WINDOWS\system32> A -FunctionToCall $function:C
I'm calling Function C with 4

PS C:\WINDOWS\system32> A -FunctionToCall { Param($x) "Got $x" }
I'm calling Got x

Solution 3

Have you thought about passing a ScriptBlock as a parameter?

$scriptBlock = { Write-Host "This is a script block" }
Function f([ScriptBlock]$s) {
  Write-Host "Invoking ScriptBlock: "
  $s.Invoke()
}

PS C:\> f $scriptBlock
Invoking ScriptBlock:
This is a script block

Solution 4

If you really want to pass the name of a function, as a string: use &, the call operator, to invoke it:

function A {
  Param($functionToCall)
  # Note the need to enclose a command embedded in a string in $(...)
  Write-Host "I'm calling: $(& $functionToCall)"
}

Function C {
  "Function C"  # Note: Do NOT use Write-Host to output *data*.
}

A -functionToCall C

As for the need to use $(...) inside "...": see this answer, which explains PowerShell's string-expansion (string-interpolation) rules.

The above yields I'm calling: Function C

Note how function C uses implicit output (same as using Write-Output explicitly) to return a value.
Write-Host is generally the wrong tool to use, unless the intent is explicitly to write to the display only, bypassing PowerShell's output streams.

You generally need the & operator in the following scenarios:

  • To invoke a command by name or path, via a variable reference and/or if the name is single- or double-quoted.

  • To invoke a script block.

Script blocks are the preferred way of passing pieces of code around in PowerShell; the above could be rewritten as (note that the invocation mechanism doesn't change, just the argument being passed):

function A {
  Param($scriptBlockToCall)
  Write-Host "I'm calling: $(& $scriptBlockToCall)"
}

Function C {
  "Function C"  # Note: Do NOT use Write-Host to output *data*.
}

A -scriptBlockToCall { C }

In either scenario, to pass arguments, simply place them after: & <commandNameOrScriptBlock>; note how splatting (@<var>) is used to pass the unbound arguments stored in the automatic $args variable through.

function A {
  Param($commandNameOrScriptBlockToCall)
  Write-Host "I'm calling: $(& $commandNameOrScriptBlockToCall @Args)"
}

Function C {
  "Function C with args: $Args"
}


A -commandNameOrScriptBlockToCall C one two # by name
A -commandNameOrScriptBlockToCall { C @Args } one two # by script block

The above yields I'm calling: Function C with args: one two twice.

Note:

  • As JohnLBevan points out, the automatic $args variable is only available in simple (non-advanced) scripts and functions.

  • The use of a [CmdletBinding()] attribute above the param(...) block and/or a per-parameter [Parameter()] attribute is what makes a script or function an advanced one, and advanced scripts and functions additionally only accept arguments that bind to explicitly declared parameters.

  • If you need to use an advanced script or function - such as to support what-if functionality with [CmdletBinding(SupportsShouldProcess)] - you have the following choices for passing arguments through:

    • If it's sufficient to pass positional (unnamed) arguments through, declare a parameter such as [Parameter(ValueFromRemainingArguments)] $PassThruArgs, which implicitly collects all positional arguments passed on invocation.

    • Otherwise, you must explicitly declare parameters for all potential (named) pass-through arguments.

      • You can scaffold parameter declarations based on an existing command with the help of the PowerShell SDK, a technique used to create proxy (wrapper) functions, as shown in this answer.
    • Alternatively, your function could declare a single parameter that accepts a hashtable representing the named pass-through arguments, to be used with splatting; that, of course, requires the caller to explicitly construct such a hashtable.

Solution 5

Duncan's solution worked great for me. However I run into some issues when the function name had a dash in it.

I was able to get around it by building off his third example:

function A{
    Param([scriptblock]$functionToCall)
    Write-Host "I'm calling $($functionToCall.Invoke(4))"
}

function Execute-FunctionWithDash($x)
{
    Write-Output "Function Execute-FunctionWithDash with $x"
}

PS C:\WINDOWS\system32> A -functionToCall { Param($x) Execute-FunctionWithDash $x }
I'm calling Function Execute-FunctionWithDash with 4
Share:
33,283

Related videos on Youtube

woter324
Author by

woter324

Updated on July 09, 2022

Comments

  • woter324
    woter324 almost 2 years

    I've written function 'A' that will call one of a number of other functions. To save re-writing function 'A', I'd like to pass the function to be called as a parameter of function 'A'. For example:

    function A{
        Param($functionToCall)
        Write-Host "I'm calling : $functionToCall"
    }
    
    function B{
        Write-Host "Function B"
    }
    
    Function C{
        write-host "Function C"
    }
    
    A -functionToCall C
    

    Returns: I'm calling: C

    I am expecting it to return: I'm calling: Function C.

    I've tried various things such as:

    Param([scriptblock]$functionToCall)
    

    Cannot convert System.String to ScriptBlock

    A -functionToCall $function:C
    

    Returns "Write-Host "Function C"

    A - functionToCall (&C)
    

    This evaluates before the rest of it:

     Function C
     I'm Calling :
    

    I'm sure this is programming 101, but I can't work out the correct syntax or what it is I'm doing wrong.

  • Duncan
    Duncan about 10 years
    No, that is just passing the string "c" to the function, it isn't passing the function object C as a parameter.
  • Frode F.
    Frode F. about 10 years
    question was unclear. see updated answer. Using the scriptblock-property you could also invoke passed on function
  • woter324
    woter324 about 10 years
    Spot on. Thank you very much @Frode F. Much appreciated. So simple when you know how.
  • Duncan
    Duncan about 10 years
    It is still passing in the name of the function and then looking it up in the function namespace from inside A. That will probably work, but it precludes you just defining the function inline at the point of call.
  • Frode F.
    Frode F. about 10 years
    That's true. There's nothing in the code that verifies that the functionname exists atm. He could add a test to see if the get-item command returns anything, or try your approach(although that isn't a console-friendly to write)
  • Duncan
    Duncan about 10 years
    My approach isn't actually that unfriendly if you omit the spurious braces I had in it (I've removed them from my answer now).
  • woter324
    woter324 over 9 years
    What does "4" represent, as in .Invoke(4)? Thanks.
  • Duncan
    Duncan over 9 years
    Just demonstrating that you can pass arguments when you call the function
  • JohnLBevan
    JohnLBevan about 8 years
    (Get-Item "function:$functionToCall").ScriptBlock.InvokeWithContext($n‌​ull,$null) works; or invoke-command (Get-Item "function:$functionToCall").ScriptBlock
  • f0rt
    f0rt over 7 years
    This way you're passing $x but not the function which is undesired behavior.
  • Derek
    Derek over 7 years
    Actually the last line is calling function A and passes in a function that takes a parameter $x, calls Execute-FunctionWithDash, and passes parameter $x to it.
  • dwarfsoft
    dwarfsoft about 7 years
    Couldn't you have just wrapped it appropriately to account for the dash? A -functionToCall ${Function:Execute-FunctionWithDash}
  • nitzel
    nitzel over 6 years
    If one were to use the result of the scriptblock one might consider InvokeResultAsIs instead of Invoke, since Invoke seems to return a Collection - even if there's only a single object returned.
  • Jan Hohenheim
    Jan Hohenheim almost 6 years
    Functions with dashes can be accessed like this: ${Function:Do-SomethingCool}