Build Your Own PowerShell Cmdlet: Part 6 of 9

Microsoft Scripting Guy, Ed Wilson, is here. Guest blogger and Windows PowerShell MVP, Sean Kearney, has written a series about building cmdlets. For more about Sean, see his previous guest blog posts.

Note This is Part 6 of a nine-part series about building your own Windows PowerShell cmdlet. Read the entire series as it unfolds.

Here’s Sean…

Parameters within cmdlets

If you don’t enable [CmdletBinding()], you can still extend features of your advanced function as a cmdlet. This is done by preceding a parameter name or names with the argument [parameter()].

The additional properties you can send with [parameter()] are:

Mandatory

Position

ValueFromPipeline

ValueFromPipelineByPropertyName

ValueFromRemainingArguments

HelpMessage

Alias

Each of these properties has a purpose reflective of its name. This makes them quite easy to use and remember their purpose.

Mandatory

As you would have guessed, Mandatory means that this parameter absolutely must be in the pipeline. If it does not receive data, the cmdlet will prompt for the value. A good example of a mandatory parameter would be the path in Remove-Item. This cmdlet cannot function unless it knows what to remove. This would be mandatory for it to function.

If we want to make the $FolderName parameter mandatory in our cmdlet, we would modify it in the following fashion.

function global:ADD-LOGFILE{

[CmdletBinding(

DefaultParameterSetName=”Folder”,

SupportsShouldProcess=$True,

ConfirmImpact=’High’

)]

PARAM(

[parameter(Mandatory=$True)

[STRING[]]$Folder=”C:\PowerShell”,

[STRING[]]$Preface=”Logfile”,

[STRING[]]$Extension=”.log”

)

Position

Position is simpler to deal with. If you would like users to run your cmdlet without keying in the parameter names, you can specify positions for certain parameters. For example, even though I can execute the following cmdlet…

GET-CHILDITEM –literalpath C:\MyFolder

…I can equally execute this cmdlet:

GET-CHILDITEM C:\MyFolder

Why is that? Because the assigned position of the LiteralPath parameter is 1, as we can see from Get-Help.

With the position parameter, we can make it easier for users to execute cmdlets with parameters they might typically use. We can do the same thing to our current set of parameters.

By default, if you don’t define this, Windows Powershell will assign a position based on the order that you define the parameters. It is recommended as a best practice to always assign the position when defining the parameters. You’ll find things don’t break later on when you go to add additional parameters as you need changes.

function global:ADD-LOGFILE{

[CmdletBinding(

DefaultParameterSetName=”Folder”,

SupportsShouldProcess=$True,

ConfirmImpact=’High’

)]

PARAM(

[parameter(Mandatory=$True),

Position=0]

[STRING[]]$Folder=”C:\PowerShell”,

[parameter(Position=1)]

[STRING[]]$Preface=”Logfile”,

[parameter(Position=2)]

[STRING[]]$Extension=”.log”

)

ValueFromPipeline

You may or may not (because the design is completely in your hands) want to have a parameter accept data from the pipeline. This property is strictly a Boolean True or False setting. It specifies that a parameter can accept input from the pipeline. We can leverage this feature in our cmdlet by changing our parameters in the following manner.

function global:ADD-LOGFILE{

[CmdletBinding(

DefaultParameterSetName=”Folder”,

SupportsShouldProcess=$True,

ConfirmImpact=’High’

)]

PARAM(

[parameter(Mandatory=$True,

ValueFromPipeline=$True,

Position=0)]

[STRING[]]$Folder=”C:\PowerShell”,

[parameter(ValueFromPipeline=$True,

Position=1)]

[STRING[]]$Preface=”Logfile”,

[parameter(ValueFromPipeline=$True,

Position=1)]

[STRING[]]$Extension=”.log”

)

ValueFromPipelineByPropertyName

This is similar to the prior parameter, but it specifies the actual property name that the parameter can accept from the pipeline. A good example to work with is Get-ChildItem. When the Get-ChildItem cmdlet is run against the file system, it returns multiple properties such as FullName, Extension, DirectoryName, and LastAccessTime amongst others as shown in the following example.

If we want to ensure that a parameter only accepts data from the pipeline when the property name is Extension, we can make this change to a parameter. We can tell the Add-LogFile cmdlet that we are building to accept the Extension property if passed through the pipeline.

function global:ADD-LOGFILE{

[CmdletBinding(

DefaultParameterSetName=”Folder”,

SupportsShouldProcess=$True,

ConfirmImpact=’High’

)]

PARAM(

[parameter(Mandatory=$True,

ValueFromPipeline=$True,

Position=0)]

[STRING[]]$Folder=”C:\PowerShell”,

[parameter(ValueFromPipeline=$True,

Position=1)]

[STRING[]]$Preface=”Logfile”,

[parameter(ValueFromPipeline=$True,

Position=1,

ValueFromPipelineByPropertyName=$True)]

[STRING[]]$Extension=”.log”

)

ValueFromRemainingArguments

When you use this parameter, it identifies that any data coming down the pipeline that is not already assigned to other parameters will be accepted by this variable.

HelpMessage

There is nothing better for a user than being a little bit helpful. The Help message is a simple bit of text that you can provide to explain what this parameter is. In the case of the cmdlet we are building, it can be as simple as, “Folder to place log files in.” Here is a simple example of the HelpMessage put to use in our cmdlet.

function global:ADD-LOGFILE{

[CmdletBinding(

DefaultParameterSetName=”Folder”,

SupportsShouldProcess=$True,

ConfirmImpact=’High’

)]

PARAM(

[parameter(Mandatory=$True,

ValueFromPipeline=$True,

Position=0,

HelpMessage=’Folder to Store Logfiles in’)]

[STRING[]]$Folder=”C:\PowerShell”,

[parameter(ValueFromPipeline=$True,

Position=1,

HelpMessage=’TEXT to prepend all logfiles with’)]

[STRING[]]$Preface=”Logfile”,

[parameter(ValueFromPipeline=$True,

Position=1,

ValueFromPipelineByPropertyName=$True,

HelpMessage=’File Extension for Logfiles’)]

[STRING[]]$Extension=”.log”

)

If we ran our cmdlet now and did not provide any information for the Folder parameter (because it is a MandatoryParameter), we would see the Help message echoed in this fashion.

Our Folder parameter now has a simple line of text to explain its purpose if needed. As part of the features of the cmdlet, Windows Powershell allows the user to enter “!?” to pull up that simple line of Help.

Alias

An alias allows you to provide alternate names that a parameter can accept data with. The parameter name itself will not change; but in the case of AcceptValueByPropertyName, we can give a parameter multiple names to accept the data with automatically.

In the case of Folder, we could choose to use an alias of Path or Destination if that made more sense.

Validation Parameters

You can apply various attributes that allow you to indicate how values sent into the parameters should be dealt with or how they can be referred to alternate. Validation attributes are added like the other parameter attributes, and they can allow us to validate data being passed into the cmdlet or to specify what the cmdlet does not permit for certain parameters. The validation attributes that are available to us are:

AllowNull

AllowEmptyString

AllowEmptyCollection

ValidateCount

ValidateLength

ValidatePattern

ValidateRange

ValidateScript

ValidateSet

ValidateNotNull

ValidateNotNullOrEmpty

~Sean

Thank you, Sean. You absolutely ROCK! Guest Blogger Week will continue tomorrow when Sean will bring us Part 7.

Although this will likely never be edited, I have to point out that DefaultParameterSetName=”Folder” does absolutely nothing, because Sean forgot to define a Parameter Set. DefaultParameterSetName has nothing to do with Parameter variable names.

Also, is it best practice to set the Position parameter variable to 0? Doesn’t Sean point out an example of another cmdlet that starts at one? Microsoft does point out that the position numbers don’t need to be contiguous and can also start at 0, this appears to be confirmed by the fact that the shell output translates Position 0 into Position 1. It is technically correct, so we can let that slide.

On top of that, your code changes with no explanation (assuming it to be an honest mistake), because the Position parameter variables change from 0,1,2 the first go-around to 0,1,1 for the rest of the article. Again, seems like an honest mistake.

Also, (sorry to keep doing this) the statement “By default, if you don’t define this, Windows Powershell will assign a position based on the order that you define the parameters” appears to be contradicted by Microsoft Documentation, which states: “When the Position keyword is not specified, the cmdlet parameter must be referenced by its name.” This is also mirrored in other articles I’ve read.