Windows PowerShell

A command-line shell and scripting language for Microsoft Windows


February 27, 2008
URL:http://www.drdobbs.com/windows/windows-powershell/206900601

Doug Finke works for Lab49, a company that builds advanced applications for the financial service industry. Over the last 20 years, Doug has been a developer and author working with numerous technologies. You can catch up with Doug at his blog.


Microsoft PowerShell is an operating environment for commands -- cmdlets, functions, filters, scripts, aliases, and executables. PowerShell is based on the .NET Framework, which means that out of the box your existing investment in .NET components is preserved. An additional benefit is PowerShell's extensibility. PowerShell "Snap-ins" let you seamlessly integrate existing .NET components. By eliminating the IDE, PowerShell offers new feedback loops by removing the compile, link, and go steps. Additionally, PowerShell provides new ways to work with cumbersome, long-standing abstractions. For example, instead of launching to a specific area of the registry using regedt32, PowerShell lets you use the dir cmdlet using wildcards on the registry.

[Click image to view at full size]

To provide rapid development of software solutions, PowerShell pipes objects and provides parameter binding. By reducing the number of lines of code, you realize an enormous benefit during testing and re-factoring for change.

Why Learn PowerShell?

For administrators, PowerShell provides efficient, powerful access to key indicators of the operating system. (It is an abstraction much the way Visual Basic was to the Win32 APIs.) For developers, PowerShell is helpful in SQL scripting or developing DLLs. The concepts and cmdlets of PowerShell offer new ways to work with old information.

Developers need to continually find ways to quickly, effectively, and reliably deliver software solutions. A key element in this process is exchanging higher-level concepts for low-level ones. Consider the following example that demonstrates several concepts.

[Click image to view at full size]

First, this PowerShell example illustrates the use of the range operation. Second, although no explicit looping or print commands are defined, note that PowerShell iterated and printed the numbers in the range. This is not to say that PowerShell lacks looping constructs (it has foreach, for, do, and while). Rather, it is an example of abstracting away unnecessary details. This is a key idea behind PowerShell.

Here is an example of using ForEach and a glimpse at "piping" with PowerShell.

[Click image to view at full size]

Note the $_ variable in the previous example. This is an automatic PowerShell variable indicating the current object in the pipeline (variables will be discussed later). In short, PowerShell preserves the looping constructs you are familiar with and provides simpler ways to get things done.

How Does PowerShell Work?

When starting to work with PowerShell, one of the first things you'll use is a cmdlet (pronounced "Command Let"). A cmdlet is a single-feature, built-in command -- a building block by which an operation is completed. For example, one such cmdlet is Get-Date. Note the verb ("Get") and noun ("Date)" are separated by a dash; all cmdlets use this memorable verb-noun form. You can imagine what Get-Date does -- it returns the date. PowerShell also makes use of familiar commands, such as dir. (Digging a bit deeper, you find that dir is a memorable alias for the Get-ChildItem cmdlet.) The cmdlets delivered with PowerShell are covered later.

The runtime performs another role -- information used within the environment is done through structured information: objects with properties. These data objects are compatible with the .NET Framework. Data objects are supplied to and from commands through the pipeline. PowerShell pipes objects and provides parameter binding. This innovation provides rapid development of software solutions for many challenges. Developers are not burdened with creating "plumbing" between cmdlets.

How Does PowerShell compare with .NET?

PowerShell provides features that are accessible and robust. For example, you can create an XML document in a variable based on the contents of an XML file. Refer to the following example:

[Click image to view at full size]

The task can be completed in a few keystrokes. In contrast, to accomplish the same task in .NET requires several steps and background information: launch the IDE, create a project, know the System.Xml namespace, and use the Load method. Yet even after all those steps, the data has yet to be printed. To print the data requires returning to the IDE, adding more code, compiling, running, and waiting for the results. In PowerShell, you can easily; see the nodes:

[Click image to view at full size]

Programs help users concentrate and focus by quickly providing user feedback to issued commands. (Inellisense is a good example of employing such "feedback loops.") PowerShell, is a terrific high-speed feedback loop at many levels.

Real-World Examples in PowerShell

One of the most effective ways of highlighting the flexibility and usability of PowerShell is by example. The following two examples focus on simple handling of directory listings. However the principles demonstrated here will be applicable to other areas of PowerShell.

Example 1: Handling Directories using PowerShell

