In this article, I want to show how I customize scripts that are useful in my daily work by using parameters. This allows me to use one script over and over with slight changes.
I will start with a look at how I write scripts, and then look at the ways that you can adapt them to use parameters in your own work. I'll talk briefly about why parameters are useful and then look at simple parameters, naming them, and using defaults.
Script Execution in Files
I tend to write scripts to solve a particular task. Each script is its own file, and I save them as some name with the .ps1 extension. Then when I need the script, I can call the filename from PowerShell. As an example, I might have a script that gets the latest file in a folder and I want to customize this for each folder in which I might run it.
Often when I write PowerShell, I do it at the command prompt, working out some of my problems. Once I think I understand the logic, I'll put the entire thing in a .ps1 file. Here's a short example. I was working through some file manipulation, so I type in the CLI a bit.
I do make mistakes, so it's easier for me to work in the CLI to realize that Get-ChildItem works better than Get-Content. Once I have this, I can put this into a script file. Here I have that code, with a bit more, in the ISE.
That works OK, but it can be a pain to edit the files each time to get them to work as you intend. Here I might need to rename the $filetype and $prefix variables.
We often want to just call a script, but have it react differently to meet our needs. In this case, I'm using this archive files based on some year and month. What I need to do is parameterize this script.
The Simple Way To Use Parameters - $args[]
The first way I learned was the args[] set of parameters. This args[] collection is populated with the parameters passed into a file. This is an array, that you can access by looking for the particular number. It is important to note that PowerShell is a zero-based array language, as you can see below.
Essentially, I can get parameters like this:
- $args[0] - first parameter passed in
- $args[1] - second parameter passed in
- $args[2] - third parameter passed in
- etc.
In my script, I have to know how many parameters are being used. If I want to know if a parameter is missing, I can check the length for 0 like this:
$i = $args[3] if ($i.Length -eq 0) {write-host("Missing parameter") } else {write-host("Fourth parameter: $i")}
Or I can check for a null value, like this:
$i = $args[3] if ($i -eq $null) {write-host("Missing parameter") } else {write-host("Fourth parameter: $i")}
In both these cases, I will get output that the parameter is missing, like this:
I could edit my script like this:
$filetype = $args[0] $prefix = $args[1] $a = Get-ChildItem . foreach ($i in $a) { if ($i.extension -eq $filetype) { $newname = $prefix + $i.Name Rename-Item $i $newname } }
Then the first parameter passed in is used for the extension test and the second for the prefix.
This isn't a great way of getting parameters. For one thing, what if I need a parameter to be passed in? I could write a script that prints something and then does a Read-Host, but that's unnecessary programming. Instead, I should use the more formal named parameters in my script.
Named Parameters
A better way of getting parameters involves using the param() function. This allows me to specify the parameters used by the script. I do this in the following way. I've edited my test script to ask for parameters. In this case, I use the variable name I want inside the param call. Then each parameter is bound to the variable automatically. Here is the script:
param($extension, $prefix ) if ($extension -eq $null) { write-host("Missing extension parameter") } else { write-host("Extension Parameter: $extension") } if ($prefix -eq $null) { write-host("Missing prefix parameter") } else { write-host("Prefix Parameter: $prefix") }
Now if I pass in values, I see the parameters bound to variables or missing.
This avoids the variable assignment, and more importantly, I can now pass these in with names, in any order I choose, as you can see below. Here I give the variable name (minus the $) and then the value. PowerShell will pick these up and assign them accordingly.
That's good for users in one sense, but it doesn't do a great job of helping me ensure the user enters these parameters. Instead, what I should do is require or not require parameters. I can do this by marking a parameter as mandatory. By default, parameters are optional, so I don't need to mark them if they are. For mandatory ones, I use the [Parameter(Mandatory)] prefix with the name. I'll change my script to show this:
param([Parameter(Mandatory)]$extension, $prefix ) if ($extension -eq $null) { write-host("Missing extension parameter") } else { write-host("Extension Parameter: $extension") } if ($prefix -eq $null) { write-host("Missing prefix parameter") } else { write-host("Prefix Parameter: $prefix") }
Now if I run the script without supplying the extension parameter, PoSh asks me. Even if I name the optional parameter, I get asked. As you can see, the optional parameter below is either marked as missing (first call) or assigned the value from the named call (second call).
Using these named parameters, the user gets prompted if they are mandatory, and now rename script is more useful because I can give the user a name that makes sense and have PoSh prompt them if the parameters are missing. Here's an updated script:
param( [Parameter(Mandatory)]$FileExtension, [Parameter(Mandatory)]$ArchivePrefix ) $a = Get-ChildItem . foreach ($i in $a) { if ($i.extension -eq $FileExtension) { $newname = $ArchivePrefix + $i.Name Rename-Item $i $newname } }
If I call this with no parameters, I get asked for both (I entered "txt" and "202106" below).
Default Values for Parameters
This technique works well, but what if I don't necessarily need the user to make a choice. Perhaps I expect that most of the time I'll be dealing with csv files, so why make the user type this in? For that matter, why need the period? I can fix my script in a few ways. First, let's ensure that I add the period for the file extension. I can make a quick IF test to see if there is a period, and if not, include it.
Second, I can add a default value for the parameter. I do this with a simple assignment in the parameter definition, as shown below. In this case, I'll add a default of CSV to the extension parameter. Note that since I've added a default, I removed the mandatory marker. If I didn't do this, PoSh would still ask for the parameter.
param( $FileExtension="csv" , [Parameter(Mandatory)]$ArchivePrefix ) if ($FileExtension.substring(0,1) -ne ".") { $FileExtension = "." + $FileExtension } $a = Get-ChildItem . foreach ($i in $a) { if ($i.extension -eq $FileExtension) { $newname = $ArchivePrefix + $i.Name Rename-Item $i $newname } }
Now when I execute this script, I get the behavior I want, without forcing me to specify the extension if I don't want it.
Adding Help
I often forget what each script does, and what parameters are needed. This is understandable across lots of scripts. As a result, I've learned to add help. For parameters, I add a .Parameter section in the header. I haven't written about headers yet, but for now, I've included a simple on in my script. I give the name of the parameter and then a description.
<# .PARAMETER FileExtension The extension of the files to be checked. This can include or exclude the period. The default here is CSV. .PARAMETER ArchivePrefix A string value that is prepended to files matching the extension parameter. .EXAMPLE .prefix_filenames.ps1 -ArchivePrefix "202106_" This will add 202106_ to each file with the default csv extension. #> param( $FileExtension="csv" , [Parameter(Mandatory)]$ArchivePrefix ) if ($FileExtension.substring(1,1) -ne ".") { $FileExtension = "." + $FileExtension } $a = Get-ChildItem . foreach ($i in $a) { if ($i.extension -eq $FileExtension) { $newname = $ArchivePrefix + $i.Name Rename-Item $i $newname } }
If I run Get-Help for my script, with the -detailed flag, I see my parameter descriptions listed. This is helpful when I want to quickly check that this script does. I'll cover help in more detail in the next installment of this series.
Summary
There is more to learn about parameters, but using the name of the variable as a declared parameter, adding the mandatory flag if needed, and including some defaults are things that will solve most of your basic PoSh scripting needs.
The Help section should be included in your scripts, as you will quickly lose track of all the scripts you write, and since you'll need to add a reminder at the top anyway, you might as well do it in the format that PoSh expects. That way others can use Get-Help to find out what your scripts does. I'll cover this in more detail in the next article.