1

I have a very basic PowerShell script:

Param(
    [string]$MyWord
)

function myfunc([string] $MyWord) {
    Write-Host "$MyWord"
}
myfunc @PSBoundParameters

This is how I execute it:

PS C:\> .\test.ps1 -MyWord 'hello'
hello

All fine. But I want to set a default value if -MyWord isn't specified. I tried this:

Param(
    [string]$MyWord='hi'
)

function myfunc([string] $MyWord) {
    Write-Host "$MyWord"
}
myfunc @PSBoundParameters 

But than the output of my script was just empty. It was printing nothing when I did not describe my parameter. (it only showed 'hello' if I specified the parameter). I also tried:

Param(
    [string]$MyWord
)

function myfunc([string] $MyWord) {
    [string]$MyWord='hi'
    Write-Host "$MyWord" 
}
myfunc @PSBoundParameters

But than the output was of course always 'hi' and never 'hello'. Even when I executed the script with the parameter -MyWord 'hello'

Can someone explaining what I'm doing wrong?

When I'm not using the function it is working as expected:

Param(
    [string]$MyWord='hi'
)
Write-Host $MyWord

Output:

PS C:\> .\test.ps1 -MyWord 'hallo'
hallo

PS C:\> .\test.ps1
hi
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
lvthillo
  • 28,263
  • 13
  • 94
  • 127
  • It's an interesting question; perhaps you could make it clearer by prefacing it with your intent (assuming I'm reading it correctly): You want your _script_ to _pass through_ to your _function_ all parameters that have a value, including parameters with _default values_. (The latter are, unfortunately, not reflected in `$PSBoundParameters`.) – mklement0 Mar 08 '17 at 19:21

4 Answers4

6

Automatic variable $PSBoundParameters, as the name suggests, contains only bound parameters, where bound means that an actual value was supplied by the caller.

Therefore, a parameter default value does not qualify as binding the associated parameter, so $MyWord with its default value of 'hi' does not become part of $PSBoundParameters.

Note: Arguably, a parameter with a default value should also be considered bound (it is bound by its default value, as opposed to by a caller-supplied value). Either way, it would be convenient to have an automatic variable that includes default values too, so as to enable simple and comprehensive passing through of arguments. A suggestion has been submitted to the PowerShell repository as GitHub issue #3285.


Workarounds

  • The following solutions assume that you want to pass the default value through, and don't want to simply duplicate the default value in function myfunc (as demonstrated in Ansgar Wiecher's helpful answer), because that creates a maintenance burden.

  • Regarding function syntax: The following two forms are equivalent (in this case), though you may prefer the latter for consistency and readability.[1]

    • function myfunc([string] $MyWord = 'hi') { ... }
      parameter declaration inside (...) after the function name.

    • function myfunc { param([string] $MyWord = 'hi') ... }
      parameter declaration inside a param(...) block inside the function body.


A simple fix would be to add the default value explicitly to $PSBoundParameters:

Param(
     [string]$MyWord = 'hi'
)

function myfunc ([string] $MyWord){
    Write-Host "$MyWord" 
}

# Add the $MyWord default value to PSBoundParameters.
# If $MyWord was actually bound, this is effectively a no-op.
$PSBoundParameters.MyWord = $MyWord

myfunc @PSBoundParameters

To achieve what you want generically, you must use reflection (introspection):

param(
  [alias('foop')]
  [string]$MyWord = 'hi'
)

function myfunc ([string] $MyWord) {
  Write-Host "$MyWord" 
}

# Add all unbound parameters that have default values.
foreach ($paramName in $MyInvocation.MyCommand.Parameters.Keys) {
  if (-not $PSBoundParameters.ContainsKey($paramName)) {
    $defaultVal = Get-Variable -Scope Local $paramName -ValueOnly
    # A default value is identified by either being non-$null or
    # by being a [switch] parameter that defaults to $true (which is bad practice).
    if (-not ($null -eq $defaultVal -or ($defaultVal -is [switch] -and -not $defaultVal))) {
      $PSBoundParameters[$paramName] = $defaultVal
    }
  }
}

myfunc @PSBoundParameters

[1] The param(...) form is required if you need to use the [CmdletBinding()] attribute with non-default values, as well as in scripts (.ps1). See this answer.

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

A parameter is bound only if you actually pass it a value, meaning that a parameter's default value does not show up in $PSBoundParameters. If you want to pass script parameters into a function, you must replicate the script parameter set in the function parameter set:

Param(
    [string]$MyWord = 'hi'
)

function myfunc([string]$MyWord = 'hi') {
    Write-Host "$MyWord" 
}

myfunc @PSBoundParameters

Maintaining something like this is easier if you define both parameter sets the same way, though, so I'd put the function parameter definition in a Param() block as well:

Param(
    [string]$MyWord = 'hi'
)

function myfunc {
    Param(
        [string]$MyWord = 'hi'
    )

    Write-Host "$MyWord" 
}
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
0

If you want to use "Param" enclose it in the function like this:

function myfunc {

    Param(
        [string]$MyWord='hi'
    )

    Write-Host "$MyWord" 
}
  • 1
    `param(...)` is being used correctly by the OP to declare parameter for the _script_. Inside a function, you get to _choose_ between the `param(...)` syntax, as in your answer, and the `myfunc([string] $MyWord)` variant chosen by the OP - makes no difference. _Duplicating_ the _script's_ default value for `$MyWord` inside the function is, I think, exactly what the OP wants to avoid. – mklement0 Mar 08 '17 at 18:04
0

Very simple way is,

function myfunc([string]$MyWord = "hi") {
Write-Output $MyWord
}
Mohan S
  • 31
  • 1
  • 2
  • The question is about passing the _script's_ default value through to the _function_, without needing to duplicate the default value in the function. But, yes, this is how you can define a default value for the function. – mklement0 Mar 10 '17 at 16:43