9

In powershell you can make functions with function name {commands} and make those functions take arguments with this:

function myFunction {
    param($var1, $var2)
}

but you can also accomplish this with

function myFunction($var1, $var2) {}

and they would be the same.

For example, if I made a function func1 be:

function func1 {
    param($var1, $var2)
    echo "$var1 $var2"
}

I would call it by using func1 1 2 where $var1 would be equal to 1 and $var2 would be equal to 2.

Input:

PS C:\Users\Neko> func1 1 2

Output:

1 2

However, if I do the same thing but instead I did the other method of passing arguments to functions:

function func2($var1, $var2) {
    echo "$var1 $var2"
}

I would also call it the same exact way, calling it by using func2 1 2 where $var1 would be equal to 1 and $var2 would be equal to 2 like the previous function.

Input:

PS C:\Users\Neko> func2 1 2

Output:

1 2

So everything seems the same and constant between the two renditions of the function, so my question is, is there a difference between the two methods of passing arguments to functions or are they both actually the same? Even if it is the most minor of details, or just a parsing difference, I would like to know any differences between the two in functions specifically since param has other uses as well.

UPDATE: The arguments you can do in param like [parameter(Mandatory=$true, ValueFromPipeline=$true)] and [String[]] are not unique to param. You can also accomplish this in the other 'non-param' example by doing:

function func2(
    [parameter(Mandatory=$true, ValueFromPipeline=$true, etc)] 
    [String[]]
    $var1, $var2
) {
    echo "$var1 $var2" 
}
Nico Nekoru
  • 2,840
  • 2
  • 17
  • 38
  • 3
    I don't believe there is a difference. Please note that when calling the function, do _not_ use parentheses and commas. You should call the function like: "func1 1 2" If you use "func1(1,2)", the parentheses are parsed as an array initializer with two elements, and this array is passed to $var1. Note for clarity you could also call like this "func1 -var1 1 -var2 2" – Mark Arend May 21 '20 at 22:02
  • It seems that there is far less documentation on the non-param alternative for passing arguments to functions, is param a more true-powershell solution? – Nico Nekoru May 21 '20 at 22:37
  • 2
    I think it's an issue of style. With Param, it seems to be clearer to write longer parameter lists with attributes, default values, etc. Can do the same in the inline-list but I think it's just a legacy style thing. – Mark Arend May 21 '20 at 22:51
  • 1
    And by the way func(1)(2) is legal but very unusual looking. Parentheses either specify ordering, compute a result or initialize an array. PowerShell calls typically expect spaces between arguments, which may have their names prepended like func -var1 1 -var2 2, or optional like func 1 2. I know it's weird looking especially coming from pretty much any other language. – Mark Arend May 21 '20 at 22:57
  • I guess I would still prefer ()() for arguments, it feels more natural I guess... – Nico Nekoru May 21 '20 at 22:58
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/214385/discussion-between-neko-musume-and-mark-arend). – Nico Nekoru May 22 '20 at 01:42

3 Answers3

10

To complement 7cc's helpful answer:

While the two syntax forms are mostly interchangeable when you define a function's parameters, only the param(...) block syntax works in the following circumstances:

  • If you want to use a [CmdletBinding()] attribute and its properties to (explicitly) make your function or script an advanced function or script.[1]

  • If you're writing a script file(*.ps1) or script block ({ ... }): the only way to declare parameters for them is is by placing a param(...) block at the beginning.

Therefore, you may opt to always use the param(...) block syntax, for consistency across function and script parameter definitions.

If a [CmdletBinding(...)]) attribute is used, it must directly precede the param(...) block.


As for:

I would call it by using func1(1)(2)

No, you would call it as follows:

func1 1 2

That is, PowerShell functions are called like shell commands: without parentheses, separated by whitespace; while your invocation happens to work too, the use of (...) around the arguments can change their interpretation:

  • without the enclosing (...) the arguments are parsed in argument mode, where, notably, strings needn't be quoted

  • with the enclosing (...), are parsed in expression mode, where strings do need to be quoted.

See this answer for more information.


