PowerShell: An In-Depth Scripting Crash Course — Redmondmag.com

PowerShell: An In-Depth Scripting Crash Course — Redmondmag.com.


PowerShell: An In-Depth Scripting Crash Course

This extended Windows PowerShell column will jump-start your knowledge of this powerful technology.

More of you are getting used to Windows PowerShell and realizing its advantages. With that in mind, this month’s column is going to be a long one. This is a lightning overview of Windows PowerShell scripting, including how to build parameterized scripts. Over the next few months, I’ll focus on specific topics that build on this foundation.

If you’re not used to running Windows PowerShell commands in the console, you might find this too advanced, but try to plow through anyway. You should have a thorough understanding of Windows PowerShell security features. You should already know about execution policy, and know what setting you’re using. If you don’t already know the difference between “RemoteSigned” and “AllSigned,” and why one might be better than the other, you might not be ready for the following material.


You should also know how to execute scripts in the shell, and should recall that you always have to provide a path and filename in order to execute a script. Finally, you should also know the difference between running a script in the Integrated Scripting Environment (ISE) and the console. In the ISE, scripts run in the global scope. In the normal shell console, scripts get their own scope. I’ll review scope, but you should already have an idea of what it means and what it does.

If you’re not feeling quite up to speed, take a look at my book, “Learn Windows PowerShell in a Month of Lunches” (Manning Publications, 2011), and the companion Web site, morelunches.com, and see if those resources can help you build a better foundation.

Try to follow along as you read this column. Try the examples. If you type (or copy and paste) the script examples into the Windows PowerShell ISE starting on line 1, then your line numbers will correspond with the line numbers in the descriptions.

Windows PowerShell Script Files
A Windows PowerShell script file is nothing more than a plain-text file that has a .PS1 filename extension. The “1” doesn’t refer to the version of Windows PowerShell, but rather the version of the language engine. Windows PowerShell version 1 and 2 both use language engine version 1. That’s why both versions of the shell are installed into a v1.0 folder under \Windows\System32\WindowsPowerShell.

A Windows 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 (remember not to type the line numbers):


Now type those exact same lines into a script file, or the ISE script editing pane, and run the script. You’ll get different-looking results. Each time you hit Enter in Windows PowerShell, you start a new pipeline. Whatever commands you typed are run in that single pipeline. At the end of the pipeline, Windows PowerShell converts its contents into a text display. When you run the two commands in the normal console, you’ve done so in two distinct pipelines.

Windows 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. The Windows PowerShell 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. In this case, both commands ran in a single pipeline. That’s what happened when you ran the script.

The practical upshot of all this is that a script should produce only one kind of output. It’s a bad idea, due in large part to the limitations of the formatting system. There are other considerations, as well. You don’t want a script dumping several different kinds of things into the pipeline at the same time.

Focus on that as a rule for everything we’ll cover. A script should generate one, and only one, type of output. The only exception would be if it’s a script being used as a repository for multiple functions. In that case, each function should generate one, and only one, type of output.

Think of variables as a box. You can put one or more things, even dissimilar things, into this box. The box has a name, and in Windows 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.” If you want to reference the contents of the box, add a dollar sign: $var. You’ll often see Windows 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 Windows PowerShell that you want the contents, rather than the box itself. For example:

$var = 'hello'
$number = 1
$numbers = 1,2,3,4,5,6,7,8,9

Those examples show you how to place items into a variable using the assignment operator (=). That last example creates an array, because Windows PowerShell interprets all comma-separated lists as an array, or collection, of items. The first example assigns a string object, with the characters in the string contained within quotation marks.

There’s one aspect of Windows PowerShell that can confuse newcomers. Windows 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 plural variable name. The statement

$numbers = 1

is equally valid to the shell, as is

$numbers = 'fred.'

When a variable does contain multiple values, however, you can use a special syntax to access just a single one of them. You would use $numbers[0] as the first item, $numbers[1] is the second, $numbers[-1] is the last, $numbers[-2] is the second-last and so on.

Quotation Marks
As a best practice, use single quotes to delimit a variable unless you have a specific reason to do otherwise. There are three specific instances where you would want to use double quotes.

The first is when you need to insert a variable’s contents into a string. Within double quotes only, Windows 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 contents of that variable will replace the variable name and the $:

$name = 'Don'
$prompt = "My name is $name"

The $prompt will now contain “My name is Don” because $name will be replaced with the variable contents. This is a great trick for joining strings together without having to concatenate them.

Within double quotes, Windows PowerShell will also look for its escape character, the backtick or grave accent, and act accordingly. Here are a couple of examples:

$debug = "`$computer contains $computer"
$head = "Column`tColumn`tColumn"

In the first example, the first $ is being “escaped.” That removes its special meaning as a variable accessor. If $computer contained “SERVER,” then $debug would contain ‘$computer contains SERVER.”

