You are on page 1of 10

Greg Moore

17 September 2019

How to Use Parameters in PowerShell Part I


Every DBA should have basic PowerShell skills. In this article, Greg Moore
explains how to write a PowerShell script that takes parameters.

The series so far:

1. How to Use Parameters in PowerShell Part I

2. How to User Parameters in PowerShell Part II

Recently I had a client ask me to update a script in both production and UAT. He wanted
any emails sent out to include the name of the environment. It was a simple request, and I
supplied a simple solution. I just created a new variable:

$envname = "UAT"

After updating the script for the production environment, I then modified the subject line
for any outgoing emails to include the new variable.

At the time though, I wanted to do this in a better way, and not just for this variable, but
also for the others I use in the script. When I wrote this script, it was early in my days of
writing PowerShell, so I simply hardcoded variables into it. It soon became apparent that
this was less than optimal when I needed to move a script from Dev\UAT into production
because certain variables would need to be updated between the environments.

Fortunately, like most languages, PowerShell permits the use of parameters, but, like
many things in PowerShell, there’s more than one way of doing it. I will show you how to
do it in two different ways and discuss why I prefer one method over the other.

Let’s Have an Argument


The first and arguably (see what I did there) the easiest way to get command line
arguments is to write something like the following:

$param1=$args[0]
write-host $param1
If you run this from within the PowerShell ISE by pressing F5, nothing interesting will
happen. This is a case where you will need to run the saved file from the ISE Console and
supply a value for the argument.

To make sure PowerShell executes what you want, navigate in the command line to the
same directory where you will save your scripts. Name the script
Unnamed_Arguments_Example_1.ps1 and run it with the argument FOO. It will echo back
FOO. (The scripts for this article can be found here.)

.\Unnamed_Arguments_Example_1.ps1 FOO

You’ve probably already guessed that since $args is an array, you can access multiple
values from the command line.

Save the following script as Unnamed_Arguments_Example_2.ps1. 

$servername=$args[0]
$envname=$args[1]
write-host "If this script were really going to do something,
it would do it on $servername in the $envname environment"

Run it as follows:

.\Unnamed_Arguments_Example_2.ps1 HAL Odyssey

You should see:

One nice ability of reading the arguments this way is that you can pass in an arbitrary
number of arguments if you need to. Save the following example as
Unnamed_Arguments_Example_3.ps1

write-host "There are a total of $($args.count) arguments"


for ( $i = 0; $i -lt $args.count; $i++ ) {
    write-host "Argument  $i is $($args[$i])"
}

If you call it as follows:

.\Unnamed_Arguments_Example_3.ps1 foo bar baz


You should get:

The method works, but I would argue that it’s not ideal. For one thing, you can accidentally
pass in parameters in the wrong order. For another, it doesn’t provide the user with any
useful feedback. I will outline the preferred method below.

Using Named Parameters


Copy the following script and save it as Named_Parameters_Example_1.ps1.

param ($param1)
write-host $param1

Then run it.

.\Named_Parameters_Example_1.ps1

When you run it like this, nothing will happen.

But now enter:

.\Named_Parameters_Example_1.ps1 test

And you will see your script echo back the word test.

This is what you might expect, but say you had multiple parameters and wanted to make
sure you had assigned the right value to each one. You might have trouble remembering
their names and perhaps their order. But that’s OK; PowerShell is smart. Type in the same
command as above but add a dash (-) at the end.

.\Named_Parameters_Example_1.ps1 -

PowerShell should now pop up a little dropdown that shows you the available parameters.
In this case, you only have the one parameter, param1. Hit tab to autocomplete and enter
the word test or any other word you want, and you should see something similar to:

.\Named_Parameters_Example_1.ps1 -param1 test

Now if you hit enter, you will again see the word test echoed.
If you run the script from directly inside PowerShell itself, as opposed to the ISE, tab
completion will still show you the available parameters, but will not pop them up in a nice
little display.

Create and save the following script as Named_Parameters_Example_2.ps1.

param ($servername, $envname)


