Windows scripting has long been the butt of jokes from UNIX system administrators. This has been the case for several reasons; chief among them is that Windows has been lacking many of the key features that make scripting truly useful. However, Microsoft is now not only catching up to but actually surpassing other OSs' scripting capabilities with its new Windows PowerShell scripting environment. Formerly code-named Monad, PowerShell delivers several scripting capabilities that Windows has never had and greatly improves some existing features. Even though PowerShell is still a release candidate, it's useful to know how PowerShell is filling in the capability gaps and how it works.

Filling In the Gaps
Microsoft designed PowerShell to provide a more powerful scripting and command environment than was previously available in Windows. The design team members had a number of goals in mind. In summary, they wanted to make the Windows scripting environment more powerful and easier to use than the scripting tools in any other OS. They also wanted to build an environment that would be usable for systems administration tasks and application development—and one that would integrate with the Microsoft .NET Framework. Meeting these goals meant that Microsoft had to address some capabilities that have been missing from Windows scripting tools. To fill these capability gaps, Microsoft incorporated into PowerShell the best aspects of a wide variety of commandline environments:

  • Composition. Composition lets you create complex commands by stringing together lots of little commands, each of which typically does only one task. UNIX shells deliver this capability in spades. Most UNIX administrators use composition every day, even if they don't know that's what it's called. The original designers of the UNIX shell environment had an explicit goal of making the shell use lots of small, single-purpose commands that administrators could combine in various ways. This stood in sharp contrast to other environments available at the time, which tended to use large, complex command structures.
  • Pipelining. Pipelining lets you take the output of one command and pass it as input to the next command in a composition. The command interpreter in current Windows OSs does this in a very limited manner. For example, if you run the command dir | more the output of the Dir command will be piped to the More command. However, most Windows commands don't support pipelining (or the close cousins, output and input redirection).
  • Object orientation. PowerShell commands output objects, not blocks of text. Objects have properties (items you can view and change) and methods ( executable actions). For example, you can use the get-mailbox command to get a list of mailbox objects, which you can then manipulate in a variety of ways. Get-mailbox doesn't return a list of names of mailboxes; it returns objects that represent the mailboxes themselves. As you'll see in the "PowerShell in Action" section, having commands that return sets of objects gives you a huge degree of power.
  • Safety. Most of the PowerShell commands that create or change objects support two flags: confirm and whatif. When you set the confirm flag, you'll be asked whether you really want to perform the specified action. When you set the whatif flag, you'll be shown what happens without actually changing anything. These flags are invaluable because they give you a way to test changes before they actually take place.
  • Consistent structure and consistent naming. Almost all of the PowerShell commands use a consistent verb-object structure and follow consistent naming patterns. To boot, there aren't that many verbs. Because of the consistencies and the small number of verbs, it's easy to figure out how to construct a command if you know the object you want to work with.
  • Robust online help. PowerShell provides robust online help for objects, commands, and the shell environment itself.

PowerShell 101
PowerShell is made up of lots of objects called cmdlets (pronounced command-lets). Cmdlets are analogous to DLLs in that they're executable objects that can't run on their own. For that reason, cmdlets are run from within the PowerShell command interpreter. PowerShell Release Candidate 1 (RC1) includes more than 120 cmdlets.

Cmdlets can have parameters, which can be named or positional (i.e., PowerShell interprets them according to their location on the command line). For example, when you use the get-mailbox cmdlet, the first parameter always specifies what mailboxes you want to get. You can include other parameters in this cmdlet by specifying their names.

There are several built-in cmdlets that you'll use often enough to warrant a mention here:

  • The format family of cmdlets (i.e., format-table and format-list) take a stream of objects and format them appropriately. You can choose what properties are displayed. So, for example, when displaying a list of mailboxes, you can choose to see only the mailbox name and total size by running a command such as
    get-mailboxstatistics |
       format-table name, size
    (Although this command appears on several lines here, you would enter it on one line in PowerShell. The same holds true for the other multiline commands in this article.)
  • The where cmdlet lets you select objects to be used. For example, if you run the command
    get-eventlog application | where
      \{$_.ID eq "-1018"\}

    PowerShell searches the application event log and retrieves all events whose event ID is 1018. Interestingly, the where cmdlet is one of the few PowerShell cmdlets that don't follow the verb-object syntax I mentioned earlier.

  • As its name suggests, the group cmdlet groups objects according to a property you specify. Let's say you want a list of all the mailboxes in your organization along with their sizes, grouped according to which mailbox database they were in. This command would do the trick:
    get-mailboxstatistics |
        group MailboxDatabase |
        format-table count,name

When you look at other people's Power-Shell scripts, you might see funny-looking abbreviations for some cmdlets, such as fl for format-list. PowerShell has extensive support for aliases. There are built-in aliases for many common commands. Plus, you can define your own aliases in your shell profile, which is a special file that PowerShell uses to apply per-user defaults and settings.

PowerShell's scripting language, Powerscript, has all the constructs you'd expect to see in a scripting language, such as constructs for looping, branching, and string manipulation. If you've ever used VMS or IBM's mainframe JCL, the syntax will be somewhat familiar. UNIX scripters will also find familiar elements, as will administrators who are used to writing VBScript scripts.

