Professional Documents
Culture Documents
17 September 2019
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.
$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.
$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:
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
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.
param ($param1)
write-host $param1
.\Named_Parameters_Example_1.ps1
.\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:
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.
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:
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.
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.
With the flexibility of PowerShell and quoting, you can do something like:
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.
What if you don’t control the data being passed, and the passing program passes items in
quoted strings?
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
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.
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.
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:
It will do exactly what you think: use the passed in servername value of HAL and the
default environment of Odyssey.
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.
.\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:
And PowerShell won’t prompt for the servername since it’s already there.
.\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:
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.
.\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!