Sunday, January 28, 2007

PowerShell: Designing a CmdLet

Introduction

Since the early PowerShell beta days I have been excited about how PowerShell allows us to develop and add to its richness.  By adding custom CmdLets to the PowerShell shell we can add functionality to support third party products, legacy systems or just add basic functionality to simplify common administrative tasks.

Since the release of the first PowerShell SDK I have been considering the usefulness of a group of CmdLets that would abstract common administrative objects such as “user” and “computer”.  Here I am going to record, at some basic level of detail, some of the issues I have been presented with while attempting to come up with a final design for a CmdLet that abstracts a computer.

Let me begin by noting that I am not a PowerShell Guru and I have only limited experience with building CmdLets.  My reason for doing this is twofold.  First, I want to explore the limits and behaviors of a CmdLet, and second, I would like to document as much of my experience as possible so others, primarily administrator types, can benefit.

This series of short articles is directed at Windows Administrators who may find a need to build a custom CmdLet to solve some problem they are facing or to simplify some repetitive task that is not easily solved via functions or PS scripts.

I am not addressing these articles at full time PowerShell CmdLet developers.  Developers are usually very much at home with the NET classes and the Windows APIs.  Much of what I will post here assumes a limited knowledge of these. Administrators generally have good knowledge about the physical and structural components of their systems but may have only a basic knowledge of the underlying details of the systems software.  I will endeavor to keep  the technical programming issues out of the discussion except wher they are needed for clarity.  In many instances I may only provide a link to external resources that can be used to furthur understand the issue.

Behavior

How should our Get_Computer CmdLet behave?

All design should start with a requirement or list of requirements.  The first step of the design process, in my view, is to “vet” this requirements list.

My requirements list is an attempt to provide a single object that summarizes most of the common functions and information sets that an administrator needs for managing computers in a domain.  Minimally these would be service, process, accounts, sessions, software, join/rejoin, reboot, shutdown, etc.

Initially I would like my CmdLet to do the following:

1.      Take input from a file.

2.      Take input from a collection of objects

3.      Take input from a string

4.      Take input from a list of strings

5.      Take input from the pipeline

6.      Output a custom object of type “Computer”

7.      Provide direct access to instances of the “most used” WMI classes.

8.      Provide access Active Directory computer information.

9.      Provide frequently used “action” methods as direct methods on the Computer object

10.  Ease in adding new functionality in future releases.

 

This looks like a small list.  I have already done some building and testing with many of its elements and have found that it is not small at all.  When trying to pull all of this functionality into a CmdLet many questions bubble to the surface.  I will try and tackle most of these questions in print here.

The biggest issue I face with drawing all of this together is what the Computer object exposes in the way of properties and methods.  I have tried to approach this by modeling how the CmdLet would be used from the command line and from the pipeline.  In order to understand how the CmdLet should behave I have had to consider the differences between behavior from the command line and behavior in the pipeline.

Behavior of a CmdLet from the Command Line

From the command line a CmdLet returns an object or a collection of objects. THe information returned to the command line is generally formatted to display what is considered most useful.  The exact nature of the default display of the objects properties can be externally controlled or may be modified by switches defined by the CmdLet.  Simple command line use of a CmdLet is generally set up for direct viewing of output by the user.

The properties and switches for my CmdLet should allow the user to get usable information by typing only a minimal command line.

What happens when the user types “Get_Computer” with no arguments?

What should the input type of the default argument be? In this case it should probably be a simple string with the computer name; but is it that easy?  What is a computer name anyway?  Is it the NetBIOS name, the DNS name, the IP address…?

We will need to provide mechanisms for addressing all of these variations without the user having to know an exact definition for a computer name.

What switches will the CmdLet process specifically from the command line and will they have a different behavior in the pipeline? 

Assume we have a switch, “ping”, which cause the computer to be pinged when the computer object is created.  What will this ping do from the command line and from the pipeline? This is the kind of question that needs to be answered in order to correctly support command line usage of a CmdLet.

