Chapter 3. PowerShell’s scripting language
Before we dive into scripting and toolmaking, we need to cover a few background concepts—some of which are unique to PowerShell. We’re also going to do a lightning overview of PowerShell’s scripting constructs. If this seems a bit brief, don’t worry—we’ll be re-explaining a lot of these when you see them in a more practical context. The idea now is to familiarize you with what’s ahead.
A PowerShell script isn’t exactly like a command-line batch file, and running a script isn’t precisely the same as running the same commands yourself in the same sequence. For example, open a console window and run the following, pressing Enter after each line:
Now type those exact same lines into a script file, or into the ISE’s script editing pane, and run the script. You’ll get different results.
In PowerShell, each time you hit Enter you start a new pipeline. Whatever commands you typed are run in that single pipeline, and at the end of the pipeline PowerShell converts the contents of the pipeline into a text display. When you ran the two commands in the normal console, you did so in two distinct pipelines. Therefore, PowerShell was able to construct a unique display for each set of output. When entered into a script, however, both commands ran in the same pipeline, and PowerShell’s formatting system isn’t sophisticated enough to construct the same unique output for two different sets of results. Try running this in the console:
Those results should look the same as they did when you ran the script containing those two commands. That’s because in this case both commands ran in a single pipeline—which is what happened when you ran the script.
The practical upshot of all this is that a script should produce one kind of output only. It’s a bad idea—due in large part to the limitations of the formatting system, but also due to other considerations—to have a script that’s dumping several different kinds of things into the pipeline at the same time.
Focus on that as a rule for everything that we’ll cover: A script should output one, and only one, kind of thing.
Variables provide a named, temporary place in memory that you can store objects—whether those are simple values like the number 5 or a collection of complex objects like the output of Get-Service.
Think of variables as a box, into which you can put one or more things—even dissimilar things. The box has a name, and in PowerShell that name can include almost anything. Var can be a variable name, as can {my variable}. In that second example, the curly brackets enclose a variable name that contains spaces—which is pretty ugly. As a good practice, stick with variable names that include letters, numbers, and underscores.
Using a variable’s name references the entire box, but if you want to reference the contents of the box you need to add a dollar sign: $var. Most commonly, you’ll see PowerShell variables preceded with the dollar sign because the whole point of using one is to get at the contents. It’s important to remember, however, that the dollar sign isn’t part of the variable name: It’s just a cue to tell PowerShell that you want the contents rather than the box itself.
These examples show how to place items into a variable, by using the assignment operator (=). The first example assigns a string object to the variable var, with the characters in the string contained within quotation marks. The second example creates a variable with an integer. Note the last example: It creates an array, because PowerShell interprets all comma-separated lists as an array, or collection, of items.
One thing that can sometimes confuse newcomers is that PowerShell doesn’t understand any meaning you may associate with a variable name. A variable like $computername doesn’t tell the shell that the variable will contain a computer name. Similarly, $numbers doesn’t tell the shell that a variable will contain more than one number—the shell doesn’t care if you use a variable name that happens to be plural. $numbers = 1 is equally valid to the shell, as is $numbers = 'fred'.
Variable names normally consist of just letters, numbers, and the underscore character. But ${this is also a legal variable name} is also a valid variable name. In this example, the curly brackets enclose the entire name. We don’t recommend using that approach—it’s confusing to read, and there’s really no need to have such a long variable name. But you may run across that in others’ scripts, so we wanted you to know what to look for.
When a variable does contain multiple values, you can use a special syntax to access just a single one of them. $numbers[0] gets the first item, $numbers[1] is the second, $numbers[-1] is the last, $numbers[-2] is the second to last, and so on.
PowerShell includes a number of commands for working with variables (run Get-Command –noun variable to see them), but by and large they’re unnecessary. PowerShell doesn’t force you to declare variables in advance and technically provides no way to do so. So we’ll just work with variables by referring to them, and for the most part we won’t use the cmdlets. Keep in mind that if you choose to use those cmdlets, they ask for a variable name, which does not include a dollar sign. If you run New-Variable –Name $x, you won’t be creating a new variable named x; you’ll be creating a new variable named whatever is inside of $x, because the dollar sign draws out the contents of $x.
Note
Variables are something you should already be pretty familiar with from using the shell as a command-line interface. Learn Windows PowerShell 3 in a Month of Lunches has a whole chapter on this introductory topic.
As a best practice, you should use single quotes to delimit a variable unless you have a specific reason not to. There are three specific instances where you’d want to use double quotes.
The first is where you need to insert a variable’s contents into a string. Within double quotes only, PowerShell will look for the $ and will assume that everything after the $, up to the first character that’s illegal in a variable name, is a variable name. The variable name and $ will be replaced with the contents of that variable.
$prompt will now contain My name is Don because $name will be replaced with the contents of the variable. This is a great trick for joining strings together without having to concatenate them. If you need to insert something more complex than a single variable’s contents, you can use a subexpression, for example:
The $() is the subexpression; anything inside it is evaluated as code, and the entire subexpression is replaced with the results of that code. Using this technique, you should almost never have to concatenate strings together—simply insert items into double quotation marks using either variables or subexpressions.
Within double quotes, PowerShell will also look for its escape character, the backtick or grave accent, and act accordingly. Here are a couple of examples:
In the first example, the first $ is being escaped. That removes its special meaning as a variable prefix, so if $computer contained 'SERVER', then $debug will contain computer contains SERVER. In the second example, `t represents a horizontal tab character, so PowerShell will place a tab between each Column. You can read about other special escape characters in the shell’s about_escape_characters help topic.
Finally, use double quotes when a string needs to contain single quotes:
In this example, the literal string is name='BITS' and the double quotes contain the whole thing. Both $filter1 and $filter2 end up containing exactly the same thing; $filter2 gets there by using the variable-replacement trick of double quotes. Note that only the outermost set of quotes matters when it comes to that trick—the fact that single quotes are used within the string doesn’t matter to PowerShell. Those single quotes are just literal characters; PowerShell doesn’t interpret them.
Everything in PowerShell is an object. Even a simple string like 'name' is an object, of the type System.String. You can pipe any object to Get-Member to see its type name (that is, the kind of object it is) as well as its members, which include its properties and methods.
Use a period after a variable name to tell the shell, “I don’t want to access the entire object within this variable; I want to access just one of its properties or methods.” After the period, provide the property or method name. Method names are always followed by parentheses (). Some methods accept input arguments, and those go within the parentheses in a comma-separated list. Other methods require no arguments, and so the parentheses are empty. But don’t forget the parentheses!
Notice line 2. It starts by accessing the first item in the $svc variable. The period means “I don’t want that entire object—I just want a property or method.” We’ve then accessed just the name property. Line 5 illustrates how to access a method, by providing its name after a period, and then following that with the parentheses.
A period is normally an illegal character within a variable name, because the period means we want to access a property or method. That means line 2 below won’t work the way you might expect:
On line 2, $name will contain Service is BITS.ToUpper() whereas on line 4 $name will contain Service is BITS.
Aside from their use with object methods, parentheses also act as an order-of-execution marker for PowerShell. Just like in algebra, parentheses tell the shell to “execute this first.” The entire parenthetical expression is replaced by whatever that expression produced. Here’s a mind-bending couple of examples:
On the first line, $name will contain the name of the first service on the system. Reading this takes a bit of effort: Start with the parenthetical expression, because that’s what PowerShell will start with as well. Get-Service resolves to a collection, or array, of services. [0] accesses the first item in an array, so that’ll be the first service. Because it’s followed by a period, we know that we’re accessing a property or method of that service rather than the entire service object. Finally, we pull out just the name of the service.
On the second line, the parenthetical expression is reading the contents of a text file. Assuming that file contains one computer name per line, Get-Content will return an array of computer names. Those are being fed to the -computerName parameter of Get-Service. Any parenthetical expression that returns an array of strings can be fed to the -computerName parameter in this case, because the parameter is designed to accept arrays of strings.
Take our tour and find out more about liveBook's features:
- Search - full text search of all our books
- Discussions - ask questions and interact with other readers in the discussion forum.
- Highlight, annotate, or bookmark.
We’re about to dive into PowerShell’s scripting constructs, and they require that you recall PowerShell’s comparison operators. Specifically, you’ll need to know the ones in table 3.1.
Table 3.1. Basic comparison operators
Operator |
Purpose |
Example |
---|---|---|
-eq | Equality (case-insensitive for strings) | "hello" –eq "HELLO" (True) 5 –eq 100 (False) |
-ne | Inequality (case-insensitive for strings) | "hello" –ne "HELLO" (False) 5 –ne 100 (True) |
-like, -notlike | Wildcard string comparison (case-insensitive) | "Power" –like "*ow*" (True) "Shell" –notlike "*he*" (False) |
-gt | Greater than (numbers, dates, and times) | 5 –gt 100 (False) |
-ge | Greater than or equal to | 50 –ge 10 (True) |
-lt | Less than (numbers, dates, and times) | 100 –lt 1000 (True) |
-le | Less than or equal to | 100 –le 100 (True) |
You can learn more about these and other PowerShell comparison operators by reading the about_comparison_operators help file in the shell.
Logical constructs are used to make decisions and to execute different commands based on the outcome of that decision.
This is PowerShell’s main decision-making construct. In its full form, it looks like this:
The If keyword is the only mandatory part of this construct. Following it is a parenthetical expression that must evaluate to either True or False—although PowerShell will always interpret 0 (zero) as False and any nonzero value as True. PowerShell also recognizes the built-in variables $True and $False as representing those Boolean values. If the expression in parentheses works out to True, then the commands in the following set of curly brackets will execute. If the expression is False, then the commands won’t execute. That’s really all you need for a valid If construct.
Note that you don’t necessarily have to put a comparison in those parentheses—so long as whatever you do put in there contains True or False. For example, if you have a variable $go_ahead that you know will contain either True or False, then this is a legal construct:
It isn’t necessary to put If ($go_ahead –eq $True), though doing so won’t hurt and will work properly also.
Optionally, you can go a bit further by providing one or more ElseIf sections. These work the same way: They get their own parenthetical expression, and if that’s True the commands within the following curly brackets will execute. If not, they won’t.
Finally, you can wrap up with an Else block, which will execute if none of the preceding blocks executed. Only the block associated with the first True expression will execute. For example, if $this did not equal $that, and $those did not equal $them, then the commands on line 4 would execute—and nothing else. PowerShell won’t even evaluate the second ElseIf expression on line 5.
Note that the # character is a comment character, making PowerShell essentially ignore anything from there until a carriage return.
Also notice the care with which those constructs were formatted. You might also see formatting like this from some folks:
It doesn’t matter where you place the curly brackets. But what does matter is that you be consistent about how you place them, so that your scripts are easier to read. It’s also important to indent, to the exact same level, every line within the curly brackets. The PowerShell ISE lets you use the Tab key for that purpose, and it defaults to a four-character indent. Indenting your code is a core best practice—fail to do so and you’ll have a tough time properly matching opening and closing curly brackets in complex scripts. Also, all of the other PowerShell kids out there will make fun of you, deservedly. Imagine looking at a script that’s poorly formatted:
That’s a lot harder to read, to debug, to troubleshoot, and to maintain. Although the space after the closing parenthesis isn’t necessary, it does make your script easier to read. The indented code isn’t necessary, but it makes your script easier to follow. Placing a single closing curly bracket on a line by itself isn’t required by the shell, but it’s appreciated by human eyes. Be a neat formatter, and you’ll have fewer problems in your scripts and in your life.
The Switch construct examines a single object, often contained in a variable, and compares it to a number of possible values. This is essentially like having an If statement with a whole bunch of ElseIf statements. Many people prefer to just use If and ElseIf and to ignore Switch completely. You’re welcome to do so. But you should be aware of what Switch does, so that you can recognize it in someone else’s script. Here’s the basic construct:
With this construct, $status_text will be assigned a value based on the value of $status. The Default section will run only if no other section matched the contents of $status. One reason to keep Switch in the back of your mind is that it has some unique capabilities not shared by the If construct, for example:
In this example, the Switch construct’s –wildcard option allowed us to use wildcard characters in the possible values. We set $result to be an empty string by default and then concatenated a value based on the contents of $servername. If $servername was DCFILE01, then $result would contain Domain Controller File Server. You see, Switch will execute each matching comparison, rather than stopping after the first match. That’s different from the If construct, which will only execute the first match.
If you don’t want Switch executing multiple matches, you can add a break keyword. We’ll cover that more toward the end of this chapter, but here’s a sneak preview:
We’re using an old C programming trick that works well in PowerShell: A semicolon separates the two commands within each condition section. It’s the same as if we’d formatted each on a separate line:
When our conditional code contains only a couple of commands, we often find that using a semicolon to separate them provides for a more concise and readable code listing, but you’re welcome to format your code however you like. Switch has other capabilities, too; read the about_switch help file in PowerShell to learn about them.
Looping constructs are designed to execute some action over and over, either a specified number of times or until some condition is met.
This is a primary looping construct in PowerShell. It’s designed to repeat a block of commands so long as some condition is True or until a condition becomes True. Here’s the basic usage:
In this variation of the construct, the commands within the curly brackets will always execute at least one time, because the While condition isn’t evaluated until after the first execution. You can move the While, in which case the commands will only execute if the condition is True in the first place:
Notice that this second example doesn’t use a comparison operator like -eq. That’s because the Test-Path cmdlet happens to return True or False to begin with; just as with the If construct, there’s no need to compare that to True or False in order for the expression to work. Remember, the parenthetical expression used with these scripting constructs merely needs to simplify down to True or False—if you’re using a command like Test-Path, which always returns True or False, then that’s all you need.
As always, there’s an “about” topic in the shell that demonstrates other ways to use this construct, along with information on one additional variation that uses the Until keyword.
This construct is similar in operation to the ForEach-Object cmdlet and differs only in its syntax. The purpose of ForEach is to take an array (or collection, which in PowerShell is the same as an array) and enumerate the objects in the array so that you can work with one at a time.
It’s easy for newcomers to overthink this construct. Here are a few things to remember:
- The fact that $services happens to be a plural English word doesn’t mean anything at all to PowerShell. That variable name is used to remind us, as human beings, that the variable contains one or more services. Just because it’s plural doesn’t make the shell behave in a special fashion.
- The in keyword on line 2 is part of the ForEach syntax.
- The $service variable is one we made up. It could as easily have been $fred or $coffee and it would have worked in just the same way. We chose $services because the variable name describes what’s in the variable; we chose $service because the name describes what the variable will hold—one service at a time. That’s entirely for our benefit—PowerShell doesn’t care what we call the variables.
- PowerShell will repeat the construct’s commands—the ones contained within curly brackets—one time for each object that’s in the second variable ($services). Each time, a single object will be taken from the second variable ($services) and placed into the first variable ($service).
- Within the construct, use the first variable ($service) to work with an individual object. On line 3, we’ve used the period to indicate that we don’t want to work with the entire object but rather want to work with one of its members—the Stop() method.
There are times when using ForEach is inevitable and even desirable. But if you have a bit of programming or scripting in your past, you can sometimes leap to using ForEach when it isn’t the best approach. The previous example isn’t a good reason to use ForEach. Wouldn’t this be easier?
The point here is to really evaluate your use of ForEach and to make sure it’s the only way to accomplish what you’re trying to do. Here are some instances where ForEach is probably the only way to go:
- When you need to execute a method against a bunch of objects, and there’s no cmdlet that performs the equivalent action.
- When you have a bunch of objects and need to perform several consecutive actions against each one.
- When you have an action that can only be performed against one object at a time, but your script may be working with one or more objects, and you have no way of knowing in advance.
This construct—similar to VBScript’s For...Next construct—is designed to execute the construct’s contents a specific number of times. Here’s the basic syntax:
For some starting condition, ($i=0), while some condition is True, ($i less than 5), do the code in the curly braces. Then increment $i by 1 ($i++).
PowerShell seems to always provide a lot of alternative ways to do things. For example, if you need to execute something 10 times, some folks will use a For construct, whereas others will do something like this:
That doesn’t technically use any constructs at all. It uses PowerShell’s range operator (.., or two periods right next to each other) to produce 10 objects (the integers 1 through 10) and then uses ForEach-Object to enumerate them. The Process script block of ForEach-Object will therefore execute 10 times. It’s up to you how to do this type of thing; if you’re browsing the internet for scripts, be prepared to run across any and all variations!
Break and Continue are two special keywords. You’ve already seen Break in the section on Switch, but here’s a bit more about it:
- Break will immediately exit any construct except the If construct. If you use Break within an If construct, and the If construct is nested within another construct, then it’ll break out of that parent construct. In other words, in the following example, once $i reaches 5, the loop will exit completely:
$i = 0
do {
if ($x –eq 5) { break }
$i++
} while ($i –lt 100) - If you use Break and there are no constructs to exit from, then it will exit the current script, ceasing execution.
The Continue keyword, when used within any looping construct, will immediately jump to the end of the construct and loop again (if the loop would normally continue). For example, the following would attempt to stop only the BITS service:
That’s just meant as an easy-to-read example; it’s certainly not as easy as running Stop-Service –Name BITS, but hopefully it illustrates how the Continue keyword works.
There’s no lab for you in this chapter; our goal was to expose you to some of these basics for the first time. You’ll be using them plenty in upcoming chapters and labs, and you should remember to refer to this chapter if you need a quick refresher in how each of these items works.