How to Use Parameters in PowerShell Part II

PowerShell is a basic skill any administrator working in Windows or Azure should know. After writing his first article about PowerShell parameters, Greg Moore realized that there is even more to talk about. This article covers using pipelined parameters and more.

The series so far:

  1. How to Use Parameters in PowerShell Part I
  2. How to User Parameters in PowerShell Part II

In a previous article, I wrote about how to use parameters within your PowerShell scripts. It turns out that it was the most popular article I’ve written so far for Simple-Talk. I appreciate all the votes and page views. However, as I was writing my article on Adding Help to Your PowerShell Scripts, I found myself diving deeper into how you can use parameters. I realized I had barely scratched the topic of parameters and PowerShell, hence this article.

A bit of this will be a recap from that article on help, but I will then expand upon parameter usage and passing in PowerShell. Even then, I suspect I will not cover every possible detail. PowerShell is a deep and rich scripting language that I’m still learning more about every day.

Boolean vs Switch

In my previous article on Adding Help, I gave an example of both Boolean and switch. I will very quickly recap them here.

This script uses a Boolean parameter.

This script uses switch which you can compare to the previous script.

They both act in very similar fashions except you cannot assign an actual value to a switch datatype. If it’s not included on the command line when calling the script, it’s treated as false. If it’s included on the command line, regardless of what you try to assign to it, it will be treated as true.

If you create the above script and save it as Switch_help.ps1 or download it from here, you can play with trying to pass different values to the –help parameter. You will find they all give the same results.

Parameter Definitions

With that out of the way, I want to recap how to define a parameter and build upon it. Start with the script below (note all scripts are available on Github.).

Save it as simplest.ps1.

Run it like this:

You should get:

This is just as you’d expect and is about the simplest script you can write with a parameter.

Now save the following script as simplest_bad.ps1.

If you save this and try to run it, you should get the following error:

This example shows that the param keyword has to be the first line in a script. (Note when discussing functions, this changes a bit. I most likely will cover this in a future article).

In my first article on parameters and in the examples above with $help, I explicitly defined the datatype for a parameter. Generally, I prefer doing so for parameters. Below is a short script to illustrate this:

Save that as simplest_gettype.ps1 and then run it with various data types.

For the most part, the datatype makes make sense. PowerShell is generally pretty good at guessing the data type. However, it’s important to notice that $true is considered as a Boolean and true is considered a string. If you absolutely need to check if the user entered a $true or $false value, I highly recommend the use of Boolean (or switch) as shown at the start of this article.

As the above example shows, you can pass an object to a script. This can be done a couple of ways that may be useful.

Save the following script as Passing_Object.ps1.

 

Run as follows:

You can also define an object first and pass that. For example, if you define an object first and pass that to your script, it will work also:

As seen in my article on Help, besides defining the datatype for a parameter, you can also force a parameter to be mandatory. You can also provide default values for a parameter.

Save the following script as Default_Example_1.ps1:

If you run this in the PowerShell ISE, you will see:

In my help article, I had a slight variation on this. You may find that not only do you want to make a particular parameter mandatory, but you only want to allow specific values. Save the following script as Default_Example_2.ps1:

Now when you try to run it in the PowerShell ISE, you should see:

Note that in PowerShell ISE, the valid values are displayed. This is not necessarily true of all editors. If you enter an invalid value you will see something like:

It is possible to have multiple and different validation sets. I explore that a bit more in the help article.

ValidateSet Caveat

Enter the script below and save it as ValidateSet_Caveat_1.ps1:

Run it and select Bad:

You will get:

The error message explains the issue clearly.

Save the following script as ValidateSet_Caveat_2.ps1:

Note the $Captain parameter is no longer mandatory. This script should show several interesting details about the use of ValidateSet.

Run the following first:

Your result will be:

Note that while you provided options for the set, since it wasn’t mandatory, you weren’t prompted to enter a Captain’s name. In addition, even though you did define a ValidateSet for $status, it wasn’t used. As a result, in the first message, no $status is shown, and in the second message, a status is shown that wasn’t part of the ValidateSet.

Now if you decide you want to be the Captain of a starship, you might try something like:

That only results in:

This result makes sense because you’re trying to assign a value to $Captain at the same time it’s declared, but you’re also telling PowerShell what the valid values are and your name isn’t one of them.

Now try

Evidently, all knowledge of the Spore Drive has been declared top secret, and you can’t have a black alert. But you can set a standard alert color.

When using ValidateSet, keep in mind that it only is effective if you assign a valid value at the time you declare the variable. Otherwise, any value can be used (as shown by the $status in the above script).

The above examples are useful and applicable if you are running your scripts and typing in the parameters by hand, but, what if you want to run a script and pass it data from another source.

Pipelined Parameters

Most PowerShell cmdlets let you use the pipe | symbol to pass data. For example, run the following script:

You should see a window similar to the following show up:

You might be tempted to do something like:

You will find that nothing happens, but this is simple to fix.

Save the following script as Piped_Variable.ps1:

First, you can run it as follows:

It works as you’d expect.

Now, try this:

It also works as you’d expect, but what if you want to pass in multiple values? You could try:

That didn’t work as you might hope.

Have no fear. PowerShell does have a way of handling this. At first, the solution will look more complicated than you might need, but I think you’ll quickly agree that the solution is fairly useful.

Save the following script as Piped_Variable_2.ps1:

Run it like this:

You will see the following:

This does exactly what you would expect and want: it runs the begin block once at the start, and the end block once at the end, but the Process block runs multiple times as expected. Below is a more useful example:

Save the above script as Piped_Addition.ps1and run it as:

You should get the following as a result:

You may be curious as to what happens if you try to pass in variables in the standard manner, i.e. after the cmdlet:

This will work as expected:

However, you may be a bit surprised when you run this:

It doesn’t work.

When you pass in parameters to that function, PowerShell treats the 4,5 as an object as the error suggests.

You could write the above piped addition program as:

Save it as Nonpiped_addition.ps1 and then run it as:

You will get:

What if you want to be able to handle the user doing either? Your first attempt will probably look something like the following:

Save it as Piped_Addition_2.ps1 and run it like this:

You will get:

Supply the parameters like this:

You will get:

If you try to send the parameters both ways:

This does not work. You can’t tell PowerShell to accept a parameter as both piped AND non-piped.

However, one of the things that I continue to find amazing about PowerShell is that the authors gave a lot of thought about how as a programmer I might want to do things AND to keep things simple. You might already be thinking that could be a simpler way to do the above and there is. In fact, an earlier example came close to solving the problem.

Save the following script as Piped_Addition_3.ps1.

Run it with the following input:

You will get the following results:

This is what you would expect from the original piped addition example, but what happens if you run the following?

You will get:

Note that since the Write-Host is outside of the foreach loop, you only see it once.

It’s worth taking time to understand what exactly is happening here.

When you send piped input to a script (or a function), the Process block gets called for each individual value passed to it. This means $addends consists of a single value in the foreach loop. In the first example, Process gets called three times because there are three separate values piped in: 3,5, and 4.

When passing the parameters by name, you are actually passing in an object of type System.Array, and Process gets called just once (because there’s only one System.Array being passed to it). However, the foreach gets called three times because the System.Array consists of three values: 4,8, and 1.

This becomes obvious if you add one line to the above script and resave it as Piped_Addition_4.ps1 and run it:

 

While Piped_Addition_2.ps1 may seem like the obvious way to go, once you realize how the parameters are being passed, you should realize why Piped_Addition_3.ps1 works and is far simpler to write. You will also notice for simplicity’s sake, I eliminated the begin and end blocks since they weren’t necessary.

Multiple Piped Parameters

The above example is useful if you want to pass in a variable number of parameters for the same purpose; for example, you want to pipe in two different parameters.

Your first attempt might look like this:

Save this as Piped_ByName_1.ps1and run it as:

You’ll get:

Which makes sense as this is the behavior you saw with the named addition examples above. It passes each value into the Process block, and even though you asked for two values, it only read one from the pipeline at a time.

You might try:

But that’s no better:

It might dawn on you to try to pass in an object:

But that’s no better either.

Before giving up entirely though, try the following:

Your results will look similar to:

The fact that you could pass in the output from a cmdlet and get something semi-useful hopefully piqued your curiosity. Before I give the complete solution, you should run this code:

You will see the following:

I’ve highlighted two lines since that’s what you want in your script.

Save the following script as Piped_ByName_2.ps1:

You will notice that now the parameters use ValueFromPipeLineByPropertyName instead of ValuefromPipeline and that the datatype for $Provider matches what get-member provided.

Run this command:

You will get:

Note you can still call the script with named parameters:

That may seem like a trivial example since you’re hardcoding the name of the drive, but you could also do something like this:

You’ll get this back:

In an earlier article on Help, I showed how using the –Full or -Showwindow parameter would provide a lot more detail. This is particularly useful if you’re trying to write a script and want to know how cmdlets can handle parameters. It will allow you to see what the names of properties are of other cmdlets so that you can use them in the pipeline to pass to your own cmdlets. In theory, you can take the named property of one cmdlet and pass it to one that you write. Note, however, that this is not always accurate.

Save the following script as Piped_byName_3.ps1:

Run this as:

You will get something like:

You’ll note it doesn’t show the maximum size.

While the help suggests you should be able to get the maximum size of a drive from get-psdrive, the reality is you can’t. To prove that run:

You’ll see that it returns data for Used, Free, and more, but not for the Maximum size. I think the easiest solution would be to add up Used and Free space and display that. I leave that as an exercise for you to figure out.

I do want to draw your attention to one detail in PowerShell that I absolutely love: the fact that you can divide by 1GB (or even 1MB and 1TB) $($free/1GB) works just as you’d expect!

Mixing Pipelined and Passed Parameters

As a final script, I will partially contradict something I said above. I noted you couldn’t pass in the parameter both via pipeline and as a passed parameter. That’s not quite true. You can’t pass in the same parameter both ways, but you can combine the usage. Below is an example:

Save this as Check-FreeDisk.ps1 and run it as before:

It will pretty much run as you might expect, though in this case, I’ve changed it slightly only to show actual storage devices.

Now try running:

You should see something like:

Try running the code without the named parameter:

You will get very different results from what you expect!

Basically, PowerShell is capturing the first passed parameter and using that! Fortunately this is an easy fix, simply move the $buymore parameter to the start of the list of parameters.

Save this as Check-FreeDisk_2.ps1 and run it:

You’ll get what you expect:

Try both of these examples:

Each one will result in:

This is most likely what you want. In general, put parameters that you don’t necessarily expect a user to specifically name first. Note this can obviously get more complicated when you want to allow the same parameter to be passed by the pipeline or as part of calling the cmdlet (as in the addition examples above).

Conclusion

When I started this article, I thought it would be a sequel and conclusion to my previous article. However, as I dove deeper, I realized I’m still haven’t touched upon everything there is to know about Parameters, including using parameters in functions, the use of cmdletbinding, how to work with some of the default parameters that PowerShell offers such as –verbose, -erroraction, etc. I suspect there’s at least one more article in the works.

Thanks again for your support and hopefully this article will approach the popularity of the first.