Behavior of a CmdLet in the pipeline

When the Get_Computer CmdLet is run as part of a pipeline it’s behavior needs to be very well defined. The behavior should be adjustable through the use of custom switches to tailor the input and output objects to their use in the pipeline.  In order to do this we need to consider the relationships between the types of input objects allowed from the pipeline and the possible uses of the output objects downstream in the pipeline.

Behavior in the pipeline will also require careful attention to how exceptions and anomalous conditions are handled by the CmdLet.  We need to assume that the CmdLet will be used arbitrarily in any pipeline that it doesn’t reject and that the CmdLet does not lie to downstream pipeline members or misinterpret upstream objects.  Failure to handle these issues could lead an administrator into deleting or removing information that they didn’t intend to alter.

Let’s begin the design!

In the next installment I will discuss some of the initial properties and the bootstrap behavior of "Get-CmdLet".  Getting the first few components defined will move us towards a test rig for “proof-of-concept” testing which we will use for the remainder of the design process.

It may seem that this is all a bit complicated but it’s not.  The wordiness is driven by a need to show to some level of detail the issues that have to be considered.  In practice much of this is done quickly.  Many of these issues may not come up at all if the CmdLet is a simple command line only utility like a game or a lookup.  For non-utility CmdLets much of the above applies selectively based on whether the CmdLet is used for altering the pipeline or just filtering it.  The Get-Computer CmdLet, at this time, is only going to produce objects that can be manipulated by the pipeline for basic purposes like reporting and restarting computers and services.  This will reduce the number of issues that we will initially have to deal with.  In the future I may extend the CmdLet design to be more radical. 

Windows PowerShell : Working With WMI Events

 Jeffrey Snover has blogged another good PowerShell function (link below) which has gotten me to thinking about PoSH and CmdLets from a slightly different direction.

Jeffrey's function shows how easy it is to extend PowerShell without building CmdLets.  This function parks an event watcher on a WMI event class and returns objects to the pipeline or command line.

The great difference between functions and CmdLets is that CmdLets give you very powerful control over the behavior of the CmdLet through the use of switches,  CmdLets can also help us dig deeper into the NET classes and quickly expose information at a higher and more convenient level.

If a function gives you all you need for your work then I suggest the function is the best approach.  When the function becomes to complex or cannot readily grab the information needed or if you need much better error control then is when a CmdLet can come to the rescue.

Check out Jeff's nifty "Event Watcher".

Link to Windows PowerShell : Working With WMI Events

Tuesday, January 23, 2007

BS on PoSH has a PowerShell Information Center

BSonPosh has assembled a pretty good collecion of information layed out in a very usable way at http://bsonposh.com/modules/wordpress/?page_id=13 Check it out.

Sunday, January 21, 2007

PowerShell: Some thoughts on CmdLet design

Some food for thought on designing and building PowerShell CmdLets.

The PowerShell team has done a good job of attempting to define some basic standards for us to follow when building CmdLets.  They have even pushed these standards into the design tools in the SDK.  We can choose to browse and select from any of the Verb categories that have been pre-defined.

Is this enough?

I see that many PowerShell users are designing scripts and functions that attempt to enforce the Verb-Noun requirement.; I am not sure this was what the PS team  had in mind.

It is my strong belief that, by using the Verb-Noun nomenclature in functions and scripts, we will clutter up the basic PS environment.

Why would this happen?

PowerShell CmdLets have basic guidelines for deployment.  Among these guidelines are the necessity of providing ‘Help” support via an XML file,  “HelpMessage” support for properties, support for required default behaviors and more.

Designing a function called Get-ComputerUser looks nice and “Power Shelly” but it is a function masquerading as a CmdLet.  It will never have complete internal support by the PowerShell runtime because it can’t be completely hooked into the shell.

PowerShell CmdLets are NET objects that are intended to extend the shell in significant ways.  Functions are not truly NET objects  even if they are managed as objects by PowerShell.  They are not “hooked” into the runtime in the same ways as a CmdLet.

