We are in the process of cleaning up our Cmdlets and ensuring that they are consistent.  One of the things we want to do is the ensure that we provide Aliases for ParameterNames.  As a general rule, parameternames are not pithy.  That is great for reading scripts but can be a pain during interactive sessions.  You only have to provide enough of a parametername to disambiguate it but you can also use parameter aliases if they are defined.  For instance, you can type -OutVariable or you can type -ov because all the commands provide the alias "ov" to map to "OutVariable". 

 

The task at hand is to ensure that we make good use of parameter aliases.  As always, “to ship is to choose”.  That means that we don’t time to do everything so we have to focus on the things that are going to produce the biggest bang for the buck.  Here I walk through the steps to get a histogram of parameter use by all the Cmdlets.   Let’s break that down into steps:

1)    Get all the Cmdlets

2)    Get all the parameters of all the Cmdlets

3)    Create a histogram

 

Getting all the Cmdlets is pretty simple to do – we provide the “Get-Command” function which has the alias “gcm”

MSH> gcm

 

CommandType     Name                      Definition

-----------     ----                      ----------

Cmdlet          add-content               add-content [-Path] St...

Cmdlet          add-history               add-history [[-InputOb...

….

 

If you provide no parameters, gcm returns the list of all Cmdlets.  If you provide a name specifier (you can use wildcards), then it returns everything that can resolve a token.  You can type “gcm *” to see what that looks like.  I’ll group them to show you want I mean:

 

MSH> gcm * |Group CommandType |Sort name

 

Count Name                      Group

----- ----                      -----

  120 Alias                     {ac, alias, aliases, cat, cd, ch...

 2746 Application               {$ncsp$.inf, $winnt$.inf, _defau...

  128 Cmdlet                    {add-content, add-history, add-m...

   25 ExternalScript            {aliases.msh, aspen.msh, burn-co...

   64 Function                  {..., A:, Alias:, Aspen:, B:, C:...

 

For this work, we are only interested in Cmdlets so how do we find just the Cmdlets? 

 

The thing I love about Monad is that with a few concepts, you can do almost anything.  Since you know that gcm returns objects and you know that you can filter objects using “where”, the following gives you exactly what you want:

 

MSH> gcm * |where {$_.CommandType -eq "Cmdlet"}

 

With that as a baseline, let’s explore whether there is a better way to do things.  Type “gcm -?” and you’ll get help on “Get-Command” and it will tell you all sorts of wonderful things.  I’m one of those “less is more” type of readers so I often just want the examples. I do this to get just the examples:

 

MSH> (gcm -?).Examples

 

In this case it doesn’t tell me what I wanted to see (looks like we need to cleanup our help as well J) so let’s use gcm on Get-Command:

 

MSH> gcm Get-Command |fl *

DLL           : C:\WINDOWS\assembly\GAC_MSIL\System.Management.Auto

                mation\1.0.7487.0__31bf3856ad364e35\System.Manageme

                nt.Automation.dll

Verb          : get

Noun          : command

HelpFile      : System.Management.Automation.dll-Help.xml

MshSnapIn     : Microsoft.Management.Automation.Core

Type          : System.Management.Automation.Commands.GetCommand

ParameterSets : {CmdletSet, AllCommandSet}

Definition    : get-command [[-CommandArguments] Object[]] [-Verb S

                tring[]] [-Noun String[]] [-MshSnapin String[]] [-T

                otalCount Int32] [-Synopsis] [-Verbose] [-Debug] [-

                ErrorAction ActionPreference] [-ErrorVariable Strin

                g] [-OutVariable String] [-OutBuffer Int32]

                get-command [[-Name] String[]] [[-CommandArguments]

                 Object[]] [-Type CommandTypes] [-TotalCount Int32]

                 [-Synopsis] [-Verbose] [-Debug] [-ErrorAction Acti

                onPreference] [-ErrorVariable String] [-OutVariable

                 String] [-OutBuffer Int32]

 

Name          : get-command

CommandType   : Cmdlet

 

This tells me that I can use “-Type Cmdlet” to get just the items I want:

 

MSH> gcm –Type Cmdlet

 

Notice also that gcm shows you the ParameterSets.  Every command can have multiple sets of parameters.  For instance you can do a “get-process” specifying either an ID or a ProcessName or by pipelining a set of instance to the command.

 

MSH> (gcm get-process).parametersets |ft name,parameters -Auto

 

Name        Parameters

----        ----------

ProcessName {ProcessName, Verbose, Debug, ErrorAction, ErrorVari...

Id          {Id, Verbose, Debug, ErrorAction, ErrorVariable, Out...

Input       {Input, Verbose, Debug, ErrorAction, ErrorVariable, ...

 

So now what we want to do is to get all the parameters of all the ParameterSets of all the commands which are Cmdlets.  First let’s get all the ParameterSets.  We know that gcm returns all the properties of the Cmdlet and that “select” can be used to pick just those properties that we want so it seems like we should be able to do the following:

 

MSH> gcm -type Cmdlet |Select ParameterSets

 

Do this and you’ll discover that it doesn’t quite give you want you want because Parametersets is a Collection.  That command sequence returns a stream of collections when what we want is a stream of objects.  This is what “Select –Expand “ is designed to do.

 

MSH> gcm -type Cmdlet |Select -Expand ParameterSets |Select -Expand

Parameters

 

 

Name                            : Value

Type                            : System.Object[]

IsMandatory                     : True

IsDynamic                       : False

Position                        : 1

ValueFromPipeline               : True

ValueFromPipelineByPropertyName : True

ValueFromRemainingArguments     : False

HelpMessage                     :

Aliases                         : {}

Attributes                      : {System.Management.Automation.All

                                  owNullAttribute, System.Managemen

                                  t.Automation.AllowEmptyCollection

                                  Attribute, __AllParameterSets}

….

 

 

 

 

  

Now we are ready to party.  Let’s put these parameters in a variable so the subsequent commandlines are easier to read.

MSH> $p = gcm -type Cmdlet  |Select -Expand ParameterSets |Select -Expand Parameters

 

 

Now we want to produce a histogram:

 

MSH> $p |group Name

 

Count Name                      Group

----- ----                      -----

   12 Value                     {Value, Value, Value, Value, Val...

   47 PassThru                  {PassThru, Passthru, PassThru, P...

   50 Path                      {Path, Path, Path, Path, Path, P...

   26 Filter                    {Filter, Filter, Filter, Filter,...

   49 Include                   {Include, Include, Include, Incl...

 

We want to focus on the ones that matter the most so let’s find out which parameters are used the most:

 

MSH> $p |group Name |sort -Descending count

 

Count Name                      Group

----- ----                      -----

  174 ErrorAction               {ErrorAction, ErrorAction, Error...

  174 Debug                     {Debug, Debug, Debug, Debug, Deb...

  174 Verbose                   {Verbose, Verbose, Verbose, Verb...

  174 OutBuffer                 {OutBuffer, OutBuffer, OutBuffer...

  174 OutVariable               {OutVariable, OutVariable, OutVa...

 

Now notice that the most used parameters are the ubiquitous parameters.  Pretty obvious really, when you think about the word “ubiquitous” J .  <Digression – Cmdlets are subclasses of a base .Net class.  Ubiquitous parameters are parameters that we put on the base class so that all commands inherit them and whose behavior is implemented by the Monad Cmdlet hosting environment.  For example, -OutVariable allows you to take the results of a command and assign or append it to a variable [as well as passing it on].  When a Cmdlet writes its results, Monad looks to see if the user has specified –OutVariable and it does this work [not the Cmdlet].>

 

The command line is about to get large again so let’s put the results into another variable and for yucks let’s use –OutVariable.  Of course who wants to type –OutVariable so let’s use its alias –ov.  It turns out that we already have good aliases for the ubiquitous parameters so we want to find the top 20 non-ubiquitous parameters.

MSH> $p |group Name |sort -Descending count –ov p1

 

Count Name                      Group

----- ----                      -----

  174 ErrorAction               {ErrorAction, ErrorAction, Error...

  174 Debug                     {Debug, Debug, Debug, Debug, Deb...

  174 Verbose                   {Verbose, Verbose, Verbose, Verb...

  174 OutBuffer                 {OutBuffer, OutBuffer, OutBuffer...

  174 OutVariable               {OutVariable, OutVariable, OutVa...

 

 

There are 6 ubiquitous parameter so there are 2 ways to get the top 20:

MSH> $p1 |select -first 26 |select -Last 20 –ov p2

MSH> $p1[6..25]

 

Count Name                      Group

----- ----                      -----

   52 WhatIf                    {WhatIf, WhatIf, WhatIf, WhatIf,...

   52 Confirm                   {Confirm, Confirm, Confirm, Conf...

   50 Path                      {Path, Path, Path, Path, Path, P...

   50 Exclude                   {Exclude, Exclude, Exclude, Excl...

   49 Include                   {Include, Include, Include, Incl...

   47 PassThru                  {PassThru, Passthru, PassThru, P...

   43 Force                     {Force, Force, Force, Force, For...

   37 Credential                {Credential, Credential, Credent...

   32 InputObject               {InputObject, InputObject, Input...

   29 Name                      {Name, Name, Name, Name, Name, N...

   26 Filter                    {Filter, Filter, Filter, Filter,...

   20 Property                  {Property, Property, Property, P...

   13 Scope                     {Scope, Scope, Scope, Scope, Sco...

   12 Value                     {Value, Value, Value, Value, Val...

    8 Description               {Description, Description, Descr...

    8 DisplayName               {DisplayName, DisplayName, Displ...

    8 ServiceName               {ServiceName, ServiceName, Servi...

    8 Input                     {Input, Input, Input, Input, Inp...

    6 Type                      {Type, Type, Type, Type, Type, T...

    6 Encoding                  {Encoding, Encoding, Encoding, E...

 

Now let’s turn the dial to 11 and see if these things already have aliases and if so, whether there are inconsistent aliases.  I’m going to make the assumption that if a parameter has an alias, it only has 1 alias (I validated this assumption with the following command “$p2 |select -expand group |where {$_.aliases.count -gt 1}” ).

 

The Group command keeps the things it grouped in a property called group.  We grouped parameters so this command gets us back the parameters:

 

MSH> $p2 |select -expand group

 

We now want to group these parameters by Name AND alias.  The issue is that the parameter class does not have such a property.  No problem!  Remember – Monad is all about helping you cope with a world that doesn’t give you want you need.  (If it did, you wouldn’t need us J ).  Also remember that there are a small set of KEY MONAD CONCEPTS and TECHNIQUES that you need to learn and then the world is your oyster.  The technique that saves us here is ScriptBlock Parameters.

 

I mentioned that we are turning to dial to 11 didn’t I?  OK – put your seatbelt on.

 

When you pipeline data to a command, you can specify a ScriptBlock to a parameter that does not take a ScriptBlock.  The Monad engine detects this and for every pipelined object, it assigns that object to the variable $_, evaluates the ScriptBlock and assigns the results to the parameter.  Let’s provide a simple example.  Here we pipe 2 integers to the stop-process command.  We provide a ScriptBlock to the parameter –ID which only accepts INTs.  The ScriptBlock issues a prompt and asks the user to enter a process id.  Monad assigns the pipelined objects (1 and 2) to $_, evaluates the ScriptBlock and passes the parameter to –ProcessName.

 

MSH> 1..2 |Stop-Process -ProcessName {Read-host "Record $_  Name" } -whatif

Record 1  Name: lsass

What if: Performing operation "stop-process" on Target "lsass (1440)".

Record 2  Name: msh

What if: Performing operation "stop-process" on Target "msh (3428)".

 

 

 

Back to the problem at hand.  We want to group these parameters by Name and Alias so let’s use the fact that Group can group by multiple parameters and use a ScriptBlock as one of those parameters:

 

MSH> $p2|select -Expand Group |group Name,{$_.Aliases[0]} |sort name

 

Count Name                      Group

----- ----                      -----

   52 Confirm.cf                {Confirm, Confirm, Confirm, Conf...

   37 Credential                {Credential, Credential, Credent...

    8 Description               {Description, Description, Descr...

    8 DisplayName               {DisplayName, DisplayName, Displ...

    6 Encoding                  {Encoding, Encoding, Encoding, E...

   50 Exclude                   {Exclude, Exclude, Exclude, Excl...

   26 Filter                    {Filter, Filter, Filter, Filter,...

   43 Force                     {Force, Force, Force, Force, For...

   49 Include                   {Include, Include, Include, Incl...

    8 Input                     {Input, Input, Input, Input, Inp...

   32 InputObject               {InputObject, InputObject, Input...

   29 Name                      {Name, Name, Name, Name, Name, N...

   47 PassThru                  {PassThru, Passthru, PassThru, P...

    2 Path                      {Path, Path}

   48 Path.MshPath              {Path, Path, Path, Path, Path, P...

   12 Property                  {Property, Property, Property, P...

    8 Property.MshProperty      {Property, Property, Property, P...

   13 Scope                     {Scope, Scope, Scope, Scope, Sco...

    8 ServiceName.Name          {ServiceName, ServiceName, Servi...

    6 Type                      {Type, Type, Type, Type, Type, T...

   12 Value                     {Value, Value, Value, Value, Val...

   52 WhatIf.wi                 {WhatIf, WhatIf, WhatIf, WhatIf,...

 

Here it shows that most parameters don’t have an alias and that PATH uses the alias MSHPATH but that 2 commands don’t use this alias which gives us something to investigate.

 

 

Here is what I hope you’ve learned from this:

1)    Get-Command provides detailed information about anything that can be executed included parameter information for Cmdlets.

a.    Explore how Get-Command returns different data for different types of executables (e.g.
Get-Command ipconfig.exe |fl *
Get-Command prompt |fl *

2)    You can always use WHERE to filter the results of a command but it can be worthwhile investigating whether the command does its own filtering which can sometimes be faster or easier to specify.

a.    Explore how you can leverage the fact that help returns objects so you can craft your own view of help.  E.g.
get-help get-* |fl Name,Synopsis
get-help * |where {$_.Synopsis -match "process"} |fl name,Synopsis

3)    Commands can have multiple ParameterSets which define valid sets of parameters to perform an operation.

a.    Explore which command has the most ParameterSets.  Hint – use a ScriptBlock parameter on group.

4)    Select can pick properties of an object and/or expand the elements of a property if that property is a collection.

a.    Explore the power of both selecting properties and expanding properties. Take the example below and explore each step in the pipeline:
MSH> gps |select ProcessName -expand Modules -ea silentlycontinue |group ModuleName |sort -Descend count |select -first 20

5)    Group can produce a histogram based upon a property or set of properties and keep the original set of objects.

a.    Explore how you can use group to gain insights into your data.  E.g.
dir |group {$_.CreationTime.DayOfWeek} |sort Name
dir |group {$_.CreationTime.DayOfWeek},extension |sort Name

6)    ScriptBlock Parameters can be used in any pipelines to dynamically evaluate what data to pass to a parameter.

a.    Explore how you can use ScriptBlock parameters to do funky file tree manipulations. 
dir *zip |cpi -destination {$_.fullname.Replace("jps","BackupJps") } –whatif

 

Jeffrey P. Snover [MSFT]

Monad Architect

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]