Skip to content

Impressions of the PowerShell

Introduction

For a Unix user of many years, like me, doing serious work with Microsoft Windows is really hard. The graphical user interface lets you do some types of work quickly and easily, but it prevents you from doing recurring tasks in a repeatable way. The scripting possibilities of Unixoid OSes (like Shells, Perl, Python, etc.) have never had an equal match under Windows.

It seems, even someone in Redmond realized this deficiency a few years ago, and invented the PowerShell. I will not go into the details of its history and the relation to other Microsoft products (like .NET), Wikipedia's entry does this much better. I would like to give you a short walk-through to some of the features I deem interesting. This overview is neither complete nor is it meant to be a tutorial. I'll give a few links to more thorough resources at the end of the tour.

Interacting with the PowerShell

When starting the PowerShell, you are greeted with a window showing a prompt, waiting for you to enter commands:

entering PowerShell commands

The prompt indicates, that the current working directory is my home directory.

If you are already familiar with the older command line tools of Windows, your first try might be the command "dir":

command "dir"

Indeed, this shows the contents of the directory you are standing in. But if you come from the Unix world, your first command might be "ls":

command "ls"

And fair enough, this command does also show you the contents of the current directory.

Now you might be tempted to see, if these commands are really the handy tools you have known for so long. Let's see if they understand some of the switches you use every now and then:

command switches

Nope, they don't. PowerShell gets quite chatty when an error occurs. Let's find out what is really happening. The command "alias" shows a list of defined aliases, and PowerShell comes predefined with a handful of those:

command aliases

As you can see, "dir" is actually an alias for "Get-ChildItem", and if we'd scroll down a little bit, we would find that "ls" is an alias for the same command. And indeed, if we call "Get-ChildItem" directly, we get exactly the same result:

command "Get-ChildItem"

So, what command line switches and parameters does "Get-ChildItem" understand? A nice feature of PowerShell is, that every command understands the option "-?", which yields an explanation:

command help

The structure of this manual is of course a good replica of Unix man-pages, with the different sections like NAME, SYNOPSIS, SYNTAX, and so on. As we can see, the command takes an optional path (eventually preceded by the even more optional parameter "-Path"), and there is an option "-Recurse". So let's try them:

Get-ChildItem options

As you can see, there are directories I am not allowed to access, but the command continues to walk through all other folders it finds.

Another useful command is "Get-Process":

command "Get-Process"

Before you ask: Yes, there is a predefined alias called "ps".

And for the average Windows sysadmin, "Get-Service" will be handy:

command "Get-Service"

result of "Get-Service"

As you can see, the last command generates more output than fits on the screen. The solution is neither a surprise to the Unix user nor to the Windows user: Pipe the result through the command "more":

pipe through "more"

result of pipe

Another possibility to get help on commands is to use "Get-Help":

command "Get-Help"

Did you notice the REMARKS section? If you add the option "-examples", you can get even more help on the command:

show examples

Actually, you can use "Get-Help" to search an expression in all available documentation. If you use the wildcard "*", you get a list of all manual pages:

all the help

A number of manuals do not deal with single commands, but instead explain larger concepts and ideas. For example, if you need to know what exactly aliases are and how they work, just ask for help:

help on aliases

cmdlets

I have used the expression "command" rather liberally above. Actually, "Get-ChildItem", "Get-Process", "Get-Service" and "Get-Help" are so-called "cmdlets" ("commandlets"). These are programs using the .NET framework and adhere to a few standards, e.g. they all come with their own online help. Another common feature is the structure of their name: Every cmdlet is a combination of a verb and a noun (in singular form), connected by a hyphen.

To find out which cmdlets are defined, there is of course a cmdlet:

show all cmdlets

As you can see from the first column of this list, there are a few other things besides cmdlets, that PowerShell knows. Add the option "-CommandType Cmdlet" to get a list of only real cmdlets:

show only cmdlets

To filter the output even more, you can search for those cmdlets starting with a certain verb, or acting on a certain noun:

cmdlets with verb "Get"

cmdlets with noun "Computer"

Even the list of verbs is fixed, and can be viewed with "Get-Verb" (which, ironically, itself is not a cmdlet but a function, as you can see in one of the images above):

show all verbs

Scripting

Interactive work with a shell is one part of the job, but in order to automatize your recurring tasks, a shell has to be able to execute scripts. I have a few sample scripts in the subfolder "examples". As a convention, PowerShell scripts use the suffix ".ps1".

I have an old habit: Whenever I learn a new language, one of the first programs I write is Towers of Hanoi. This exercise tests a lot of features of the language (functions/subroutines, recursion, data types, control flow, arithmetics, formatted output). And I am always amazed, how you can break down such a seemingly complex problem into six lines of code. This is the PowerShell version I came up with:

Towers of Hanoi

Let's try to execute this script:

running "Towers of Hanoi"

These are PowerShell's very protective security features at work: Initially, you are not allowed to execute any scripts at all. But you can define, under which circumstances scripts from different sources are trusted and will be executed. Usually, a script has to be signed and you have to trust the corresponding certificate. But you can allow even unsigned scripts to be executed.

To view the current policies, use the cmdlet "Get-ExecutionPolicy":

command "Get-ExecutionPolicy"

Let's ease these tight rules a little bit. I, the current user, will allow to execute all scripts that reside locally on my computer. Only scripts downloaded from the Internet need to be signed:

allow local scripts

Now I can execute the script:

actually running "Towers of Hanoi"

The script is executed in its own process. But you can also import the definitions of a script into the current PowerShell, by putting a single dot in front of the script name:

importing a script

The difference to the preceding example is, that the function "mvdisk" is now defined within the current PowerShell:

using an imported function

If you want to know, how long a script or command takes to run, use the cmdlet "Measure-Command". The code to be measured is given as a sub-expression in curly braces:

command "Measure-Command"

result of command "Measure-Command"

Objects

Let's find out, what date and time it is right now:

command "Get-Date"

But let's take a closer look at what actually is returned by this cmdlet:

return value of command "Get-Date"

The return value is not just a string with the current date and time, it's an object. And one method available to all objects is "gettype()". The parenthesis indicate, that this is a method, not an attribute.

On closer examination, everything in PowerShell is an object:

other examples of objects

What can we do with an object of type "DateTime"? Either we can look it up in the .NET documentation (which is a good idea anyway), or we could ask PowerShell to give us a little insight:

command "Get-Member"

The cmdlet "Get-Member" lists all methods and properties of an object. For example, the method "Ticks" returns the number of ticks (which is a ten-millionth of a second since January 1, 0001, midnight):

number of ticks

The method "AddMinutes()" adds the given amount of minutes and returns a DateTime object representing that timestamp:

DateTime arithmetics

As the return value is again a DateTime object, you can apply another method immediately:

concatenating methods

This way you can inspect all the different objects returned by the cmdlets we have seen so far:

members of Sytem.IO.Directoryinfo returned by Get-ChildItem

members of System.Diagnostics.Process returned by Get-Process

Let's start a process to play with: The Windows Calculator.

running and examining the Windows Calculator

As you can see, you can get the corresponding object by passing the name of the process to the "Get-Process" cmdlet. To terminate the calculator, we use the method "Kill()":

terminating the calculator

Let's start two calculators and try to kill them both:

killing twins

Oh, that didn't work. Hmmm... what does not contain a method named "Kill"? What is the return value of the "Get-Process" cmdlet from above?

Get-Process again

Ah, it's not an object of type "System.Diagnostics.Process", but instead it is an array of objects of this type. Which makes sense, because the cmdlet should find both calculator processes. And there is no method "Kill()" defined on arrays.

There are several ways to terminate both calculators at the same time (so PowerShell also supports TIMTOWTDI, it seems). Let's pipe the result of the "Get-Process" cmdlet to the "Stop-Process" cmdlet:

pipes to the rescue

Piping

The preceding example demonstrated one of the main features of the PowerShell, that Microsoft has touted as the big innovation compared to classical Unix shells: Instead of piping lines of text from one process to another, the PowerShell pushes objects through its pipes. This of course means, that the recipient has far more possibilities to process the data. Let's take a look at some examples:

sorting processes

Here, the output of the "Get-Process" cmdlet (which is an array of process objects, as we now know), is sorted by the "Sort-Object" cmdlet. This latter cmdlet sorts arrays of arbitrary objects (of the same type) according to the property given as parameter. Imagine doing this with the text-oriented commands of Unix. It's not completely impossible, but it would be harder by several magnitudes.

Say we want to revert the sort order:

invert sort

and then display only the ten topmost CPU consuming processes:

limit output

It is even possible to sort on a property that is not visible by default:

sorting on invisible attribute

... and then select a completely different set of properties to display:

output other attributes

The cmdlet "Where-Object" allows you to remove objects from the pipe, if they don't match a given condition. This is expressed as a code fragment, where "$_" is a placeholder for the object that is currently examined by the cmdlet:

dynamic "Where-Object"

Another cmdlet that accepts generic objects is "Measure-Object". Without any further parameters, it just counts the objects piped to it:

"Measure-Object" revisited

If you specify a property and one or more types of aggregation, you can do a little statistics on the objects:

statistics with "Measure-Object"

But of course this is only possible for numeric properties. Obviously, this does not work:

no average company

For the last example in this section, let's first take a look at this enumeration operator stolen, err..., borrowed from Perl:

enumeration