The idea of using a Verb-Noun nomenclature comes from areas of formal system design.  The intention is to make the use of the function, CmdLet, procedure or script more obvious to the user.  It is also a way of reminding the designer that  a procedure should have an action-object  relation to the system.

This can be used for functions as well as CmdLets but in PowerShell the use of the Verb then the “dash” then the Noun should be reserved for CmdLets to avoid confusion.

A function can be named in the action-object mode by using the verb and noun without the dash and naming the function in Pascal Case which is a recommendation of the PowerShell team.  Instead of:

function Get-ComputerUser ()

use:

function GetComputerUser ()

This same naming rule should be applied to scripts also.

About what a PowerShell CmdLet should be used for.

When should we build a separate PowerShell CmdLet?  Should we build one whenever we are using the same thing over and over in script?  Isn’t that what functions and script files are for?

So if functions and script file can do this and so many other things what are CmdLets for anyway?

Looking at the PowerShell built-in CmdLets it is somewhat clear that these CmdLets perform global tasks.  They are divided up into functional areas defined by their verb category.  Here is a list of verb categories currently used in PowerShell.

PowerShell Verb Categories

Common Verbs

Add, Clear, Copy, Get,  Join, Lock, Move, New, Remove, Rename, Select, Set, Split, Unlock

Communication Verbs

Connect, Disconnect, Read, Receive, Send, Write

Data Verbs

Backup, Checkpoint, Compare, Convert, ConvertFrom, ConvertTo, Dismount, Export, Import, Initialize, Limit,  Merge, Mount, Restore, Update, Quit

Diagnostic Verbs

Debug, Measure, Ping, Resolve, Test, Trace

Lifecycle Verbs

Disable, Enable, Install, Restart, Resume, Start, Stop, Suspend, Uninstall

Security Verbs

Block, Grant, Revoke, Unblock

The full list with rules is at: Cmdlet Verb Names

These categories are a very good start to providing a clue as to what the designers have in mind for PowerShell. They seem to have covered most of our needs for CmdLet design.  It is also useful to note that this list took some time to develop.  Many from the user community were consulted during the beta phase of PowerShell.  Blogs by the PowerShell team indicate that they believe that this is a near complete set of categories.  It may well be as it seems to follow very closely after the System Model for Windows or for most other systems for that matter.

PowerShell Object Naming Conventions

It seems pretty clear that these verbs are being selected because they provide a common ground for admin users to work from.  By knowing the verb-set it should be much easier for an admin to discover what CmdLets are available.  To find out how to export data,  just typing “help export”.  This will create a list of all installed CmdLets that support exporting data. It also allows a user to find help on the particular export CmdLet.  None of this is available if the user is using a function called “export-mydata” which may cause the user to become frustrated.  However, if the user is using ExportMyData and knows the basic rules of PowerShell he/she will not expect to find help on the command since it is clear that it must be a function or script.

Of course there is always flexibility in all rules so there are reasons why we might name a function with the ver-noun convention; but that's another, future,  issue.

PowerShell CmdLet Functionality

The question of what functionality a PowerShell CmdLet should, at minimum, have, is one that I expect will provoke many discussions.  This is not intuitive and can have many approaches and answers.  It is also an area that should be considered to some degree before choosing to use a custom CmdLet as a solution.

Issues that are immediately on the table:

Does the proposed CmdLet

  • abstract functionality sufficiently beyond what can be done with a simple function?
  • support all potential uses in a pipeline.
  • understand potential input object candidates
  • simplify the development of administrative scripts
  • flatten the learning curve for the administrator

These are off the top of my head and are tabled as seeds for future discussion.

PowerShell Required and Recommended  Elements

The PowerShell Team has put together a set of lists of required and suggested elements to be supported by all CmdLets.  Any design must take these into account before a proposed CmdLet can be completed.  How to support these requirements needs to be fully discussed both in the abstract and for the specific CmdLet being considered.  .

