Interesting finds around PowerShell Mandatory Parameters

Posted 20 June 2011, 15:47 | by | | Perma-link

I noticed an interesting thing today while attempting to make some parameters on a PowerShell script required.

As I'm just starting to get the hang of PowerShell, you'll have to bear with my script-kiddie tendencies, however, here's the background:

I'm creating a script that will take the following parameters (can you tell I'm still working with TFS?):

  • [string] WorkItemType
  • [string] ProjectsToUse
  • [switch] ExportWitd

I wanted to make the WorkItemType parameter required, and searching around the internet led me to the following code:

[string] $WorkItemType = $(throw 'workItemType is required')

Which does indeed "work" if you're happy with exceptions being thrown if you don't set it:

workItemType is required
At C:\Scripts\UpdateTFSWorItems.ps1:17 char:37
+     [string] $WorkItemType = $(throw <<<<  "workItemType is required"),
    + CategoryInfo          : OperationStopped: (workItemType is required:String) [], RuntimeException
    + FullyQualifiedErrorId : workItemType is required

And it also doesn't help if more than one parameter is required (you only get the first error), and get-help doesn't help:

PS C:\Scripts> get-help .\UpdateTFSWorItems.ps1
UpdateTFSWorItems.ps1 [[-WorkItemType] <String>] [[-ProjectsToUse] <String>] [-ExportWitd]

As you can see, get-help thinks that WorkItemType is optional (the square brackets around the name and the type).

The actual answer came in the form of the parameter attribute:

[parameter(Mandatory = $true)]
[string] $WorkItemType

Now when I run the script without the WorkItemType parameter I get prompted to supply the required parameters as a read prompt, rather than nasty exceptions:

cmdlet UpdateTFSWorItems.ps1 at command pipeline position 1
Supply values for the following parameters:

However, this also has some fairly major consequences:

It converts your script into an "Advanced Function", as some of the documentation on TechNet for this states:

[T]o be recognized as an advanced function (rather than a simple function), a function must have either the CmdletBinding attribute or the Parameter attribute, or both.

There doesn't appear to be a way to apply one or other of these attributes and not be recognised as an advanced function.

Because your script is now "advanced", get-help is a bit different:

PS C:\Scripts> get-help .\UpdateTFSWorItems.ps1
UpdateTFSWorItems.ps1 [-WorkItemType] <String> [[-ProjectsToUse] <String>] [-ExportWitd]
[-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>]
[-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>]
[-OutBuffer <Int32>]

Where did all those extra parameters come from? Your function now supports all the "Common Parameters", this is more clearly stated if you add some help documentation to your script:

.Parameter WorkItemType
    Required. The name of the work item type you want to update in TFS.
.Parameter ProjectsToUse
    New or Legacy, defaults to New.

Calling get-help now results in the following syntax line being auto-generated:

    C:\Scripts\UpdateTFSWorItems.ps1 [-WorkItemType] <String> [[-ProjectsToUse] <String>]
    [-ExportWitd] [<CommonParameters>]

In general though I think this is a bonus, as it allows me to call the write-verbose and write-debug methods to get additional output and breakpoints.

If you're going to go to the effort of adding parameter documentation, you might as well also supply the HelpMessage parameter of the Parameter attribute:

[parameter(Mandatory = $true, HelpMessage = "The name of the work item type you want to update in TFS.")]
[string] $WorkItemType

Which then allows the user to get some help as they are prompted:

cmdlet UpdateTFSWorItems.ps1 at command pipeline position 1
Supply values for the following parameters:
(Type !? for Help.)
WorkItemType: !?
The name of the work item type you want to update in TFS

Gotcha with Write-Verbose

Write-host appears to take an array of objects and write a string representation of them out to the screen, which can be (ab)used in "interesting" ways:

write-host "Exporting WIT Definition from TFS Project" $selectedProjects[$i]

Results in output like:

Exporting WIT Definition from TFS Project Generic

Write-verbose (and indeed write-debug) explicitly only take a single string however, and calling them in the same way results in a nasty exception to that effect, so make sure you concatenate them:

write-verbose ("Project folder " + $selectedProjects[$i] + " already exists")

Which is slightly tedious.

Filed under: PowerShell, TFS