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. Any help would be gratefully received. Many Thanks.
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
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
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
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 automatic variable $Args
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.
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