In the coming months I am hoping to see many blogs tackle this issue.  There are many interesting and complex questions that these requirements pose.  Let’s find these questions and start listing the possible answers to them. 

I am hoping for is something like I have seen from MOW, ThePowerShellGuy,  MOW, on his blog, has taken to breaking down each and every piece of the PowerShell environment -  in the open and in the raw.

The PowerShell Team Blog has also addressed some of these issues over the last year but they have not done an exhaustive treatment of the subject(s).

Finally

The above may, at times, may be a little wordy and, possibly, not ideally worded.  I hope that anyone finding this vague, confusing or incorrect will leave a comment or criticism before I attempt the second part of this issue.

This next part may be my undoing as I am going to try and give concrete examples of some of the issues I have presented here.  I am hoping that, with these examples, I will be able to clarify for myself and others just what needs to be minimally established for the successful design of a PowerShell CmdLet.

 

Thursday, January 18, 2007

PowerShell CmdLet Parameters 101 (Part III)

I just realized that I have failed to note that, so far, all examples have been given in VB.NET syntax.  This has happened because I started this little project when I was trying to convert a CmdLet into VB.NET.  As I was working with the Attributes I noticed that, in the SDK,  not one scrap of code has been given in VB.NET and that the conversions were not completely obvious to all.

For C# CmdLet builders the SDK has all of the necessary syntaxes and examples.  Hopefully my discussion will be of value to beginners with CmdLets in C#.

Now for more fun with CmdLet Parameters, this time with a no-attribute, no-argument parameter.

The Switch Parameter

This is one of my favorite.  It is easy to build and easy to use.  It also comes in very handy when building CmdLets that run mostly from the command line but will work well with pipelines.

    <Parameter( _
    )> _
    Public Property Detailed() As SwitchParameter

The SwitchParameter property type can be declared as simply as above.  It is a Boolean and requires no input after the switch. You use it by adding it to the command line anywhere after the CmdLet

PS>Test-CmdLet mytest -detailed

This will set the property to True.  Not using the switch parameter will leave it at False, the default.

PS>Test-CmdLet mytest -detailed                                                                                         
                                                                                                                                                                                                                                   
Name           : mytest                                                                                                 
Host           :                                                                                                        
Detailed       : True                                                                                                   
Stopping       : False                                                                                                  
CommandRuntime : Test-Cmdlet                                                                                            
                                                                                                                                                                                                                                            
                                                                                                                     
PS>Test-CmdLet mytest                                                                                                   
                                                                                                                                                                                                                                       
Name           : mytest                                                                                                 
Host           :                                                                                                        
Detailed       : False                                                                                                  
Stopping       : False                                                                                                  
CommandRuntime : Test-Cmdlet                                                                                            

 

The Rest Of The Line Parameter Value

    <Parameter( _
        ValueFromRemainingArguments:=True) _
    > _

By using ValueFromRemainingArguments we can get a list from the command line.  This is useful when we want to specify a few quick items such as user ids or computer names.  Larger lists should probably always be taken from a file or from the pipeline.

Here is one of the ways this parameter value can be used.

PS>Test-Cmdlet -host alpha,beta,gamma,delta 
   
Name   :
Host   : {alpha, beta, gamma, delta}
Detailed   : False  
Stopping   : False  
CommandRuntime : Test-Cmdlet 

PS>get-bufferhtml 25 >d:\buffer.txt 

 The Host property is populated with an array of all of the strings on the remainder of the command line.  This can be used in any way you need to make a CmdLet that is flexible from the command line and in the pipeline.

In the next installment I am going to try and tackle ParameterSets.  We have enough pieces here to begin to experiment with how ParameterSets can add to the flexibility of a CmdLet.

Here is a hook into the online SDK docs about using ParameterSets and Parameters.

http://msdn2.microsoft.com/en-us/library/ms714647.aspx

Wednesday, January 17, 2007

PowerShell CmdLet Parameters 101 (Part II)

In the first chapter I established the relationship between two of the available "Attributes" for CmdLet properties.  The two I chose were chosen because they are immediately usable and I can very quickly demonstrate their usefulness.