To perform an arbitrary operation on each single object in a pipe, use the cmdlet "Foreach-Object":

command "Foreach-Object"

It is the PowerShell equivalent of "xargs".

Formatting Data

As we have seen in the examples above, most of the cmdlets return objects, or even arrays of objects. Yet, when we do not pipe these objects to other cmdlets, the result is some sort of formatted text. A DateTime object is shown in a human readable form, an array of process objects yields a tabular display of some of their more interesting properties. Every object has a default format, that is used to display the object, unless you request another behaviour.

In addition, these default formats try to anticipate what is a reasonable format for the type and amount of data that needs to be displayed. Let's see what happens, when we select a lot more properties from our process objects than are shown by default:

lots of properties

As this amount of data would not have fit into a table, PowerShell switches to a record oriented display. Yet, even in this case, we can convince PowerShell to use the tabular style:

force tabular formatting

If we select only a few properties, the amount of space PowerShell uses for padding is sometimes a little bit excessive:

too much padding

With the paramter "-AutoSize", the formatter reduces the whitespace and the table becomes more readable:

better formatting with "-AutoSize"

We can request the record oriented display by piping the objects through "Format-List":

force list formatting

Maybe you have noticed the way the property "Threads" was displayed in some of the examples above. As a process usually has several threads, this property is a hash array, and in the examples above, only the keys to the hash elements were displayed. To drill deeper into such data structures, the formatter "Format-Custom" can be used:

drilling into nestes data structures

And finally, you can pass a list of objects to the cmdlet "Out-GridView", which opens a new window, displaying the objects as a spreadsheet:

spreadsheet display of objects

You can sort the entries by clicking on one of the column headers, just like in a real spreadsheet application:

sorting the spreadsheet

Embedding Languages like C#

If you have read the Wikipedia article on the .NET framework, you know that one of it's key components is the Common Language Runtime (CLR). As a consequence, you can chose from several programming languages, which are translated into a common set of instructions for a virtual machine. As these languages also share their datatypes, defined in the Common Type System (CTS), it is easy to mix and match several pieces of code, written in different languages, into a single, homogeneous application.

In the PowerShell, you an easily define new datatypes by inlining a class definition written e.g. in C#. The following example defines a class "Complex", which represents complex numbers. The notation "@" ... "@" is a here-string, i.e. the C# code is stored in a variable and is then compiled on-the-fly by feeding it to the cmdlet "Add-Type".

inlinging C#

The two methods "Re" and "Im" either return or set the real or imaginary part of the complex number. Note the shorthand notation for the getter and setter methods. In addition, the operators "+", "-" and "*" are overloaded to implement the addition, subtraction and multiplication of complex numbers. As you can see, these overloaded operators can even be used in the PowerShell script:

using the inlined functionality

Exporting Objects

If you want to process object information outside the PowerShell, it is easy to export data to files. A common format (well, at least in the Windows world), is CSV. Piping objects to "Export-Csv" generates a shallow copy of the underlying datastructures:

exporting as CSV

All properties of the selected objects are written to the given file, even those that are not displayed when the output is directed to the screen. But structured properties, like "Threads", do not get expanded.

The exporter for XML, "Export-Clixml", emits a deep copy of the given objects, as you can see in this example:

exporting as XML

A simple set of tag names is used to denote the atomic datatypes of PowerShell (I32 for integers, S for strings, T for types, and so on). And the nested structure of the objects gets unfolded. There is even a corresponding cmdlet "Import-Clixml", that can thaw objects that have been frozen this way. Of course this works only for objects that are totally described by the XML dump. Reimporting this example file will of course not regenerate all these processes.

Conclusion

I hope this walk-through showed you some of the aspects of the PowerShell. The developers have incorporated some really great ideas into the PowerShell - and copied shamelessly from other scripting solutions, like Bourne Shell and Perl. Passing objects from cmdlet to cmdlet instead of text is (as far as I know) a novel idea, and it has great potential.

On the other hand, the PowerShell retains some features from the Windows Command Shell, like tab completion or history handling, that are far inferior to the solutions found in almost all Unix shells. The use of parentheses and brackets (which I did not address above) is sometimes puzzling, but probably something one gets used to after writing a few hundred lines of code.

If you want to try the PowerShell, but do not want to switch to Windows, have a look at Pash. This is a rewrite of the PowerShell, but it is based on Mono, the Open Source reimplementation of the .NET framework. But somehow I doubt this approach: The universality of the PowerShell is based in its close relation to the .NET framework - which in turn covers the complete Windows Operating System with all its features and functions. To the best of my knowledge, no such object model exists for Unix. The BeanShell seems to be a similar approach using the Java Runtime Environment as underlying object model, this might be a better approach in the Unix world.

References


created 2010-07-24 / last updated 2024-01-06