This example addresses some practical examples for using PowerShell to list the contents of directories. Consider if you want to simply list all files in a directory and below that have been modified within the past week. You could resort to "walking" the directory structure and try to capture the output in DOS. Or, you might use a number of other creative approaches. With PowerShell, the solution is efficient and elegant-two cmdlets work collaboratively (dir and where) using pipes.

dir . -r | Where { $_.LastWriteTime -ge (Get-Date).AddDays(-7) } | Select name

In reviewing this code, we see that objects with properties-not text-is being piped between commands using the pipe (|). First, a recursive directory listing is retrieved (using the -r flag). The dir cmdlet produces a System.IO.FileInfo object for each located item, based on the parameters passed. Each time this object is presented, it is piped to the Where cmdlet.

The Where cmdlet allows a script block; this is defined between the two curly braces. The $_ is a PowerShell automatic variable which has the current object in the pipeline (a System.IO.FileInfo outputted from the dir cmdlet). To access the contents of the date property, the conventional object notation is used: $_.LastWriteTime. The purpose of LastWriteTime is used to restrict the list to only those files modified within the last seven days. Finally, the name of the file is displayed. All this is accomplished in one line of code.

A similar command-only restricting by a fixed date and a filetype of .cs-would be:

dir . -Recurse *.cs| Where {$_.LastWriteTime -gt "11/14/2006"}

Note in this example that the -gt represents the greater-than comparison operator. The LastWriteTime is of type System.DateTime, so when the greater than comparison is done, PowerShell coerces the .LastWriteTime and String for us to be compared. Note also that the mathematical greater-than symbol ">" cannot be used; PowerShell interprets that character as a command to redirect the output.

[Click image to view at full size]

Example 2: Formatting Directory Outputs using PowerShell

Taking the previous example one step further, we can also define the format of the output by using the Sort-Object and Format-Table cmdlets. The Format-Table cmdlet trims down the verbose data. The -AutoSize parameter automatically adjusts the contents of each column. We can also define the properties we wish to be printed-specifically each file's LastWriteTime and Name.

dir . -Recurse *.cs| Where {$_.LastWriteTime -gt "11/12/2006"} | |
Format-Table -AutoSize LastWriteTime, Name

[Click image to view at full size]

We can also use wildcards when providing the properties to Format-Table such as *Time. This will display any property that ends with the letters "Time." This will produce a table with CreationTime, LastAccessTime and LastWriteTime as shown in the following screenshot.

[Click image to view at full size]

Notice the retrieved data is in random order. By inserting a pipe to the Sort-Object (between the Where and Format-Table) and adding the LastWriteTime as a parameter, we can sort the files by when the file was last written.

dir . -Recurse *.cs| Where {$_.LastWriteTime -gt "11/12/2006"} |
Sort LastWriteTime | Format-Table -AutoSize LastWriteTime, Name

[Click image to view at full size]

To see the table in descending time order, add the -Descending parameter to the sort cmdlet.

dir . -Recurse *.cs| Where {$_.LastWriteTime -gt "11/12/2006"} |
Sort LastWriteTime -Descending | Format-Table -AutoSize LastWriteTime, Name

To review, PowerShell shows its strengths by being able to pipe output through multiple commands to achieve the desired output.

Functionality in PowerShell

We've introduced to the power of PowerShell and some examples of how it can be used in daily operations with directories. Now, we'll review some of the common operations within PowerShell and how they work together to achieve time-saving results. This will be fundamental for our deeper dive into PowerShell.

Parameters in PowerShell

PowerShell is about automation and saving time. This is applicable to both using cmdlets -- and as we'll see later-authoring them. In the previous example, three parameters have been specified to three of the cmdlets. -Recurse, -Descending and -AutoSize. PowerShell's parameter binding mechanism accepts -R, -D, and -A as legal representations.

When a cmdlet supports multiple parameters, such as Path and Pass, parameter binding allows the fewest characters to be typed for a match to bind. For example, the characters of "Pat" and "Pas" are sufficient for the "Path" and "Pass" parameters.

Get-Stuff -Pat "c:\" -Pas $true

Additionally, you do not need to be concerned with the order of parameters when authoring cmdlets. This is handled by PowerShell.

Providers in PowerShell

You can access the help facility within PowerShell by typing help about_provider at the command line. The short description on providers is as follows:

Windows PowerShell providers provide access to data and components that would not otherwise be easily accessible at the command line. The data is presented in a consistent format that resembles a file system drive.

Using another cmdlet that ships-Get-PSDrive-yields the following:

[Click image to view at full size]

So what does this mean? It means you can change directory to the registry, certificate store, environment variables, variables you create, functions you create and even your C drive.