The next two useful "Attributes" I am going to explore are [Alias] and [HelpMessage].  Neither of these are obvious in usage nor in demonstration.

Let's explore [Alias] first.  The Alias attribute looks like this.

[Alias( alias1,alias2,...)]

By itself on a property it is declared thusly:

[Alias( alias1,alias2,...)] Public Property MyProperty

When combined with "Parameter" it looks like this:

[Alias( alias1, alias2)] _

[Parameter()] _

Public Property MyProperty

What does "Alias" do?

The Alias attribute allows us to give alternate names to the same property. 

Suppose we want to pass an object in the pipeline that contains a user name property called "Fullname".  Our CmdLet doesn't accept "Fullname" but does look for "Name". 

Couldn't we just add another property? 

Well we could but we can more easily just add an "Alias" - "Fullname".  This would allow the exact same code to execute and allow the same property to bind to objects that express "Name" or "Fullname".

There is an actual example of this in the Windows PowerShell Programmer's Guide code sample for CmdLet building that uses the Get-Process CmdLet to demonstrate the use of an Alias.  Get-Proc defines the property "ProcesssID" with the  Alias "ID" as a shorthand for ProcessID. ( see Adding Aliases, Wildcard Expansion, and Help to Cmdlet Parameters )

If I modify my test CmdLet "Name" property like this:

<[Alias](foobar)> _

<Parameter( _

           Position:=0, _

          ValueFromPipelineByPropertyName:=True)> _

Public Property Name

I can do this:

                                                                                                                                                                                                                                      
PS>test-cmdlet -foobar SomeFooBar                                                                                       
                                                                                                                        
Name                 Stopping CommandRuntime                          
----                 -------- --------------                          
SomeFooBar           False Test-Cmdlet                             
                                                                                                                        

Now "-foobar" can set the Name property from the command line or from the pipeline.  Notice that the property is still "Name" so all code references to this will continue to be the same.  All internal references within the CmdLet will reference "Name" and external references from outside the CmdLet code can reference either "Name" or "foobar".

So the "Alias" attribute is pretty easy to describe.  What about the "HelpMessage" value of the Parameter attribute?  How does it look?  What does it do?

The "HelpMessage" value looks like this:

HelpMessage:="some useful message"

It is added into the list of values following the "Parameter" declaration.

<[Alias](foobar)> _

<Parameter( _

          HelpMessage:="some useful message" ,  _

           Position:=0, _

          ValueFromPipelineByPropertyName:=True)> _

Public Property Name

Help message adds useful runtime information that can be displayed with the Get-Command utility CmdLet. 

                                                                                                                        
PS>get-bufferhtml 15 >d:\buffer.txt                                                                                     
PS>((gcm test-cmdlet).Parametersets[0].parameters)[0]                                                                   
                                                                                                                        
                                                                                                                        
Name                            : Name                                                                                  
ParameterType                   : System.String                                                                         
IsMandatory                     : False                                                                                 
IsDynamic                       : False                                                                                 
Position                        : 0                                                                                     
ValueFromPipeline               : False                                                                                 
ValueFromPipelineByPropertyName : True                                                                                  
ValueFromRemainingArguments     : False                                                                                 
HelpMessage                     : Some useful message.                                                                  
Aliases                         : {TheName, foobar}                                                                     
Attributes                      : {System.Management.Automation.AliasAttribute, __AllParameterSets}                     
                                                                                                                                                                                                                                                                                                                                                                                                                             

Notice that this message show up in the properties of a property.  Notice also that all of the aliases are listed here too.

The command I used to explore this gets the members of the default CmdLet "PropertySet" and gets the first property in the set which is out "Name" property.  All of these items listed are the standard  attributes of a PowerShell property object.  Notice that the "ValueFromPipelineByPropertyName" parameter value is listed and set to true.  The "Position" is set to zero.