PowerShell in Action
The first Microsoft application to take advantage of PowerShell is Microsoft Exchange Server 2007 (formerly code-named Exchange 12), so I'll be providing Exchange-related examples of PowerShell in action. ( Windowsrelated examples will be covered in a future article.) Exchange 2007's UI is based entirely on PowerShell. Every action you can take in the Exchange 2007 version of Exchange System Manager (ESM) is a task, and every one of those tasks is written using PowerShell. This opens up a wonderful new world: Everything you can do in ESM can be done from the command line—something that definitely isn't true for Exchange Server 2003. There are also additional operations you can perform only from the command line, although Microsoft hasn't released a complete list of what those operations are yet.

Exchange 2007 comes with its own set of cmdlets for working with Exchange objects. You can use these cmdlets for manipulating all kinds of objects, including Microsoft Outlook Web Access (OWA), virtual directories, mailboxes, distribution groups, storage groups, and unified messaging dial plans. Because these cmdlets follow the same naming pattern, it's immediately obvious, for example, what the new-mailbox, enable-mailbox, and get-mailboxStatistics cmdlets do.

Because of PowerShell's pipelining and composition capabilities, you can perform tasks from the command line that you just can't perform in the Exchange 2003 ESM or even in the Microsoft Management Console (MMC) Active Directory Users and Computer snap-in. For example, suppose that you have a default mailbox quota policy in place and you want to apply a different quota (500MB) to members of the Executive group. In Exchange 2003, mailbox quota policies apply to databases, so you have four choices: Don't use the policy, move the users in that group to their own mailbox database, use the Active Directory Users and Computers snap-in to manually apply a quota to each account, or write a script that uses Active Directory Service Interfaces (ADSI) to make the change. With Exchange 2007's PowerShell support, applying a different quota is much easier. You simply run a command such as

get-group "Executives" |
   set-mailbox
   -SendStorageQuota 500MB
Although this command is short, you can see both pipelining and composition in action. Note that the parameter for getgroup is positional, whereas the parameter for set-mailbox is a named parameter (i.e., SendStorageQuota). Why? All you can do with get-group is get a list of account objects that are members of the group, but set-mailbox has lots of other options you can apply. For example, you can just as easily give special quotas to people whose last names end in the letter x:
get-mailbox *x |
   set-mailbox
   -SendStorageQuota 1GB

As this example shows, you can use wildcards to find collections of objects that interest you. However, you don't have to. The cmdlets' default behavior is to return all objects of the selected type. For example, if you run the cmdlet

get-mailboxstatistics you'll get statistics for all the mailboxes in your Exchange organization. This feature makes it simple to perform actions that apply to all objects of a given type. For example, if you want to retrieve all the mailboxes in your organization, group them according to the mailbox database in which they reside, and format the resulting set of objects as a list, you'd run the command
get-mailbox |
  group Database | fl
Let's look at another more powerful example of cmdlets. The command
get-OWAVirtualDirectory |
   set-OWAVirtualDirectory
   -FileAccessControlOnPublicComputers
   blockAllExceptTranscoded
enumerates every OWA virtual directory in an organization, then sets the WebReady Document Viewing controls to block all attachment access, except for files that can be rendered by the Document Viewing layer. Presto! With this one line, you've effectively prevented users on public computers from saving or accessing attachments unless those attachments can be transcoded, which greatly lowers the risk of accidental exposure of confidential data. If you had to apply this setting manually to each OWA virtual directory, it would be easy to miss one or more servers, leaving your protection incomplete.

Favorite Features
As you work with PowerShell, you'll find features that you use constantly. Here are a few of my favorite features:

  • Start-transcript and its partner stop-transcript. The start-transcript cmdlet tells PowerShell to keep a complete log of your interactions with the shell, making it easy to go back to a previous session to find out exactly what you did. You can even use this log to turn sets of commands into a reusable script. The stoptranscript cmdlet tells PowerShell to stop this logging.
  • Get-command with the -synopsis flag. If you don't know what a certain cmdlet does, you can use the get-command cmdlet with the -synopsis flag to get a summary of the specified cmdlet. For example, if you run the command
    get-command -name get-service
          -synopsis
    PowerShell will give you a usage synopsis for the get-service cmdlet.
  • Get-excommand. If you don't know what cmdlet you're looking for, you can use get-excommand to find cmdlets that might be of interest. When you use getexcommand with wildcards, it's fairly easy to find what cmdlets exist. For example, the command get-excommand *ailbox* returns cmdlets, properties, and methods that are related to mailboxes. To get only cmdlets, you can use the where cmdlet to filter the get-excommand cmdlet's output:
    get-excommand *ailbox* |
        where \{$.Name -eq "Cmdlet"\}

Powering Up for PowerShell
Initially, your PowerShell scripts won't be pretty. They're probably going to look like the first scripts you created when you learned your first scripting language (only with slightly different syntax). So, until you get fluent in PowerShell and learn its ways, it might be best to think of PowerShell as a complement to existing scripting methods. The Learning Path box provides some resources to help you with the learning curve. I recommend Andy Oakley's book Monad and the Windows PowerShell Quick Start Web page, which is part of the Windows PowerShell Wiki on Microsoft's Channel9 (http://channel9.msdn.com/wiki/default.aspx/Channel9.WindowsPowerShellQuickStart).