Welcome to the first post in my Next-Level Scripting series where I show you the PowerShell scripting techniques that take your scripts to the next level - a level defined by concise, well-written code that conforms to best practices and uses the built-in tools in PowerShell whenever possible to handle common scripting tasks.
In today's post we're going to look at how to use parameters in PowerShell to tackle a very common scenario - getting input from a user.
Being the hater of inefficiency that you are, you're writing a script to create the home drive folder for new users which you're going to give to another employee to run who is not overly technical. The script needs to take the username and the user's department as input and then create the folder, share it, set permissions etc. The question we will tackle is - how do you get the information from the person running the script?
One way to get the username and department would be this:
| 001 002 | $userName = Read-Host "Enter the username" $department = Read-Host "Enter the user's department" |
So far, so good - when you run the script you'll get a prompt for the username and department. Now let's say you wanted to do some basic checking to verify that the username entered is valid, based on your company's standards for usernames and departments:
- Usernames are a maximum of 8 characters long
- Department must be either FIN, HRD, ITD, PAY or MKT
- You also want to verify that the person running the script didn't just press Enter to your prompts, leaving you with a blank username.
You might do something like this to perform these checks:
| 001 002 003 004 005 006 007 008 009 010 011 012 | if ($userName.Length -gt 8 -or $UserName.Length -lt 1) { Write-Host -ForegroundColor Red "Error in username, please try again. Usernames must be 1 to 8 characters long." exit } if ($department -ne "FIN" -or $department -ne "HRD" -or $department -ne "ITD" -or $department -ne "PAY" -or $department -ne "MKT") { Write-Host -ForegroundColor Red "Error in department, please try again. Department must be either FIN, HRD, ITD, PAY, or MKT " exit } |
Looking good so far - we're prompting for a username and department and if the user gets either of those wrong we warn them and exit the script so they can try again. So what's the problem? After all, we've achieved our goals for getting the information we needed for our beautiful script to run! Yes, this approach works, but it's not how a well-written script should ask for this type of information and validate it. If I was interviewing someone who said they had some good PowerShell skillz I would have serious doubts about their knowledge of PowerShell if they used the above approach. So what's the right way to do it? Enter the mighty PARAMETER…
The param Block
With the proper use of parameters, this is how our script (called New-HomeDrive.ps1) would be run: New-HomeDrive.ps1 -Username bpitt -Department FIN
So how do we get our script to the next level? We use what's known as a param block at the beginning of our script. Before I show you the parameter block, let's recap our requirements:
- 1 to 8-character username, must be present
- Three-letter department, must be from set FIN, HRD, ITD, PAY or MKT, must be present
This is how we would define the parameters for our script:
| 001 002 003 004 005 006 007 008 009 010 011 | Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [ValidateLength(1,8)] $UserName, [Parameter(Mandatory=$True)] [ValidateSet("FIN","HRD", "PAY", "MKT", "ITD")] $Department ) |
Let's see how this takes care of our requirements by looking at each parameter line-by-line.
Parameter 1 – UserName
Parameter 1 is defined by the following lines of code:
| 001 002 003 004 | [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [ValidateLength(1,8)] $UserName, |
Requirement 1 – Parameter must be used
The use of Parameter(Mandatory = $true) tells PowerShell that this parameter must be used. How is this enforced if we don’t specify it?
This is what happens if we run the script without specifying the parameter:
We've now ensured that the script cannot run without this parameter being present. If you run the script without specifying the parameter, PowerShell will do all the heavy lifting for you and ask the user for it. What's not to like about that?
We're still not in the clear yet. What if the user running the script just presses Enter to the prompt without typing anything in? That is the second part of meeting our requirement and is achieved with this line of code: [ValidateNotNullOrEmpty()]
What we are doing here is telling PowerShell that this parameter cannot be empty. So if we were to just press Enter to the prompt shown above, we would get this message:
Cannot validate argument on parameter 'UserName'. The character length (0) of the argument is too short. Specify an argument with a length that is greater than or equal to "1", and then try the command again.
Requirement 2 – Parameter must be 1 to 8 characters long
This line of code – [ValidateLength(1,8)] - tells PowerShell that this argument must be 1 to 8 characters long. Anything below or above that threshold will trigger an error message, such as this one:
Cannot validate argument on parameter 'UserName'. The character length (9) of the argument is too long. Shorten the character length of the argument so it is fewer than or equal to "8" characters, and then try the command again.
So with this one simple line of code we've taken care of the length requirement and as a bonus you get the error message handled for you, without having to type any extra code!
Parameter 2 – Department
Parameter 2 is defined by the following lines:
| 001 002 003 | [Parameter(Mandatory=$True)] [ValidateSet("FIN","HRD", "PAY", "MKT", "ITD")] $Department |
Let’s see how this parameter takes care of our requirements. We’ve already covered how to make a parameter mandatory with our first parameter so let’s look at the second requirement
Requirement - Must be one of the following: MKT, FIN, HRD, PAY, MKT, ITD
This is one of my favourite features of parameters in Powershell - the ability to define a list of possible choices. We do this with the following line of code:
[ValidateSet("FIN","HRD", "PAY", "MKT", "ITD")]
If the user enters anything other than one of those six choices, their input will be rejected with this error message:
Cannot validate argument on parameter 'Department'. The argument "Finance" does not belong to the set "FIN,HRD,PAY,MKT,ITD" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.
In the example above, our user typed Finance instead of FIN which is a perfectly reasonable thing to do, except it's not what our script is expecting! Which leads me to what I think is the most powerful feature of ValidateSet - documentation. Imagine that you were handing this script over to another user to run and you had done your department check the old-fashioned way like I did at the beginning of this post. You would need to provide the user with a list of what the choices are when they get prompted for the department. If you ever added support for another department to your script you would need to update the documentation and make sure the user is aware of the change.
Not so when you're using ValidateSet! After the user types -Department in the PowerShell prompt, all they need to do is hit TAB and PowerShell will cycle through all the available choices. This really is an incredibly powerful tool because now the choices are self-documented and if you add another department to your script it will show up as the user is cycling through the choices you have programmed in. No need to update documentation and hope the user gets it right.
Conclusion
We've taken our home drive creation script to the next level in this post by using the built-in features of PowerShell to handle our parameters rather than writing our own code to do it. When compared to the first method I demonstrated, the param block method is more efficient, scalable and robust and is the one that a "Next Level" scripter would use. Stay tuned for the next post in the Next Level Scripting Series – the use of the switch parameter.
No comments:
Post a Comment