write-host "If this script were really going to do something,
it would do it on $servername in the $envname environment"

Note now you have two parameters.

By default, PowerShell will use the position of the parameters in the file to determine what
the parameter is when you enter it. This means the following will work:

.\Named_Parameters_Example_2.ps1 HAL Odyssey

The result will be:

Here’s where the beauty of named parameters shines. Besides not having to remember
what parameters the script may need, you don’t have to worry about the order in which
they’re entered.

.\Named_Parameters_Example_2.ps1 -envname Odyssey -servername


HAL

This will result in the exact same output as above, which is what you should expect:

If you used tab completion to enter the parameter names, what you will notice is once
you’ve entered a value for one of the parameters (such as –envname above), when you try
to use tab completion for another parameter, only the remaining parameters appear. In
other words, PowerShell won’t let you enter the same parameter twice if you use tab
completion.

If you do force the same parameter name twice, PowerShell will give you an error similar
to:
One question that probably comes to mind at this point is how you would handle a
parameter with a space in it. For example, how would you enter a file path like C:\path to
file\File.ext.

The answer is simple; you can wrap the parameter in quotes:

.\Named_Parameters_Example_2.ps1 -servername HAL -envname 'USS


Odyssey'

The code will result in:

With the flexibility of PowerShell and quoting, you can do something like:

.\Named_Parameters_Example_2.ps1 -servername HAL -envname


"'USS Odyssey'"

You’ll see this message back:

If you experiment with entering different values into the scripts above, you’ll notice that it
doesn’t care if you type in a string or a number or pretty much anything you want. This
may be a problem if you need to control the type of data the user is entering.

This leads to typing of your parameter variables. I generally do not do this for variables
within PowerShell scripts themselves (because in most cases I’m controlling how those
variables are being used), but I almost always ensure typing of my parameter variables so
I can have some validation over my input.

Consider why this may be important. Save the following script as


Named_Parameters_Example_3.ps1

param ([int] $anInt, $maybeanInt)


write-host "Adding $anint to itself results in: $($anInt +
$anInt)"
write-host "But trying to add $maybeanInt to itself results
in: $($maybeanInt + $maybeanInt)"

Now run it as follows:

.\Named_Parameters_Example_3.ps1 -anInt 5 -maybeanInt 6


You will get the results you expect:

What if you don’t control the data being passed, and the passing program passes items in
quoted strings?

To simulate that run the script with a slight modification:

.\Named_Parameters_Example_3.ps1 -anInt "5" -maybeanInt "6"

This will result in:

If you instead declare $maybeanInt as an [int] like you did $anInt, you can assure
the two get added together, not concatenated.

However, keep in mind if someone tries to call the same script with an actual string such
as:

.\Named_Parameters_Example_3.ps1 Foo 6

It will return a gross error message, so this can be a double-edged sword.

Using Defaults
When running a script, I prefer to make it require as little typing as possible and to
eliminate errors where I can. This means that I try to use defaults.

Modify the Named_Parameters_Example_2.ps1 script as follows and save it as


Named_Parameters_Example_4.ps1

param ($servername, $envname='Odyessy')


write-host "If this script were really going to do something,
it would do it on $servername in the $envname environment"

And then run it as follows:

.\Named_Parameters_Example_4.ps1 -servername HAL

Do not bother to enter the environment name. You should get:


This isn’t much savings in typing but does make it a bit easier and does mean that you
don’t have to remember how to spell Odyssey!

There may also be cases where you don’t want a default parameter, but you absolutely
want to make sure a value is entered. You can do this by testing to see if the parameter is
null and then prompting the user for input.

Save the following script as Named_Parameters_Example_5.ps1.

param ($servername, $envname='Odyessy')


if ($servername -eq $null) {
$servername = read-host -Prompt "Please enter a servername"
}
write-host "If this script were really going to do something,
it would do it on $servername in the $envname environment"

You will notice that this combines both, a default parameter and testing the see if the
$servername is null and if it is, prompting the user to enter a value.

You can run this from the command line in multiple ways:

.\Named_Parameters_Example_5.ps1 -servername HAL

It will do exactly what you think: use the passed in servername value of HAL and the
default environment of Odyssey.

But you could also run it as:

.\Named_Parameters_Example_5.ps1 -envname Discovery

And in this case, it will override the default parameter for the environment with Discovery,
and it will prompt the user for the computer name. To me, this is the best of both worlds.

There is another way of ensuring your users enter a parameter when it’s mandatory.

Save the following as Named_Parameters_Example_6.ps1

param ([Parameter(Mandatory)]$servername, $envname='Odyessy')


write-host "If this script were really going to do something,
it would do it on $servername in the $envname environment"
and run it as follows:

.\Named_Parameters_Example_6.ps1

You’ll notice it forces you to enter the servername because you made that mandatory, but
it still used the default environment name of Odyssey.

You can still enter the parameter on the command line too:

.\Named_Parameters_Example_6.ps1  -servername HAL -envname


Discovery

And PowerShell won’t prompt for the servername since it’s already there.

Using an Unknown Number of Arguments


Generally, I find using named parameters far superior over merely reading the arguments
from the command line. One area that reading the arguments is a tad easier is when you
need the ability to handle an unknown number of arguments.

For example, save the following script as Unnamed_Arguments_Example_4.ps1

write-host "There are a total of $($args.count) arguments"


for ( $i = 0; $i -lt $args.count; $i++ ) {
    $diskdata = get-PSdrive $args[$i] | Select-Object
Used,Free
    write-host "$($args[$i]) has  $($diskdata.Used) Used and
$($diskdata.Free) free"
}

Then call it as follows:

.\Unnamed_Arguments_Example_4.ps1 C D E

You will get back results for the amount of space free on the drive letters you list. As you
can see, you can enter as many drive letters as you want.

One attempt to write this using named parameters might look like:

param($drive1, $drive2, $drive3)


$diskdata = get-PSdrive $drive1 | Select-Object Used,Free
write-host "$($drive1) has  $($diskdata.Used) Used and
$($diskdata.Free) free"
if ($drive2 -ne $null) {
$diskdata = get-PSdrive $drive2 | Select-Object Used,Free
write-host "$($drive2) has  $($diskdata.Used) Used and
$($diskdata.Free) free"
    if ($drive3 -ne $null) {
    $diskdata = get-PSdrive $drive3 | Select-Object
Used,Free
    write-host "$($drive3) has  $($diskdata.Used) Used and
$($diskdata.Free) free"
    }
    else
    { return}
}
else
{return} # don't bother testing for drive3 since we didn't
even have drive 3

As you can see, that gets ugly fairly quickly as you would have to handle up to 26 drive
letters.

Fortunately, there’s a better way to handle this using named parameters. Save the
following as Named_Parameters_Example_7.ps1

param($drives)
foreach ($drive in $drives)
{
    $diskdata = get-PSdrive $drive | Select-Object Used,Free
    write-host "$($drive) has  $($diskdata.Used) Used and
$($diskdata.Free) free"
}

If you want to check the space on a single drive, then you call this as you would expect:

.\Named_Parameters_Example_7.ps1 C

On the other hand, if you want to test multiple drives, you can pass an array of strings.

This can be done one of two ways:

.\Named_Parameters_Example_7.ps1 C,D,E

Note that there are commas separating the drive letters, not spaces. This lets PowerShell
know that this is all one parameter. (An interesting side note: if you do put a space after
comma, it will still treat the list of drive letters as a single parameter, the comma basically
eats the space.)

If you want to be a bit more explicit in what you’re doing, you can also pass the values in
as an array:
.\Named_Parameters_Example_7.ps1 @("C","D","E")

Note that in this case, you do have to qualify the drive letters as strings by using quotes
around them.

Conclusion
Hopefully, this article has given you some insight into the two methods of passing in
variables to PowerShell scripts. This ability, combined with the ability to read JSON files in
a previous article should give you a great deal of power to be able to control what your
scripts do and how they operate. And now I have a script to rewrite!

You might also like