PowerShell Not Your Father’s Command Line Part 8 of 31: Won’t You Take Me To Functiontown? (Part 2)

In my last post, I mentioned getting a little further with functions by covering the runtime life cycle. Now, let’s go a little further and explore some of the arguments of the Parameter attribute, some additional attributes that can be used, and finally the CmdletBinding attribute.

Arguments of the Parameter attribute

When working with parameters in advanced functions, mark them with the Parameter attribute. This tells PowerShell that this is an advanced function and needs to be treated as such. This attribute can take a variety of arguments, including:

  • Mandatory – specifies whether the parameter is mandatory. If omitted, the parameter is not mandatory. Note that you need to specify that this equals $true, as this is not like a SwitchParameter.
  • Position – specifies where the parameter appears when it is used in a command
  • Help message – used with mandatory parameters, this tells the person calling the command what should be entered. This is useless for optional parameters.

Other Attributes

There are other attributes besides the Parameter attribute that you can use while working with advanced functions. There are attributes out there for aliases, validation, and dynamic parameters as well. The Alias attribute allows those entering a command to use a shortened alias for a parameter.

In terms of validation, you can validate to make sure inputs aren’t null, match a pattern, are within a range, allow nulls, and many other things.

The following code sample shows the Parameter, ValidateNotNullOrEmpty, and ValidateLength attributes in action, as well as some of the arguments for the Parameter attribute.

function Get-MaskedString{
param(
    [parameter(Mandatory=$true, Position=0,
    HelpMessage="Enter the string to be masked")]
    [string]
    [ValidateNotNullOrEmpty()]         
    $StringToMask, 
       
    [parameter(Position=1)]
    [string]    
    [ValidateLength(1,2)]  
    $MaskCharacter = '*'
    )
       
    Write-Host ($MaskCharacter * $StringToMask.Length)
}

Some things to note with the above code:

  • The Position argument is used on both parameters to control where they appear in a command. However, since the second parameter isn’t mandatory, it may or may not appear as part of the command.
    This is a valid command:

    Get-MaskedString ‘cake’

    This is also a valid command:

    Get-MaskedString ‘cake’ ‘§’

  • Since the ValidateNotNullOrEmpty attribute is used, the command will throw an error if it is fed an empty string. It will look like this:
    ValidateNotNullOrEmpty() error message
  • Since the ValidateLength attribute is used, the command will throw an error if $MaskCharacter is present and longer than 2 characters. In this example, 1 is the minimum length, and 2 is the maximum length. It will look like this:
    ValidateLength() error message
    However, this only validates when the $MaskCharacter is present. Otherwise, this parameter has a default value of ‘*’ and will use that if it isn’t present.
  • The HelpMessage argument is specified. This appears when you run the command and let it prompt you for the parameters and use ‘!?’ for help in the console, like this:
    ValidateNotNullOrEmpty() error message

CmdletBinding attribute

The CmdletBinding attribute gives advanced functions access to some functionality that a compiled cmdlet has access to – including SupportsShouldProcess, ConfirmImpact, and DefaultParameterSetName. ConfirmImpact works with the ShouldProcess in determining the impact of an operation. When SupportsShouldProcess and ConfirmImpact are used, the -whatif and -confirm parameters are unlocked.

Learning More

Note that these are just a few of the attributes and features of the CmdletBinding attribute. There are plenty more things you can do in advanced functions and with these attributes. If you really want to dig into writing advanced functions, you will also want to reference the following help topics:

about_functions_advanced
about_functions_advanced_methods
about_functions_advanced_parameters
about_functions_cmdletbindingattribute

PowerShell Not Your Father’s Command Line Part 8 of 31: Won’t You Take Me To Functiontown?

Yesterday, Matt blogged about a basic PowerShell functions. I also have blogged in the past of some helpful functions that work with some of the PowerShell-specific providers. The code for those is fairly simple. However, functions aren’t always that simple; you can also write an advanced function, which runs similar to cmdlets.

Since advanced functions can be a large topic and some of this stuff overlaps with cmdlets, I am covering only a few of the topics in today’s posts.

Runtime Life Cycle
There are three phases that a function can go through:

  • Begin
  • Process
  • End

In PowerShell, Begin is run once, at the beginning of the function, before any pipeline objects are processed. Process is run for each object that is piped to that function. The automatic variable $_ is used for the individual pipeline object that is being processed during the Process phase. Once Process is finished, then End is run. Like Begin, End is run only once per function call.

If these keywords are left out, the advanced function’s normal processing behavior is treated like End.

Here is an example that uses all of these phases:

function Convert-FahrenheitToCelsius{
   begin{
      "Converting Fahrenheit input to Celsius:"
   }
   process {
      $degreesC = ($_ - 32)/1.8 
      "$_  degrees F = $degreesC degrees C"
   }
   end {
      "Done converting the input to Celsius"
   }
}

If I run the following command, I should get the following output.

Command

80, 32, 0  | Convert-FahrenheitToCelsius

Output

Converting Fahrenheit input to Celsius:
80  degrees F = 26.6666666666667 degrees C
32  degrees F = 0 degrees C
0  degrees F = -17.7777777777778 degrees C
Done converting the input to Celsius

Now since begin and end are only displaying strings and not handling the conversion, we can eliminate them. This would work just fine:

function Convert-FahrenheitToCelsius{
   process {
      $degreesC = ($_ - 32)/1.8 
      "$_  degrees F = $degreesC degrees C"
   }
}

Since the process keyword is present, the function will treat each of the pipeline objects individually. But what would it look like if we left off the process keyword?

function Convert-FahrenheitToCelsius{
   $degreesC = ($_ - 32)/1.8 
   "$_  degrees F = $degreesC degrees C"
}

The output for the sample command would be:

  degrees F = -17.7777777777778 degrees C

Notice that it didn’t treat the pipeline objects individually. This is probably not what you want, so if you’re going to pipe in objects that need to be treated individually, then the process keyword is needed.

This should give you an idea on the three phases that a function can go through. However, if you need more information on the runtime life cycle, you can find it by running:

Get-Help about_functions

You can also find out more about the Begin, Process, and End methods – in addition to many methids for confirmation, writing, error handling, and others – available to advanced functions by running:

Get-Help about_functions_advanced_methods

In the next post, later today, we will look at a few agruments of the Parameter attribute and mention the CmdletBinding attribute.