Windows PowerShellWindows PowerShell Constructs

Don Jones

Last month, I showed you some ways in which Windows PowerShell could be put to immediate use solving administrative tasks—without actually writing any scripts. However, while Windows PowerShell is an excellent interactive shell, you can really take advantage of its capabilities and automate more complex tasks when you start to utilize its powerful-yet simple-scripting language.

First, though, you may be asking yourself: does Microsoft really need another scripting language? After all, Microsoft has brought us KiXtart, a logon script processor, as well as Visual Basic® Scripting Edition (VBScript). The answer, however, is yes. Microsoft really did need another scripting language. I'll explain why.

The Windows PowerShell™ language needed to be simple and intuitive, so administrators could pick it up without a lot of training. It also needed to be pretty flexible, so it could accommodate all the powerful functionality Windows PowerShell itself can provide to users.

Because Windows PowerShell is based on the Microsoft® .NET Framework, its scripting syntax needed to be .NET friendly. In assembling a scripting language for Windows PowerShell, the designers selected a syntax that's essentially a very simplified C# (pronounced C-Sharp, one of the languages that ships with the .NET Framework). Why not stick with a VBScript-like syntax? The Windows PowerShell scripting language isn't actually all that different from VBScript, but by sticking more closely to C# syntax, PowerShell provides a sort of initial stepping stone for learning .NET Framework programming. If you ever decide to move up to Visual Studio® and start writing C# applications, much of your Windows PowerShell scripting syntax will move up with you.

One of the most important things in the Windows PowerShell scripting language-or any scripting language, for that matter-is its constructs. These are special language elements that allow Windows PowerShell to perform logical comparisons and take different actions based on the comparisons' results or that allow it to repeat one or more instructions over and over.

Thinking Logically

Logical comparisons are at the heart of many scripting language constructs, and Windows PowerShell is no exception. A comparison essentially looks at two values or objects and evaluates whether the comparison is True or False. For example, you could ask yourself, "Is this user's password expiration date the same as today's date?" The result will either be True if the dates are the same or False if they are different. Note that I'm capitalizing True and False, because they're terms that have special meaning within Windows PowerShell.

Here's an example of a real logical comparison you could perform within Windows PowerShell:

PS C:\> $a = 1
PS C:\> $b = 2
PS C:\> $a -eq $b
False

I've created a variable named $a and set it to contain the value 1. In Windows PowerShell, variable names always begin with a dollar sign, so they're easy to spot. The = is technically referred to as the assignment operator, because it's used to assign a value. Next, I create a second variable, $b, and assign it the value 2. Then comes the actual logical comparison-I ask Windows PowerShell to compare the contents of $a and $b using the -eq (equality) operator. PowerShell performs the comparison, determines that the two values are not equal, and displays the result: False.

The Windows PowerShell operators are a bit different from other scripting languages you may have seen, and are even different from C#. Most languages will use the = operator to perform equality checks as well as for value assignment; Windows PowerShell avoids confusion by having a dedicated operator for each function. Figure 1 shows the Windows PowerShell comparison operators, along with equivalent operators in other languages (like VBScript) that you may be familiar with already.

Figure 1 PowerShell Comparison Operators

Operator Name Description
-eq Equality Tests whether values are the same. Other languages may use = or == to test for equality.
-ne Not Equal Tests for inequality. Other languages may use <> or != to test for inequality.
-gt Greater Than Tests whether one value is larger than another. Other languages may use the > character.
-lt Less Than Tests whether one value is smaller than another. Other languages may use the < character.
-ge Greater Than or Equal To Tests whether a value is larger than or equal to another value. Similar to >= in VBScript and other languages.
-le Less Than or Equal To Tests whether a value is smaller than or equal to another value. Similar to <= in VBScript and other languages.

These comparison operators have another interesting feature. Take a look at this comparison:

PS C:\> $a = "TechNet"
PS C:\> $b = "technet"
PS C:\> $a -eq $b
True

By default, the comparison operators are case-insensitive, meaning the capitalized version of TechNet is seen as equivalent to "technet". That's convenient, because in most administrative tasks you don't care about the case of the letters. However, sometimes you may, and you can ask Windows PowerShell to perform a case-sensitive comparison by prepending the letter c to the comparison operator:

PS C:\> $a -ceq $b
False

Likewise, if you're ever concerned or confused about whether or not Windows PowerShell is going to perform a case-insensitive comparison, you can force it to do so by prepending the letter i:

PS C:\> $a -ieq $b
True

Just remember that logical comparisons always result in one of two values: True or False.

Making Decisions

Now that you know how to write logical comparisons, you can start using them in constructs. The first construct I'll show you allows Windows PowerShell to make decisions based on a comparison. It's called an If construct, and there are a few variations of it. Here's the simplest:

PS C:\> if ($a -eq $b) {
>> Write-Host "They are equal"
>> }
>>
They are equal