Furthermore, because these providers are similar, cmdlets such as dir (aliased for Get-ChildItem) operate as if they were another drive mapped to your system. For example, if you execute cd HKLM: you will navigate to the registry. Typing dir displays the contents of that registry. Similarly, you can cd to SOFTWARE and display its contents.

For information about an alias, you can use the Get-Alias cmdlet. The command Get-Alias dir would retrieve the following:

[Click image to view at full size]

Objects in PowerShell

Since PowerShell is based on .Net, you can create objects using the New-Object cmdlet. The following example generates a random number using the base class libraries; it simulates rolling a single die 1,000 times. Using only 67 characters, a hash table collects and prints the frequency each side is rolled,

[Click image to view at full size]

Looking at each of these commands separately, we can review how they interrelate. First, a range is defined (1-1000). Second, this range is piped into a foreach loop, which creates a hash table with the variable name of $freq. Third, for each value from the hash table, a random number between 1 and 7 is generated. Finally, that value is printed.

cmdlets in PowerShell

A cmdlet is a unit of work in PowerShell. Over 100 cmdlets ship With PowerShell. Want to see just how many cmdlets? Use the system to figure out the system by executing the intuitive Get-Command command.

[Click image to view at full size]

The following two tables outline the unique verbs and nouns with which PowerShell is shipped. The contents can be obtained using the following command:

Get-Command | ForEach { $_.Name.Split("-")[0] } | Sort -Unique

[Click image to view at full size]

[Click image to view at full size]

Syntax in PowerShell

In PowerShell, the syntax is intuitive and familiar. For instance, variables are designated with a dollar symbol ($). Here are some typical methods of creating variables.

[Click image to view at full size]

Since PowerShell is rooted in .Net, if we set $a = "Hello World", $a is of type System.String. From the command line we have access to all the familiar methods and properties of a string; this is demonstrated in the following screenshot.

[Click image to view at full size]

Another out of the box cmdlet is Get-Member. By piping $a to it, the Get-Member cmdlet reflects over the $a (a System.String type) and displays the variable's methods and properties as shown below.

[Click image to view at full size]

Variables in PowerShell

In the following example, $v is a PowerShell variable. The dollar sign designates "v" as a variable; it will store the result of a simple multiplication operation. Note that the data type of $v is undefined. The variable $v is an object and is based on.Net. As such, we can invoke methods such as the GetType() method to display the RunTimeType information. Notice $v is of type Int32 and has a base type of ValueType.

[Click image to view at full size]

Using the same variable $v, we set it to a string; we print out the type information using the GetType() method.

[Click image to view at full size]

Similarly, we can create an array simply by setting the variable to a list of comma-separated values. The GetType() method shows $v is an array of objects. Later we'll cast variables to strongly-typed arrays.

[Click image to view at full size]

Finally, we can key value pairs in a hash table using the @{} PowerShell syntax. This will be verified again through the GetType() method. PowerShell handles both the display and iteration of the namevalue pairs in the hash table.

[Click image to view at full size]

Directories in PowerShell

To discuss a little "housekeeping" for PowerShell, it is a good practice is to have two subdirectories: src and lib. The src directory contains the projects created for the managed code; these will be accessed from PowerShell. The lib directory contains the DLLs; pointing the build area to this directory allows access to PowerShell. Also, add the "root" directory-where the scripts are kept-to your path. That way, when you launch PowerShell, you can type in the name of your command without the file extension .ps1 to execute the script.

Diving Deeper in PowerShell

Having reviewed some of the fundamentals of PowerShell, it's time to "dive deeper" into some specific examples. These will cover how PowerShell works seamlessly with .Net components, scripts, files, and other applications.

Example 1: Developing a Simple .Net Class

In this example, we'll create a simple .Net class library called Calculator. It contains a single class (Calc) and only one method (DoAddition). This method returns the sum of two arguments.

namespace Calculator
{
   public class Calc
   {
      public int DoAddtion(int var1, int var2)
      {
         return var1 + var2;
      }
   }
}

Once we've created the class, we must compile the code, start PowerShell and change directory to where the DLL is. The DLL needs to be loaded into the current domain using the static method LoadFrom located in the Reflection.Assembly namespace. PowerShell has special syntax for loading the DLL-it requires the namespace and class containing the static method to be in braces, two colons, and the name of the static method. The LoadFrom method also needs the path and name of the DLL. For the path, the $pwd automatic variable is used. The complete syntax is as follows:

[Reflection.Assembly]::LoadFrom("$pwd\Calculator.DLL")

If the DLL is found and loads successfully you should see the following:

[Click image to view at full size]

Now an instance of the class needs to be created using the New-Object cmdlet. It must be fully qualified with the namespace. Once successfully created, you can call the DoAddition method.

[Click image to view at full size]

PowerShell's scripting ability is expanded with .Net. Using the range syntax and the ForEach cmdlet, we can use DoAddition to count by twos.

[Click image to view at full size]

Note, as mentioned earlier, the $_ PowerShell variable refers to the current object in the pipeline. However, alternate syntax is available. The following lines of code accomplish the same

Option A:

1..5 | ForEach { $calc.DoAddition($_, $_) }

Option B:

1..5 | ForEach { $calc.DoAddition($i, $i) }

Example 2: Developing Advanced .Net Classes

In this example, we'll create another class library and return a custom type. The class library will be called People and will contain two classes, PeopleHelper and our custom type Person. Person is a simple class; it has three properties: FirstName, LastName and a Guid. All three properties get set in the constructor. The PeopleHelper class has one method (NewPerson) and takes two strings (FirstName and LastName).

using System;
namespace People
{
   public class PeopleHelper
   {
      public Person NewPerson(string firstName, string lastName)
      {
         return new Person(firstName, lastName);
      }
   }
   public class Person
   {
       private string _firstName;
       private string _lastName;

       private Guid _guid;
       public Guid Guid
       { get { return _guid; } set { _guid = value; } }
       public string LastName
       { get { return _lastName; } set { _lastName = value; } }
       public string FirstName
       { get { return _firstName; } set { _firstName = value; } }
       public Person(string firstName, string lastName)
       {
       _firstName = firstName;
       _lastName = lastName;
       _guid = Guid.NewGuid();
       }
   }
}

We create this DLL as we would any other .Net component. In PowerShell we load it into the current application domain, instantiate the class, and call the NewPerson method with parameters.

[Click image to view at full size]

To illustrate the functionality, we create an array of first names at the command line. This array is piped in order to create a Person object for each one.

[Click image to view at full size]

If first names are listed in a file (for example, names.txt), we can retrieve the data using the Get-Content cmdlet. This cmdlet will read the file, pipe it, and create a Person object for each name.

[Click image to view at full size]

In both cases -- whether the source data is file-based or an array at the command line -- the results are the same. Our .Net components are used in conjunction with PowerShell in order to create unique identifiers and first-last name combinations.

Example 3: Creating Scripts

The previous example illustrated the seamless interaction between PowerShell and .Net DLL. Now we'll build these up into constructs that are reusable. Both a legacy .Net component and the People.DLL will be wrapped in PowerShell constructs so they can operate seamlessly in the pipeline. This will demonstrate PowerShell's ability to script existing .Net DLLs.

In effect, this example will enable a transformation of data from one form to another. Starting with a simple array of names, we'll transform the array to a "table" of objects. Then, the input will come from an alternative source-a file. Moving along-while using the People.DLL -we'll filter the list of names (similar to a WHERE clause in SQL). The data-transformation will be will be complete as the data is exported to a CSV file.

First, we'll build a script file with the PowerShell file extension: People-Setup.ps1. The contents of this script file will be the individual PowerShell commands. In the script we'll setup two functions, a filter and code to call one of them. The contents of the file are as follows:

1. $scriptDefinition = $MyInvocation.MyCommand.Definition
2. $scriptDir = Split-Path $scriptDefinition
3. $libDir = Join-Path $scriptDir "lib"
4. $dllName = "People.dll"
5. $resolvedDLL = Join-Path $libDir $dllName
6. [Reflection.Assembly]::LoadFrom($resolvedDLL)

In line 1, the $MyInvocation automatic variable is used to determine the source of the script and the location of the DLL lib directory. This path is then split using the Split-Path cmdlet. Then, it is built up with the lib directory and DLL that is needed for the LoadFrom cmdlet in Line 6.

The function Add-Assembly takes two arguments-a script definition ($MyInvocation.MyCommand.Definition) and the DLL name (People.DLL). The $MyInvocation.MyCommand.Definition command-when used in the script-returns the full name of the script location. Using this as a starting point along with two cmdlets (Join-Path and Split- Path) we'll resolve our People DLL location and load the DLL.

As the previous example shows, we can create scripts that work with both .Net and PowerShell components. Reaching back to our previous Calculator class, we can see a simple example.

[reflection.assembly]::loadfrom("$pwd\Calculator.dll") | Out-Null
1..10 | % { $calc.DoAddition($_, $_) }
1..10 | % { $calc.DoAddition($_, $_*2) }

