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:
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
":
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
":
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:
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:
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:
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:
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:
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
":
Before you ask: Yes, there is a predefined alias called "ps
".
And for the average Windows sysadmin, "Get-Service
" will be handy:
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
":
Another possibility to get help on commands is to use "Get-Help
":
Did you notice the REMARKS section? If you add the option "-examples
",
you can get even more help on the command:
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:
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:
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:
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:
To filter the output even more, you can search for those cmdlets starting with a certain verb, or acting on a certain noun:
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):
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:
Let's try to execute this script:
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
":
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:
Now I can execute the script:
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:
The difference to the preceding example is, that the function
"mvdisk
" is now defined within the current PowerShell:
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:
Objects
Let's find out, what date and time it is right now:
But let's take a closer look at what actually is returned by this cmdlet:
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:
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:
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):
The method "AddMinutes()
" adds the given amount of minutes and returns
a DateTime object representing that timestamp:
As the return value is again a DateTime object, you can apply another method immediately:
This way you can inspect all the different objects returned by the cmdlets we have seen so far:
Let's start a process to play with: 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()
":
Let's start two calculators and try to kill them both:
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?
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:
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:
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:
and then display only the ten topmost CPU consuming processes:
It is even possible to sort on a property that is not visible by default:
... and then select a completely different set of properties to display:
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:
Another cmdlet that accepts generic objects is
"Measure-Object
". Without any further parameters, it just counts the
objects piped to it:
If you specify a property and one or more types of aggregation, you can do a little statistics on the objects:
But of course this is only possible for numeric properties. Obviously, this does not work:
For the last example in this section, let's first take a look at this enumeration operator stolen, err..., borrowed from Perl:
To perform an arbitrary operation on each single object in a pipe, use
the cmdlet "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:
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:
If we select only a few properties, the amount of space PowerShell uses for padding is sometimes a little bit excessive:
With the paramter "-AutoSize
", the formatter reduces the whitespace
and the table becomes more readable:
We can request the record oriented display by piping the objects through
"Format-List
":
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:
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:
You can sort the entries by clicking on one of the column headers, just like in a real spreadsheet application:
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
".
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:
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:
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:
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.