Named Parameters

Listen to this blog post!

Table of contents:

In the first part of this series, we examined unnamed parameters and how special variables like $args expose them.

A much more user-friendly approach is to define a param() block at the beginning of a script block. This allows you to create named parameters, giving users IntelliSense support, tab completion, and the ability to define strongly typed parameters.

Unnamed Arguments…

When you use $args, as described in Part 1 of this series, the user has no way of knowing what information is required.

Here is a function that takes an IPv4 address and returns the owner registration information:

function Get-IpAddressOwner
{
  Invoke-RestMethod -Uri "http://ipinfo.io/$args/json" -UseBasicParsing |
    Select-Object -Property Ip, HostName, Org, Country, Region, Postal, City, TimeZone, Loc
}

This function works well, provided you know that you must specify exactly one IP address:

PS> Get-IpAddressOwner 8.8.8.8

ip       : 8.8.8.8  
hostname : dns.google  
org      : AS15169 Google LLC  
country  : US  
region   : California  
postal   : 94043  
city     : Mountain View  
timezone : America/Los_Angeles  
loc      : 38.0088,-122.1175

If you omit the argument, the function automatically provides information for your current public IP address. If you specify more than one IP address, the function fails.

…versus Named Arguments

The solution is to use named parameters instead of $args by adding a param() block that defines the parameter names:

function Get-IpAddressOwner
{
  param
  (
    $IPAddress = ''
  )
  
  Invoke-RestMethod -Uri "http://ipinfo.io/$IPAddress/json" -UseBasicParsing |
    Select-Object -Property Ip, HostName, Org, Country, Region, Postal, City, TimeZone, Loc
}

Now the user can view the available parameter(s) through IntelliSense or tab completion, making it immediately clear what input is required:

PS> Get-IpAddressOwner -IPAddress 124.122.221.91

ip       : 124.122.221.91  
hostname : duplo.ipconnect.de  
org      : Freusche Drählekom  
country  : DE  
region   : Enchanted Forest (West)  
postal   : 82443  
city     : Zumselhofen  
timezone : Europe/Berlin  
loc      : 50.3105,11.4322

If the user does not provide the argument, it uses the assigned default value — in this case, an empty string. This default value makes sense for this use case because the web service returns information about your own IP address when no specific IP address is provided.

By optionally adding constraints, you can require the user to provide specific types of data for a given parameter — for example, only valid IPv4 addresses:

param
(
  [ValidateScript({($_ -like '*.*.*.*') -and ($_ -as [System.Net.IPAddress])})]
  [string]
  $IPAddress = ''
)

Default values are not always appropriate: sometimes there is no meaningful default for a parameter. For example, here is a function that converts text to speech:

function Convert-Text2Voice
{
  param
  (
    [String]
    $Text = (Read-Host -Prompt 'Enter the text to speak') ,

    [ValidateRange(-10,10)]
    [int]
    $Speed = 0
  )
  
  Add-Type -AssemblyName System.speech

  [System.Speech.Synthesis.SpeechSynthesizer]$speak = 
  [System.Speech.Synthesis.SpeechSynthesizer]::new()
  $speak.Rate = $Speed
  $speak.Speak($Text)
  
  $speak.Dispose()
}

While there is a reasonable default value for voice speed (0 = normal speed), there is no appropriate default for the input text.

One simple way to make any parameter mandatory is to use Read-Host as its default value. In that case, the user is prompted when no argument is provided. In the next part of this series, we will explore more sophisticated approaches.

Conclusion

Defining named parameters adds consistency and allows assigning data types and validators. Most importantly, it makes arguments discoverable to the user. That’s why named parameters are a welcome replacement for $args.

For true consistency, named parameters should also be pipeline-aware. Only then can you fully eliminate all special variables — including $_ and $input — and treat parameters equally, whether they receive their values as direct arguments or via the pipeline.

That’s what we will explore in Part 3.

Related links