"Get-Command" is how we read these values incase we need to know how a command works.  The "HelpMessage" should contain useful hints on how to use the property. It's kind of a mini  help system built into the CmdLet that can let us quickly assess the how a property is to be used.

Next I will try and unwind PropertySets.

 Here is the complete and latest code.

Imports System.Management.Automation

<Cmdlet(VerbsDiagnostic.Test, "Cmdlet", SupportsShouldProcess:=True)> _
Public Class Test_Cmdlet
    Inherits Cmdlet

    Private _Name As String
    '<Parameter(Position:=0, Mandatory:=False)> _
    <Parameter( _
         HelpMessage:="Some useful message.", _
         Position:=0, _
         ValueFromPipelineByPropertyName:=True)> _
    <[Alias]("TheName", "foobar")> _
    Public Property Name() As String
        Get
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property

    Protected Overrides Sub ProcessRecord()
        Try
            WriteObject(Me)
        Catch ex As Exception

        End Try
    End Sub

End Class

Technorati tags: ,
del.icio.us tags: ,

Monday, January 15, 2007

PowerShell CmdLet Parameters 101

For all of you who are attempting to write a CmdLet ;

"We salute you!"

Ok! Ok1 I'll turn my Ipod off.

I decided to build up a CmdLet from "First Principals" and where better to start than with an exploration of the "input"  into a CmdLet.

CmdLet input is accomplished in a myriad of ways.  It can come from the values/objects listed after the CmdLet name, from the pipeline, from ScripBlocks or from Parameters and ParameterSets.  I will try to break these down by starting with, from the perspective of the CmdLet code,  the simplest case first then add each next option.

I will start with building a trivial CmdLet; "Test-CmdLet",  Here is the code.

Imports System.Management.Automation

<Cmdlet(VerbsDiagnostic.Test, "Cmdlet", SupportsShouldProcess:=True)> _
Public Class Test_Cmdlet
    Inherits Cmdlet

    Private _Name As String
    Public Property Name() As String
        Get
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property

    Protected Overrides Sub ProcessRecord()
        Try
            WriteObject(Me)
        Catch ex As Exception

        End Try
    End Sub

End Class

You can see that the above code is trivial.  It is a basic "do-nothing"  Class template with one Property, "Name",  and returns a reference to itself.  For now I am using this returned "self" reference as a simple means to capture the CmdLet object and inspect it.

What happens when we run this in PoSH?

PS>test-cmdlet

Name             Stopping             CommandRuntime
------------------------------------------ -------- --------------
                           False                      Test-Cmdlet

It tells us that it has a "Name"  property and that the property is empty.   Let's try to set the name property.

PS>test-cmdlet SomeName
Test-Cmdlet : A parameter cannot be found that matches parameter name 'SomeName'.
At line:1 char:12
+ test-cmdlet <<<< SomeName

Can we set the Property at all?

PS>$t = test-cmdlet
PS>$t.Name = "SomeName"
PS>$t

Name                 Stopping        CommandRuntime
---- -------- -------------------------------------------
SomeName     True                 Test-Cmdlet

Yes, we can assign a value to the "Name" property so why can't we do it from the argument list?

Let's explore the "Parameter" attribute. These little "decorations" that don't seem to show up in the code are actually little hints to the compiler that tell it to generate specific information in the building of a class and in the generation of the assembly.  PowerShell has leveraged these to make it easier for programmers to "hook-up" their code to the PowerShell system.

The simplest - (First Principals) - decoration we can use is to add the "Parameter" attribute decoration:

<Parameter()> _

Public Property Name() As String

Notice that it ends in an underscore.  The underscore in VB tells us that it is really all on one line.  For convenience, readability and ease of use the "Parameter" attribute is generally built over many lines; one line for each optional argument.

After a rebuild of the code our test looks like this:

PS>test-cmdlet SomeName
Test-Cmdlet : A parameter cannot be found that matches parameter name 'SomeName'.
At line:1 char:12
+ test-cmdlet <<<< SomeName

Still can't find the input....

How about if we give the CmdLet a hint.