[1] While you can place a [CmdletBinding(...)] attribute inside the parentheses with the function Foo (...) { ... } syntax without provoking an error, doing so is effectively ignored. Separately, in the absence of an (effective) explicit [CmdletBinding(...)] attribute, with either syntax, if you happen to decorate at least one parameter with a [Parameter()] attribute, you get the default behaviors of an advanced function (e.g., support for automatic common parameters such as -Verbose), because using [Parameter()] implicitly makes a function an advanced one (as if a [CmdletBinding()] attribute - without explicit property values - were in effect). However, if you need an explicit [CmdletBinding(...)] attribute, so as to opt into non-default advanced-function behaviors, via property values such as PositionalBinding=$false or SupportsShouldProcess=$true, use of a param(...) block is your only option.

mklement0
  • 382,024
  • 64
  • 607
  • 775
6

One thing is that the CmdletBinding attribute requires Param

function Echo-Confirm
{
    # Here
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
    Param ($val=1)

    if ($PSCmdlet.ShouldProcess($val) -eq $true) {
        Write-Output "Confirmed $val"
    }
}

Edit after this comment

The syntax is fine, but CmdletBinding has no effect

Function foo (
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
    [Parameter()]$val=1
) {
    # never confirm
    if ($PSCmdlet.ShouldProcess($val) -eq $true) {
        Write-Output "always here"
    }
    else {
       Write-Output "never here"
    }
}


foo -Confirm
# throws an error
foo: A parameter cannot be found that matches parameter name 'confirm'.
7cc
  • 1,149
  • 4
  • 10
3

From About Functions - Functions with Parameters - Named Parameters:

You can define any number of named parameters. You can include a default value for named parameters, as described later in this topic.

You can define parameters inside the braces using the Param keyword, as shown in the following sample syntax:

function <name> {
  param ([type]$parameter1[,[type]$parameter2])
  <statement list>
}

You can also define parameters outside the braces without the Param keyword, as shown in the following sample syntax:

function <name> [([type]$parameter1[,[type]$parameter2])] {
  <statement list>
}

While the first method is preferred, there is no difference between these two methods.

Edit

  1. @mklement0 thanks for helpful explication of the latter (emphasized) statement.
  2. This statement (there is no difference between these two methods) is valid despite of 7cc's improper guesswork.
  3. 7cc's answer is right, explained in mklement0's comments below and his updated answer.

In About Functions Advanced Parameters => Attributes of parameters, there are some allusions to the relation of CmdletBinding and Parameter attributes and advanced functions (advanced functions use the CmdletBinding attribute to identify them as functions that act similar to cmdlets):

… if you omit the CmdletBinding attribute, then to be recognized as an advanced function, the function must include the Parameter attribute…

… to be recognized as an advanced function, rather than a simple function, a function must have either the CmdletBinding attribute or the Parameter attribute, or both.

I can't comprehend PowerShell inventors' motivation for such (confusing for me) design…

JosefZ
  • 28,460
  • 5
  • 44
  • 83
  • 3
    It's just putting some attribute like `[CmdletBinding()]$foo = 1`, which does nothing. `$PSCmdlet.ShouldProcess` never works. – 7cc May 24 '20 at 05:44
  • 3
    @7cc is correct: While you can place a `[CmdletBinding()]` attribute inside the parentheses with the `function Foo (....)` syntax, it is _effectively ignored_. Separately, if you happen to decorate at least one parameter with a `[Parameter()]` attribute, you get the _default_ behaviors of an advanced functions (e.g, support for automatic common parameters such as `-Verbose`), because using `[Parameter()]` _implicitly_ makes a function an advanced one (it's like an implied, `[CmdletBinding()]` attribute, without property values). However, any _explicit_ use of `CmdletBinding` is ignored. – mklement0 May 24 '20 at 14:44
  • 2
    To put it differently: use of at least one `[Parameter()]` attribute alone (with either syntax form) implicitly gives you the _default_ behaviors of an advanced function. If you need non-default behaviors, such as `PositionalBinding=$false`, or `SupportsShouldProcess=$true`, your only option is to use the `param(...)` syntax. – mklement0 May 24 '20 at 19:36