1..10 | % { $calc.DoAddition($_, $_*3) }

This example, presents an interesting way of using .Net DLLs. The feedback loop of code, compile, run, and observe is reduced dramatically. More importantly, existing .Net components can be scripted.

Example 4: Working with Microsoft Excel

With PowerShell, you have the ability to uniquely handle data in Microsoft Excel. Not only can you retrieve data from a worksheet within a workbook, but you can create custom properties for the created objects using PowerShell's wrapper object: PSObject. In this example, we'll connect to an Excel workbook, retrieve the data from a specific worksheet, and then use PSObject within nested loops to add custom properties.

In the following code, observe that in the Get-ConnectionString cmdlet, we build a connection string to connect to an Excel workbook. The specific workbook is passed in as a parameter.

param($workbook, $sheetName)
begin
{
   function Get-ConnectionString
   {
      param ($workbook)
      "Provider=Microsoft.Jet.OLEDB.4.0;Data
Source=$workbook;Extended Properties=Excel 8.0;"
   }

In the Get-Data function, we retrieve the data from the specified workbook. Moreover, we will be extracting data from a specific worksheet within the workbook (as defined by the parameter). By using ADO.NET, we consider the sheet like a pseudo-database. As such, we can execute a SQL statement against it and store the output. Note that we suppress the output by piping to the Out-Null cmdlet.

function Get-Data
{
  param($workbook, $sheetName)
  $workbook = Resolve-Path $workbook
  $cnnString = Get-ConnectionString $workbook
  $cnn = new-object System.Data.OleDb.OleDbConnection($cnnString)
  $cnn.open()
  if(!$sheetName.EndsWith("$")) {$sheetName += "$"}
  $sql = "Select * from [$sheetName]"
  $cmd = new-object System.Data.OleDb.OleDbCommand($sql,$cnn)
  $da = new-object System.Data.OleDb.OleDbDataAdapter($cmd)

  $ds = new-object System.Data.dataset("ExcelData")
  $da.fill($ds, "Data") | Out-Null
  $cnn.close()
  $ds ,"data"
}

The following function is the focus of our operations. Here, we will be converting the dataset object into a custom PSObject. Notice the operations occurring here. First, if the two arguments are not passed in, we will exit the function. Second, we define our column names. Third, we pipe the content of our rows into a foreach loop. In the loop, we create a new PowerShell extended object for each row. Fourth, with each column name, we pipe that into an inner loop where we use the Add-Member cmdlet. This cmdlet will add a custom property to our recently-created object as it passes through the pipeline. The name will be that of the column variable we're currently processing; the value will be the current row's value in that same column. Finally, to close out our loop, we will reset our object variable.

function Convert-DSToObject
{
  param ($dataSet, $tableName)
  if(!$dataSet) { "need a dataset please"; return }
  if(!$tableName) { "need a tablename please"; return}
  $columnNames = $dataSet.Tables[$tableName].Columns
  $dataSet.Tables[$tableName].Rows |foreach {
    $row = $_
    $o = New-Object PSObject
    $columnNames | foreach {
      $columnName = $_
      $o = $o | Add-Member -PassThru NoteProperty
$columnName $row[$columnName]
      }
      $o
    }
  }
}
end

In this final portion of code, we call our functions, and develop our custom objects. The Get-Data function requires two parameters-the workbook and worksheet name. The output of that function is then passed into our DSToObject function.

{
  if (!$workbook) {"need a workbook please"; return}
  if (!$sheetName) {"need a sheet name please"; return}
  $ds, $name = Get-Data $workbook $sheetName
  Convert-DSToObject $ds $name
}

Clearly, PowerShell affords an efficient method of extending an object by creating a custom object. In this example, we've added members to our object iteratively as we looped through the rows of an Excel worksheet.

Conclusion

The benefits of PowerShell are obvious. Being developed out of the robust and extensible .Net framework allows you to leverage existing technologies for more efficient development. PowerShell further speaks to developing systems as services. Building cohesive, small-in-scope components with a specific purpose help the development of loosely-coupled, cohesive designs. This helps typical .Net development and enables scripting of these services. This is a productivity upgrade on multiple levels. For example, re-purposing existing services glued together with a script is far quicker and more flexible than one done in compile code.

Because of PowerShell's strengths, there are signs of good growth-a strong user community has already taken shape; Microsoft is already building version 2.0; a third-party market is also emerging just as it did when Visual Basic emerged. The reason for the excitement is clear: PowerShell is flexible, extensible, and easily integrated.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.