5

Let's take the classic first-order functions example:

function Get-MyName { "George" }

function Say-Hi([scriptblock]$to) {
  Write-Host ("Hi "+(& $to))
}

This works just fine:

Say-Hi { "Fred Flintstone" }

this does not:

Say-Hi Get-MyName

because Get-MyName is evaluated, not passed as a value itself. How do I pass Get-MyName as a value?

Lars Truijens
  • 42,837
  • 6
  • 126
  • 143
George Mauer
  • 117,483
  • 131
  • 382
  • 612

4 Answers4

10

You have to pass Get-Myname as a scriptblock, because that's how you've defined the variable type.

Say-Hi ${function:Get-MyName}
mjolinor
  • 66,130
  • 7
  • 114
  • 135
  • How is this different from `Say-Hi {Get-MyName}`? Is there a type other than `[scriptblock]` that would allow passing of named functions? – George Mauer Apr 12 '13 at 17:47
  • In this case, there is no difference, because the return value is the same as the scriptblock definition of the function. Which one are you wanting to pass, the scriptblock of the function or the return value from the invocation of that scriptblock? – mjolinor Apr 12 '13 at 17:53
  • I would like to pass the function itself, same as you can do in javascript, or any of the .net languages using delegates – George Mauer Apr 12 '13 at 17:58
  • OK. We just did that. The 'value' of a function is the content of it's script block. In this case the script block is just "George". – mjolinor Apr 12 '13 at 18:08
  • That's not quite true, suppose `Get-MyName` took parameters, in your version you would have to know all the parameters that it is invoked with and forward them manually, if you're passing the function directly (as you would in js or c#) its parameters are whatever it is invoked with from inside `Say-Hi` – George Mauer Apr 12 '13 at 18:13
  • Then (get-item function:get-myname) – mjolinor Apr 12 '13 at 18:27
  • Ah, I see, and then I would have to get rid of the `[scriptblock]` constraint and it should work – George Mauer Apr 12 '13 at 18:53
  • I've never tried to pass a function that way, but the object type that returns is [functioninfo]. The scriptblock is just a property of the [functioninfo] object. – mjolinor Apr 12 '13 at 19:03
3

If you are ready to sacrifice the [scriptblock] parameter type declaration then there is one more way, arguably the simplest to use and effective. Just remove [scriptblock] from the parameter (or replace it with [object]):

function Get-MyName { "George" }

function Say-Hi($to) {
  Write-Host ("Hi "+(& $to))
}

Say-Hi Get-MyName
Say-Hi { "George" }

So now $to can be a script block or a command name (not just a function but also alias, cmdlet, and script).

The only disadvantage is that the declaration of Say-Hi is not so self describing. And, of course, if you do not own the code and cannot change it then this is not applicable at all.

I wish PowerShell has a special type for this, see this suggestion. In that case function Say-Hi([command]$to) would be ideal.

Roman Kuzmin
  • 40,627
  • 11
  • 95
  • 117
3

This might be a better example to illustrate the question, and details of execution scope. @mjolinor's answer appears to work nicely for this use case:

function Get-MyName($name) { $name; throw "Meh" }

function Say-Hi([scriptblock]$to) {
  try {
      Write-Host ("Hi "+(& $to $args)) # pass all other args to scriptblock
  } catch {
      Write-Host "Well hello, $_ exception!"
  }
}

The command and its output:

PS C:\> Say-Hi ${function:Get-MyName} 'George'
Well hello, Meh exception

In particular, I'm using this pattern for wrapping functions that work with a flaky remote SQL Server database connection, which sleep, then retry several times before finally succeeding or throwing a higher exception.

Excalibur
  • 3,258
  • 2
  • 24
  • 32
1

Strictly based on your code, the correct answer is this:

Say-Hi {(Get-MyName)}

This will produce "Hi George"

SamDevx
  • 2,268
  • 4
  • 32
  • 47
  • Thanks! Do you have any insight as to the difference between this syntax and that by @mjolinor above? – George Mauer Jun 20 '14 at 22:14
  • As @SamDevx said "strictly based on your code", there is not much difference. But if the function has parameters then calling it with parameters in this way will not be straightforward. – Roman Kuzmin Jun 23 '14 at 11:35
  • @GeorgeMauer The difference is that here is passed a new block in which `Get-MyName` is called. In mjolinor's example there is a direct reference to the `Get-MyName`-function passed. (With parameters this variation could be written like `Say-Hi { Get-MyName @args }` whereas mjolinor's would work without modification.) – TNT Feb 13 '18 at 19:07