Build Your Own PowerShell Cmdlet: Part 4 of 9

Build Your Own PowerShell Cmdlet: Part 4 of 9

  • Comments 6
  • Likes

Summary: Microsoft Windows PowerShell MVP, Sean Kearney, continues a series of guest blogs that detail how to build your own cmdlet.

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 4 of a nine-part series about building your own Windows PowerShell cmdlet. Read the entire series as it unfolds.

Here’s Sean…

Polishing the advanced function

So now what we have is an advanced function that sure feels like a cmdlet, doesn’t it? It almost is, but it needs a lot more polishing. Here are the steps we still need to complete.

  • Change parameters to accept data as an array

  • Add cmdlet binding to emulate compiled cmdlet

  • Set properties on parameters and possible validation

  • Insert Begin, Process, and End scriptblocks

  • Add Help content

Change those parameters to an array

When you’re working with a cmdlet, you’re working with the pipeline. Within the pipeline you may receive one object or a large array of them. In both cases you are dealing with an array of information, even if that array only contains one element.

To adjust the parameters as an array, we only need insert a pair of brackets into the variable. This identifies it as an array instead of as a single object.

In the case of our three parameters, they will switch from looking like this…

function global:ADD-LOGFILE{

PARAM(

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

[STRING]$Preface="Logfile",

[STRING]$Extension=".log"

)

…to the following, which allows them to receive data as an array instead of as single parameters.

function global:ADD-LOGFILE{

 

PARAM(

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

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

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

)

Enabling cmdlet binding and building on parameters

Cmdlet binding is enabled by adding a new piece before the Param block for your parameters. Cmdlet binding defines how your cmdlet and its parameters will work in the Windows PowerShell world. To enable the cmdlet binding, we simply make this change to our function.

function global:ADD-LOGFILE{

 

[CmdletBinding()]

PARAM(

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

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

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

)

Cmdlet binding will bind the parameters in the same fashion as compiled cmdlets. This means parameters that by default are available to all cmdlets are now available to your cmdlet.

With the cmdlet binding, you can also specify additional parameters, such as:

  • DefaultParameterSetName

  • SupportsShouldProcess

  • ConfirmImpact

DefaultParameterSetName

This is what it implies to be. It states which of your parameters should be assumed to be the default. For example, within Add-LogFile, we might want to have the folder name as the default parameter. To specify that we would like the folder to be the default parameter for our Add-LogFile cmdlet, we would make this change to the top of our advanced function.

function global:ADD-LOGFILE{

 

[CmdletBinding(

DefaultParameterSetName=”Folder”

)]

 

PARAM(

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

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

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

)

SupportsShouldProcess

This will allow you to leverage the WhatIf parameter. This is my favorite parameter. With this enabled, I can create a cmdlet that has the option to “say without doing” for the user.

Typically, “destructive cmdlets” (those that are intended to remove and cause actions that would be difficult to impossible to undo, such as removing a user in Active Directory) will support this. You can leverage this feature however you would like. It can even be used as part of the troubleshooting for your cmdlet.

We could enable this as part of our cmdlet by adding SupportsShouldProcess to CmdletBinding.

function global:ADDLOGFILE{

 

[CmdletBinding(

DefaultParameterSetName=”Folder”,

SupportsShouldProcess=$True

)]

 

PARAM(

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

[STRING]$Preface="Logfile",

[STRING]$Extension=".log"

)

To be able to leverage the SupportsShouldProcess parameter, you need to use the following block of script.

If ($PSCmdlet.ShouldProcess("Message")) { BlockofCode }

If the WhatIf parameter is supplied with the cmdlet, the contents of “Message” will be displayed, which should indicate what the code block would do. Otherwise, all the script within the parentheses will execute.

If we were to modify Add-LogFile to use SupportsShouldProcess with our line to create the log file, it would look like this:

If ($PSCmdlet.ShouldProcess("Creation of Logfile $Logfilename Successful")) { NEW-ITEM –Type File -path $Logfilename -Force | OUT-NULL }

Enable whatif

Now with the feature enabled, we can leverage the WhatIf parameter.

ADD-LOGFILE C:\NewFolder -whatif

The output to the Add-LogFile cmdlet that we are creating would now look like this with a controlling WhatIf parameter added.

Image of command output

ConfirmImpact

This enables your cmdlet to use the –Confirm parameter. This allows you to employ the ability to confirm before actions occur. The script that is required to leverage this feature is identical to SupportsShouldProcess.

ConfirmImpact is set to one of three values: Low, Medium, or High. It will leverage the value of the $ConfirmPreference variable. It will launch a confirmation before the action if it is equal to or greater than the variable or if the –Confirm parameter is supplied.

Our same cmdlet using the –Confirm parameter would look like this if we added this feature.

function global:ADDLOGFILE{

 

[CmdletBinding(

DefaultParameterSetName=”Folder”,

SupportsShouldProcess=$True,

ConfirmImpact=’High’

)]

 

PARAM(

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

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

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

)

Running Add-LogFile –Confirm with the same changes in the script would look like this.

Image of command output

Let’s save the script as addlogcmdlet.ps1 with our change of [CmdletBinding()] placed into it. We’ll run the new script and then execute Get-Help on the Add-LogFile cmdlet. See if you can catch the difference.

Here it was before the change:

Image of command output

And now after [CmdletBinding()] was added:

Image of command output

The difference is that now the common parameters are available. These are parameters that are available to all cmdlets. The common parameters are:

  • -Verbose

  • -Debug

  • -WarningAction

  • -WarningVariable

  • -ErrorAction

  • -ErrorVariable

  • -OutVariable

  • -OutBuffer

All of these options can be leveraged by your cmdlet, depending on how you would like to control the output. In all cases, if your cmdlet receives the parameter but has no script to recognize what it is meant for, it will do nothing.

With the Verbose parameter you can set a Boolean True or False as the property. When this parameter is active, data sent by the Write-Verbose cmdlet is viewable.

With the Debug parameter, you can set a Boolean True or False as the property. When this parameter is active, data sent by the Write-Debug cmdlet is viewable.

With the WarningAction parameter, you can override the settings contained within $WarningAction. When you send data with the Write-Warning cmdlet, you have the choice to be prompted for an action.

The WarningVariable allows you to store the data sent by Write-Warning into a variable for later retrieval.

With the ErrorAction parameter, you can override the settings contained within $ErrorAction. The actions of this parameter are similar to WarningAction except it is for the Write-Error cmdlet. When you send data with the Write-Error cmdlet, you can be prompted for an action. Again, the choice is yours.

Like its cousin, the ErrorVariable parameter allows you to store errors within a variable.

~Sean

Thanks Sean! This series is turning out great. I appreciate you hanging in there. Good stuff. Guest Blogger Week will continue tomorrow when Sean will talk more about building a cmdlet.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Where exactly in the script "If ($PSCmdlet.ShouldProcess("Creation of Logfile $Logfilename Successful")) { NEW-ITEM –Type File -path $Logfilename -Force | OUT-NULL } " should be added?

  • @PoSHV: You could well add this line of code after the "param" statement. It could act as the complete body of the function.

    @Sean: Batchman is back!!!

    Hi my friend, Sean!

    Good to "see" you again!!!

    I read through the first part of your series of blog entries here and just wanted to say "Hi!" to you :-))

    Well ... not only ... I have to admit ...

    Your explanation of the "DefaultParameterSetName" is quite ... let's say "hard to understand" ...

    You don't need a DefaultParameterSetName here at all and there is no parameter daclaration like:

    param(

    [Parameter(ParameterSetName="Folder")]

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

      ...

    )

    So there is no real effect of introducing a "DefaultParamterSet" here.

    But it might be too difficult to explain the reason of introducing this default.

    Anyway an example might help:

    If I got i right, this statement has been added to PS V2.0 in order to be able to allow for a "most appropriate behaviour" in case of ambiguous unnamed parameters!

    If we have:

    function f {

       # [CmdletBinding(DefaultParametersetName="Set2")]

       param(

           [Parameter(ParameterSetName="Set1",Position=0)] [string] $s,

           [Parameter(ParameterSetName="Set2",Position=0)] [string] $i

      )

      $PsCmdlet.ParameterSetName

    }

    the call:

    f 1

    will result in an error, because PS does't know if we want $s or $i to be set unless we use named parameters!

    The way out is to specify a default solution to this conflict:

    And if we uncomment the second line, it works because of the DefaultParameterSet

    Way cool ...

    Klaus.

  • So I'm having an issue (probably user-related) but whenever I change the Parameter for the folder to an array I have a syntax issue on my file path. In other words, if I modify this line:

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

    To become this line:

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

    Whenever I call the function, it responds with this:

    _______________________________________________________________________________________________

    PS I:\> ADD-LOGFILE

    ADD-LOGFILE : Error: C:\PowerShell\ Logfile _ 10-2-2012 _ 120659 .log exist

    s.

    At line:1 char:12

    + ADD-LOGFILE <<<<

       + CategoryInfo          : WriteError: (:) [Write-Error], WriteErrorException

       + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,ADD-LOGFILE

    C:\PowerShell

    \

    Logfile

    _

    10-2-2012

    _

    120659

    .log

    False

    _______________________________________________________________________________________________

    Any ideas on what I've managed to do wrong? It works as expected without that one change. To make sure I had things correct I also went back and copy/pasted.

  • I think i have same problem as Morimu36. No matter what parameters i give, it keep saying file already exists :(

  • I believe that all picks up tomorrow when "Begin, Process and End" blocks are used.   You'll find this really useful when you read the entire series end to end.  Almost like a Chapter from a book :)