Question

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.

Was it helpful?

Solution 4

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

OTHER TIPS

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

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

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.

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

for passing along a variable number of named parameters

function L($Lambda){
   write-host "`nI'm calling $Lambda"
   write-host "`nWith parameters"; ft -InputObject $Args
   & $Lambda @Args
}

seems to work well with strange function names

function +Strange-Name($NotUsed,$Named1,$Named2){
   ls -filter $Named1 -Attributes $Named2
}

PS C:\>L +Strange-Name -Named1 *.txt -Named2 Archive

and exe files as well

PS C:\>L grep.exe ".*some text.*" *.txt

although it looks like you still need to watch out for injection

function inject($OrigFunction){
   write-host 'pre-run injection'
   & $OrigFunction @Args
   write-host 'post-run injection'
}

PS C:\>L inject +Strange-Name -Named1 *.txt -Named2 Archive
    function strdel($a,$b,$c) {
    return ($a.substring(0,$b)+$(substr $a $c $a.length))
}
function substr($a,$b,$c) {
    return $a.substring($b,($c-$b))
}

$string = "Bark in the woods"
$in = $(substr $(strdel $string 0 5) 0 2)
write-host $in

Where Function 'substr' called function 'strdel' as the $a paramater.

Functions from https://github.com/brandoncomputer/vds

How about:

function A{
Param($functionToCall)
    $res = Invoke-Command $functionToCall 
    Write-Host "I'm calling : $res"
}

function B{
    "Function B"
}

Function C{
    "Function C"
}

A -functionToCall ${function:C}

Path the function as value using ${function:...}. Invoke the function and save the results to $res.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top