PS>test-cmdlet -Name SomeName

Name                            Stopping                      CommandRuntime
---- -------------------------------------------------------------------
SomeName                False                             Test-Cmdlet

Now that's better.  We can assign the property by specifying it's name on the command line as a parameter using the "dash" to tell PoSH that we are asking for it to find a parameter with the name "Name"

The next step is fairly obvious.  How to get the CmdLet to pick up the name from the arguments passed "positionally" to the CmdLet.

<Parameter(Position:=0)> _

Public Property Name() As String

Now when we give PoSH the CmdLet like this "test-cmdlet somename" the value is picked up and automatically assigned to the parameter because of it's position on the command line.

PS>test-cmdlet SomeName

Name               Stopping          CommandRuntime
--------------------------------------------------------
SomeName    False                 Test-Cmdlet

What happens if we send an object through the pipeline to our CmdLet?  Wouldn't it be nice if the CmdLet could get it's parameters from the object we pass?  Let's see what happens if we pass a process object through the pipeline?

PS>$p[0]|test-cmdlet
Test-Cmdlet : The input object cannot be bound to any parameters for the command either because the command does not ta
ke pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
At line:1 char:17
+ $p[0]|test-cmdlet <<<<

PoSH can't find any valid parameters. 

But the process objects have a "Name" property. Why can't PoSH find it and "bind" it to our CmdLet?

Maybe we need to explicitly tell PoSH that a parameter is available for binding.

<Parameter( _

           Position:=0, _

        ValueFromPipeLine:=true )> _

Let's try again.

PS>$p[0]|test-cmdlet

Name                                                               Stopping           CommandRuntime
---- -------- --------------
System.Diagnostics.Process (alg)       False         Test-Cmdlet

The Name, in orange, looks a little strange.  It's telling us that "Name" has been set to the string value of the process object class instance; hence the "alg" in parens.  Well that's a little better.  We are bainding to the input object but the bind seems to be to the object itself and no teh propery we want.

We wanted the actual name of the process - "alg".  How can we get this.  Maybe we need more hints.  We told PoSH to take the "Name" parameter from the pipeline but didn't say how to bind it.  Let's tell PoSH to bind it by property name.

<Parameter( _

           Position:=0, _

          ValueFromPipelineByPropertyName:=True,  _

        ValueFromPipeLine:=true )> _

PS>$p=get-process
PS>$p | test-cmdlet

Name                   Stopping                         CommandRuntime
----                       --------                        --------------
alg                        False                                Test-Cmdlet
cmd                     False                                Test-Cmdlet
csrss                   False                                Test-Cmdlet
ctfmon               False                                Test-Cmdlet
dexplore           False                                Test-Cmdlet

Now the pipeline can find the property name and bind to it so that our CmdLet can get it's input property values from the incoming object.

 Now I am going to go back and remove the reference to "ValueFromPipeLine" and test.  This parameter is not really necessary for this type and level of binding so lets leave it out and use only:

<Parameter( _

           Position:=0, _

          ValueFromPipelineByPropertyName:=True)> _

This is the minimal set of attribute values to use to get both positional binding and binding by property name.

This may seem like a lot of detail for such trivial things.  After all, doesn't the SDK documentation tell us all of this.  Yes...but?  In order to debug parameter binding issues quickly and in order to fully understand what PowerShell brings to the table I feel it is worth this in depth discussion.   It took me a little while to track this information down in the SDK.  I found that the SDK had many deficiencies in it's approach mostly due to the need to document and and not to explain.

Many of the examples in the SDK are workable but don't address many of the questions a developer might have.  I am sure that many books will be written on the PowerShell SDK.  I recommend reading them if you plan serious development in PowerShell.  For those who need to quickly build a CmdLet I hope that this blog entry will be useful and save you some time.

In the next chapter I will attack some of the other "Parameter" attribute values and how they add to the ease of CmdLet development and provide dramatically enhanced capabilities to the baseline code.

 

Technorati tags: ,

 

del.icio.us tags: ,