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.