Tip

Back to Basics – Variable Names

$x and $z and $foo just aren’t going to cut it in your production scripts. 

Andy Schneider tweeted during the Scripting Games that there was no penalty for providing a descriptive variable name.  He was right and not quite right enough.  Twitter does not provide you the opportunity to share more than a sound bite in one tweet, so he really could not have commented much further there, but he did in a post that expanded on what judges were looking for in style for the Scripting Games

The content of that post is not limited to the Scripting Games, but should be applied to our scripting experience.

Down to Business

We’re going to focus on one particular section of that blog post.

Use variables that make it easy to understand what you are doing in your script. Comments in code are great, but your code should be readable and understood without them. In general, I would choose good variable names and clear processes over heavily commented code.

Looking for Trouble

If you find yourself working on a script or function and begin to think, "I need a comment here to make sure it is clear what I’m doing," then you are already headed for trouble.  (Function, cmdlet, and script names are another portion for another day.)

Descriptive variable names can serve as inline documentation on what that variable represents.  A descriptive variable makes it much easier to follow that variable through the script. 

Remember that you might not be (and will likely not be) the last person to look at that script and need to figure out what is going on internally. 

If by chance you are the last person to look at that script, do you think you are going to be able to have the same contextual understanding one month after you wrote the script?  Two months?  Six months or more?  I know that when I review scripts I wrote years ago, before I followed my own advice better, it can take me a while to figure out what is really going on, even in some medium complexity scripts (where I should really just be able to scan and understand).

Why Aren’t Comments Good Enough

Comments are not the workaround for poor variable naming.  Scripts are rarely static entities.  As requirements change, scripts are updated, sections of functionality are changed and the objects that are operated on can change as well.  Comments are often the last thing to be reviewed (if at all), so the likelihood of ending up with stale and misleading comments goes up with each revision.

Another reason that comments aren’t good enough is that they clutter up the script file.  Comment-based help is one thing (and that is localized to one place in the file, not spread across the script.  When I’m reading a script and there are lines upon lines of comments, it really gets in the way of understanding what is happening in the script.  PowerShell is rather unique in the case, since the syntax actually lends itself well to self-documenting code (with how the command naming works). 

Naming Guidelines

These are some of the guidelines I try to follow when naming variables.

Do Not:

  • Pluralize variable names that are not arrays.
  • Pluralize variable names that are used as parameters.
  • Reuse counter variable names.  If you have a variable acting as a counter for a loop, so not reuse that variable name (you may have unexpected results if you don’t clean up after the previous loop).
  • Use generic names like $Results or $Output.
  • Use custom abbreviations.  Example: $CN should not be used for $ComputerName.

Do:

  • Describe the instance (or instances) of object(s) contained in the variable
  • Reference similar values consistently.  If you have a variable $ComputerName which has an array of computer names, don’t also have a variable $server or $servers that refer to the same data.
  • Use singular nouns.  This maintains consistency with parameter names which by best practice are singular.  The PowerShell convention is that variables with a singular noun can contain one or more objects.
  • Consider that you might be reading this script at 3 in the morning during an active outage.  Pick names that will help you know what is going on and get your work done, so you can go home.
  • (optional) Use company and industry accepted abbreviations or acronyms.  (For example $VM conveys the same information $VirtualMachine to systems administrators.

I’d like to hear what guidelines you’ve followed or what you thing of mine, so post a comment!

Parameter Validation

Glenn Sizemore wrote a great Scripting Games wrap up post about some of the parameter validation options that are available.  Go read it, I’ll wait.

The Smackdown – Ensuring You Have A Value

Very often you need to make sure that a parameter will not have a null value.  There are a couple of options in making sure you have valid content.

ValidateNotNull vs. ValidateNotNullOrEmpty

When you start to look at some of the validation parameters (in about_Advanced_Functions), the two that leap out are the ValidateNotNull and ValidateNotNullOrEmpty. 

Just looking at that names hints that there is something different about them, but it might not be immediately obvious.

ValidateNotNull will check if a parameter value is null when it is assigned.  If it has a null value, the runtime will throw an error.

ValidateNotNullOrEmpty has some additional checks to help with arrays and strings.  For example, if my function takes a parameter called ComputerName (like

param(
  [parameter()]
  [string[]]
  $ComputerName
)

and I apply the ValidateNotNull parameter,

param(
  [parameter()]
  [string[]]
  [ValidateNotNull]
  $ComputerName
)

I will only get an error if I pass $null

c:\scripts\MyScript.ps1 -ComputerName $null

MyScript.ps1 : Cannot validate argument on parameter 'ComputerName'. The argument is null. Supply a non-null argument and try the command again.

But, if I pass an empty array, or an empty string, the command will accept the input.

c:\scripts\MyScript.ps1 -ComputerName ""
c:\scripts\MyScript.ps1 -ComputerName @()

To use validation to protect against those types of input, I can use ValidateNotNullOrEmpty

param(
  [parameter()]
  [string[]]
  [ValidateNotNullOrEmpty]
  $ComputerName
)

Then I will receive an error like this.

MyScript.ps1 : Cannot validate argument on parameter 'test'. The argument is null, empty, or an element of the argument collection contains a null value. Supply a collection that does not contain any null values and then try the command again.

ValidateNotNull(OrEmpty) vs. Mandatory

Neither ValidateNotNull nor ValidateNotNullOrEmpty force a parameter to be declared.  The validations only have an impact if values (or lack thereof) are actually declared or passed in via the pipeline.  If you would like to make a parameter required, you can set the Mandatory flag in the Parameter attribute.

param(
  [parameter(Mandatory=$true)]
  [string[]]
  $ComputerName
)

This will make the parameter required and does protect against both empty strings and empty arrays, so you will not need to use either of those validation attributes. 

NOTE: Other validations can still be done (like ValidateSet, ValidateRange, and ValidateScript).

Working the Pipeline

The pipeline is a central concept in PowerShell.  It enables you to chain the output from one command to the input of the next, making automation much smoother.  Making your scripts and functions play nicely with the pipeline can mean really impact the ease of use and adaptability of your functions.

Finding the Value

In scripts and advanced functions, declaring a parameter whose value can come from the pipeline is pretty straightforward.  You can specify whether you are looking for the full object

[parameter(ValueFromPipeline=$true)]

or if you would like the value to be filled in by a property on an incoming object

[parameter(ValueFromPipelineByPropertyName=$true)]

The Hidden Value

When you enable a parameter to take a value from the pipeline (or a property on the pipeline), you gain a hidden benefit – Scriptblock Parameters.

Transforming Input

Scriptblock parameters allow you to modify the input to a parameter based on the incoming object by specifying a scriptblock instead of a value to a parameter.

Get-ChildItem C:\TestFiles\*.doc | Rename-Item -NewName {"New-$($_.name)"}

That’s a pretty trivial example, but since we are providing a scriptblock, we can actually do anything that PowerShell allows in defining a new input for that parameter.

Checking Out The Environment

If you’ve worked with cmd.exe or other shell environments, you might be familiar with environmental variables.

PowerShell exposes these variables in two different ways. (Actually it’s the same way, but don’t tell anyone..)

So, Spit It Out Already..

First, I can access environmental variables through the provider.

dir env:\

All my standard commands in navigating and retrieving items from a provider (Get-Item, Set-Item, New-Item, etc..) work in interacting with these variables.

And The Other One?

The other access method also uses the provider, but it accesses it in a bit different way..  By using the "$" and the PSDrive name.

$env:username

NOTE: This syntax can be used with other providers as well (eg $c:Windows)