Pass function as a parameter
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 theparam(...)
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.
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
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