In the second example, `t represents a horizontal tab character, so Windows 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 (bit.ly/sDbqJv).

Finally, use double quotes when a string needs to contain single quotes:

$filter1 = "name='BITS'"
$computer = 'BITS'
$filter2 = "name='$computer'"

In this example, the literal string is name=’BITS.’ The double quotes contain the whole thing. Both $filter1 and $filter2 end up containing exactly the same thing, but $filter2 gets there by using the variable-replacement trick of double quotes. Note that only the outermost set of quotes actually matters. The single quotes within the string don’t matter to Windows PowerShell. Those single quotes are just literal characters. Windows PowerShell doesn’t interpret them.

Object Members and Variables
Everything in Windows PowerShell is an object. Even a simple string such as “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 includes its properties and methods:

$var = 'Hello'
$var | Get-Member

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 a set of 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:

$svc = Get-Service
$name = $svc[1].name

Notice line two. 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.” This accesses just the name property. Line five illustrates how to access a method, by providing its name after a period, followed by 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 two in the following example won’t work the way you might expect:

$service = 'bits'
$name = "Service is $service.ToUpper()"
$upper = $name.ToUpper()
$name = "Service is $upper"

On line two, $name will contain “Service is BITS.ToUpper()” whereas on line four $name will contain “Service is BITS.”

Aside from their use with object methods, parentheses also act as an order-of-execution marker for Windows PowerShell, just like in algebra. In other words, parentheses tell the shell to “execute this first.” The entire parenthetical expression is replaced by whatever that expression produces. Here’s a mind-bending couple of examples:

$name = (Get-Service)[0].name
Get-Service -computerName (Get-Content names.txt)

On line one, $name will contain the name of the first service on the system. Reading this takes a bit of effort. Start with the parenthetical expression. That’s what Windows PowerShell will start with as well. The “Get-Service” resolves to a collection, or array, of services. The [0] accesses the first item in an array, so that will be the first service. Because it’s followed by a period, we know 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 line two, the parenthetical expression is reading the contents of a text file. Assuming the file contains one computer name per line, ‘Get-Content粕 will return an array of computer names. Those are fed to the “–computerName” parameter of “Get-Service.” In this case, the shell can feed any parenthetical expression that returns an array of strings to the “–computerName” parameter, because the parameter is designed to accept arrays of strings.

Scope is a programming concept that acts as a containerization system. Things like variables, aliases, PSDrives and other Windows PowerShell elements are all stored in a scope. The shell maintains a hierarchy of scopes, and has a set of rules that determine how scopes can interact and share information with each other.

The shell itself is a single scope, called the global scope. When you run a script, it constructs a new scope and the script runs within that. Anything created by the script, such as a new variable, is stored within the script’s scope. It isn’t accessible by the top-level shell.

When the script finishes running, its scope is discarded, and anything created within that scope disappears. For example, create a script that contains the following (don”t forget to not type the line numbers), and then run that script from the console window:

New-PSDrive -PSProviderFileSystem -Root C:\ -Name Sys
Dir SYS:

After running the script, manually run “Dir SYS:” and you should see an error. That’s because the SYS: drive was created in the script. Once the script was done, everything it created was discarded. The SYS: drive no longer exists. Not everything in the shell is scoped. Items such as modules are handled globally at all times. A script can load a module and the module will remain loaded after the script is done.

If a scope tries to access something that wasn’t created within that scope, then Windows PowerShell looks to the next-higher scope (the ‘parent’ scope). That’s why the Dir alias worked in that script you just entered. Although Dir didn’t exist in the script’s scope, it did exist in the next-higher scope: the global scope. A scope is free to create an item that has the same name as an item from a higher-level scope, though. Here’s another script to try:

New-Alias Dir Get-Alias

That may look weird, but the first time it ran “Dir,” it didn’t exist in the script’s scope. It used the higher-level Dir alias. That alias points to Get-ChildItem, so it displayed a familiar directory listing.

Then, the script creates a new alias named Dir. This points to Get-Alias. That’s what was run the second time. None of this affected the top-level Dir alias. Try running Dir in the shell after running the previous script, and you’ll still get a directory listing.

Scope can be especially confusing when it comes to variables. As a rule, a given scope should never access out-of-scope items, especially variables. There’s a syntax for doing so, such as using $global:var to forcibly access the global scope’s $var variable, but that’s a bad practice except under very specific circumstances.

Windows PowerShell Scripting Language
Windows PowerShell contains a very simplified scripting language of less than two dozen keywords. That’s a stark contrast to a full programming language such as VBScript, which contains almost 300 keywords.

Simplified though it may be, the Windows PowerShell language is more than sufficient to do the job. I’ll review its major scripting constructs now, although you can always get more help on these by reading the appropriate “about” topic within the shell. For example, help about_switch contains information on the Switch construct, while help about_if contains information on the If construct. Run help about* for a list of all ‘about” topics.

The If Construct
This is the Windows PowerShell main decision-making construct. In its full form, it looks like this:

If ($this -eq $that) {  
  # commands
} elseif ($those -ne $them) {
  # commands
} elseif ($we -gt $they) {  
  # commands
} else {  
  # commands

The “If” keyword is a mandatory part of this construct. A parenthetical expression follows that must evaluate to either True or False. Windows PowerShell will always interpret zero as False, and any nonzero value as True.

Windows 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.

You can go a bit further by providing one or more “ElseIf” sections. These work the same way as the If construct. They get their own parenthetical expression. If it’s True, the commands within the following curly brackets will execute. If not, they won’t.

You can wrap up with an Else block, which will execute if none of the preceding blocks execute. 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 four would execute — and nothing else. Windows PowerShell won’t even evaluate the second elseif expression on line five.

The # character is a comment character, making Windows 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:

if ($those -eq $these)

It doesn’t matter where you place the curly brackets. However, what does matter is that you be consistent in your placement so your scripts are easier to read. It’s also important to indent, to the exact same level, every line within the curly brackets


Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s