There are some interesting things to note here. First, the variables $a and $b still contain the values "TechNet" and "technet", respectively. I started the construct by using the If keyword. Following it, in parentheses, I entered the logical comparison I wanted to perform. Following that is a curly brace, which signals the start of what I'll call the conditional code-the code that Windows PowerShell will execute if the comparison returns a result of True. You know from the earlier example that this comparison does return True, so I'd expect the conditional code to execute. I type my conditional code, Write-Host "They are equal", and press Enter. Finally, I end the conditional code section by typing a closing curly brace, and tapping Enter twice. (The second Enter on a blank line lets the parser know that I'm done writing and ready for it to execute the code.)

Notice that this construct isn't running from a script file. I simply typed it into the Windows PowerShell com-mand line. This is what makes Windows PowerShell unique in the world of Windows scripting: scripts can be created interactively, as well as put into a file for permanent storage.

As soon as I typed the opening curly brace and pressed Enter, Windows PowerShell displayed a >> prompt. That's its way of saying, "I recognize that you're inside a construct, and I'm ready for you to start typing whatever goes inside the construct." After I typed the closing curly brace and tapped Enter twice, Windows PowerShell immediately executed the construct, determined that its logical comparison was True, and executed the conditional code. You can see this because "They are equal" was displayed before PowerShell returned to its normal prompt. Using Windows PowerShell to script interactively allows you to quickly test bits of code before assembling them into a more permanent script, which makes both learning and debugging easier.

I should point out that Windows PowerShell isn't especially picky about things like pressing Enter. For example, this is functionally the same as the previous example:

PS C:\> if ($a -eq $b) { Write-Host "They are equal" }
They are equal

Because I typed this all on one line, Windows PowerShell didn't need to display the special >> prompt; it simply executed the construct when I pressed Enter at the end of the line. How did Windows PowerShell know it was OK to execute the construct? Because it was complete at that point-the closing curly brace had been typed.

I alluded to other variations of the If construct. Here's a full example, presented as it might appear in a PS1 script file, rather than in the shell:

if ($a -eq $b) {
  Write-Host "They are equal"
} elseif ($a -lt $b) {
  Write Host "One is less than the other"
} else {
  Write Host "One is greater than the other"
}

The construct starts out the same, by using the If keyword. However, in the event that the comparison is False, I've provided another comparison by using the Elseif keyword. If that second comparison is also False, then my last keyword, Else, provides a final set of code that will execute.

Repeating Yourself

Windows PowerShell contains a couple of constructs for executing code over and over, until some comparison is either True or False. Programmers call these loop constructs. Even better, one of the most useful loop constructs is capable of enumerating the objects in a collection and executing one or more lines of code for each object. Appropriately enough, the construct is called a foreach construct, and it looks like this:

PS C:\> $names = get-content "c:\computers.txt"
PS C:\> foreach ($name in $names) {
>> Write-Host $name
>> }
>>
don-pc
testbed

I started by asking the Windows PowerShell Get-Content cmdlet to retrieve the contents of the c:\computers.txt file, a file I created myself that contains one computer name per line. Windows PowerShell treats each line as an object, so the file is essentially a collection that contains those objects. The collection winds up in the variable $names. Using the Foreach keyword, I tell Windows PowerShell to enumerate the $names collection, using the variable $name to represent the current object each time the loop executes. The loop code goes inside curly braces. So, for each name in the file, I'll output the name to the command line. And, as you can see from the output following the construct, that's exactly what Windows PowerShell does. You can see how this would provide an obvious benefit in administrative scripting: you could easily construct a list of server names, for example, and have Windows PowerShell retrieve information from each one in turn.

Real-World Constructs

So let's take logical comparisons, the If construct and the foreach construct, and do something useful. I want to quickly check the status of the Messenger service on a set of servers. I expect that the service will be stopped on most of the servers, so I don't want Windows PowerShell to list all the servers where the service is in the state I expect; I only want it to list the servers where the Messenger service is actually started because those are servers I need to do something about.

I know that the Windows PowerShell Get-Service cmdlet can help me retrieve the information I need for the local computer. Unfortunately, however, it can't reach out to a remote computer, which is really my goal. Happily, I can also access the same information via Windows Management Instrumentation (WMI), using the Get-WMIObject cmdlet, which lets me work with a remote computer. So here's the script:

$names = get-content c:\computers.txt
foreach ($name in $names) {
  $svc = get-wmiobject win32_service '
   -computer $name -filter "name='messenger'"
  if ($svc.started -eq $True ) {
    write-host "$name has Messenger running"
  }
}

Notice the ' character on the third line? It tells Windows PowerShell that the next line is a continuation. It's useful in cases like this where the entire line wouldn't fit in the magazine without wrapping. Also notice that my If construct compares $svc.started to $True. The variable $True is a special variable in Windows PowerShell that represents the Boolean True value. (A companion variable, $False, represents Boolean False.) Actually, I could have done a little shortcut there:

$names = get-content c:\computers.txt
foreach ($name in $names) {
  $svc = get-wmiobject win32_service '
   -computer $name -filter "name='messenger'"
  if ($svc.started) {
    write-host "$name has Messenger running"
  }
}

Remember that the If construct's condition simply needs to be True or False. Normally, you get True or False by comparing two values, as I did in the first version of this script. However, because the Started property is either True or False, there's no need to actually compare it to True or False.

One Useful Tool

So there you have it-a simple, useful administrative tool that utilizes constructs to do its job. Whether you type it into Windows PowerShell interactively, or save it in a PS1 file for easy reusability, it's a handy tool to check the status of a service on various computers and a great demonstration of how constructs can help automate administrative tasks.

Don Jones is the Director of Projects and Services for SAPIEN Technologies, and coauthor of Windows PowerShell: TFM (SAPIEN Press, 2006). Contact Don through his Web site at ScriptingAnswers.com.

© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.