PowerShell classes

In this in-depth article, Richard Siddaway will introduce you to PowerShell classes, and how they can be used as part of a PowerShell module.

By RicRichard Siddawayhard Siddaway, IT Consultant and Author. He has over 25 years of experience in IT, predominantly working with the Microsoft stack. Richard is a multi-year PowerShell MVP, author of a number of books on PowerShell, Active Directory and Windows administration and a regular speaker at the PowerShell Summit and user groups. He blogs regularly on PowerShell and related topics at https://richardspowershellblog.wordpress.com/

PowerShell is many things to many people. If you ask the question “What is PowerShell?” you’ll get a number of different replies for instance:

  • A shell
  • A scripting language
  • An automation engine

Various PowerShell experts will argue strongly, and at great length, for their particular view of PowerShell. In reality PowerShell is all of these and more.

IT administrators have been the primary audience for PowerShell so far though there has always been an interest in the language by parts of the developer community. The PowerShell team have stated that they will be introducing more developer orientated features. One of the first results of this is the introduction of PowerShell classes in PowerShell 5.0. Primarily introduced to make the writing of Desired State Configuration (DSC) resources easier classes can also be used in any PowerShell situation where it is appropriate. In this article I’m going to introduce PowerShell classes and show you how they can be used as part of a PowerShell module.

Before we jump into the code let’s sort out some terminology:

  • Class – a class is the definition of a template for creating objects. The class can define properties (data fields) and methods (the way to perform a task)
  • Object – an object is created (instantiated if you want to be technical) from a class definition. Objects are the basis of PowerShell and if you’ve used it all you should be used to working with objects
  • Module – a PowerShell module is a .psm1 file that contains one, or more, PowerShell functions – usually advanced functions. A module could also contain binary cmdlets i.e. cmdlets created in C# and compiled. Modules are the way to deliver functionality that extends PowerShell.

These definitions don’t tell the whole story but supply sufficient background for this article.

Next question – when might you need to use a PowerShell class?

PowerShell classes were introduced to simplify the creation of DSC resources as previously stated. I’ll cover DSC and resource creation in a future article. Classes are also useful when you need to create an object for output or future processing. You can use New-Object to create an object – it’s a standard technique but if you use a PowerShell class as the template for your object you can strongly type the properties. This means that if you define a property to be a specific type such as an integer then its value has to be that type (or something that can be turned into that type). You’ve

been able to do this in the past by creating the class in C# and using Add-Type but it’s not a technique that many administrators have known about or wanted to use.

Classes also have methods. You can add script methods to an object using Add-Member but its not a technique for inexperienced PowerShell users. Writing methods for PowerShell classes is easier – once you have learned how to deal with the quirks of the syntax.

To recap - PowerShell classes provide us with a way to create strongly typed objects. Those objects can have methods that we can use to aid our processing.

So how do we create a class? As an example I’m going to work with time zones. I’ve been working with Nano server a lot recently and in Technical Preview 5 Nano server defaults to the Pacific time zone. I discovered there isn’t PowerShell cmdlet for working with time zones – you have to use tzutil.exe. I decided I’d create a PowerShell wrapper for tzutil so I didn’t have to remember its syntax

Our class definition starts like this:

code4

The class keyword is used indicate we’re creating a class. The class needs a name – in this case RStimezone. Feel free to change it if you want as class names are arbitrary – just don’t pick an existing .NET class name. There is a System.Timezone class in .NET but it doesn’t have a method to set time zones.

You then define the properties of the class. If you’ve created PowerShell functions this will look similar to defining the parameter block but without the param keyword. You can’t use the parameter validation decorators from advanced functions here either.

Once you have a class definition you need to run it to load it into memory. Using ISE for development makes this easy. Creating an instance of the class can be performed using New-Object.

Code2

Notice that the properties are given default values. Zero for integers; empty strings for string values and False for Boolean values.

The class automatically gets a static method New() that can be used as an alternative way to create an object from the class.

Code3

A static method is one where you don’t need to create an instance of the class to use it. The classic examples are the methods on the System.Math class – for instance the Round() method.

code5

An empty object doesn’t get us very far. We need to be able to set the properties on the object. Ideally we would want to be able to use the -ArgumentList parameter on New-Object.

code6

But it doesn’t work. An object is created from a class using the class constructor. The default constructor doesn’t allow you to set the properties of the object as you create it – it just creates an empty object. You have to create the object as a first step and then set its properties.

code7

This is long winded and involves too much typing. The simpler way is to use a hash table.

code8

You can add your own constructor to the class but then you have to also define the default constructor.

code9

The properties are defined as before. The default constructor is the class name followed by empty parentheses and curly braces:

code10

The constructor that takes property values is a bit more complicated:

code11

Within the parentheses you define variables that represent the property values. You have to give the variable a type that matches its corresponding property. Within the curly braces you map the variables to the properties:

code12

I use variable names that are reminiscent of the property name. These variables are arbitrary and you can use any legal variable name – avoid the name of the property though! When mapping the variable to the property notice that the property name has $this in front of it. It’s a shorthand; for instance - the property of this object called Index is equal to the contents of the variable $idx.

Now you can use the -ArgumentList parameter on New-Object

code13

The default constructor works as previously:

code14

Now that we have the basis of our class definition it’s time to think about the module for a moment. I wanted the module to do three things:

* Provide a list of time zones with the correct information to give tzutil

* Show me the current time zone

* Enable me to set the time zone

I also decided that I wanted to limit the number of calls to tzutil. One way to solve this is for the module to create a collection of time zone objects as it loads. But you need to know what the data looks like so need to experiment with tzutil.

To view the current time zone:

code15

The output is the time zone name. To view the list of possible time zones:

code16

 

<truncated for brevity>

The output is the time zone description followed by the time zone name followed by a blank line. The code to create a collection of time zone objects looks like this:

code17

$tzs holds the output from listing the time zones. The blank lines are stripped out by only passing lines that aren’t blank strings. $ctz holds the name of the current time zone.

Two counters are created. $index is a time zone counter that will be incremented by 1 for each time zone. $num will be used to work with the collection of time zone information in $tzs. Notice that it’s set to 1 which is the second element in the collection which is a time zone name.

A variable, $timezones, will hold the output of a while loop that will iterate through the time zone information in $tzs. Within the loop the time zone name $tzs[($num)] is checked against the name of the current time zone held in $ctz. The value of $current is set to $true if they match and $false if they don’t.

A new RStimezone object is created using the non-default constructor. The -ArgumentList parameter supplies the needed data to the constructor. The values are in the required order of Index, Description, Timezone, Current.

The object is added to the $timezones collection as it’s the output from the loop. Last part of the loop increments $index by 1 and $num by 2 (moves to next time zone name).

The module contains 3 functions.

code18

Get-CurrentTimezone displays the current time zone information.

code19

The code for this is:

code20

The collection is piped into Where-Object with a filter to test the value of the Current property. Only objects with Current set to True are passed. Its syntactically equivalent to writing

code21

But is quicker to type.

Get-Timezone lists the time zone information

code22

Only the first five entries are shown for brevity. This function just outputs the contents of $timezones to the default output mechanism – the screen

code23

The last function sets the time zone. You can use the Index value as shown in Get-Timezone

code24

Or you can use the name of the time zone.

code25

Modifying the time zone is dependent on three methods in our class definition

code26

SetNotCurrent() sets the value of the Current property to False. SetCurrent() does the opposite and sets the value of Cuurent to True. SetTimeZone() uses tzutil to set current time zone. The time zone name is taken from the TimeZone property of the object – notice the use of $this to access the property.

code27

The function starts with [CmdletBinding()] which makes this an advanced function. There are two possible parameters for the function – Index which takes the index of the time zone and timezone which takes the time zone name. These two parameters are in different parameter sets which makes them mutually exclusive

code28

All time zones are set to be not current

$timezones | foreach SetNotCurrent

This is a shorthand method. The full code would be:

$timezones | foreach {$_.SetNotCurrent()}

The ability to call a function on the current object just by using its name was introduced in PowerShell 3.0. The method can’t take any arguments.

A switch statement is used to create a variable.

code29

The value of the variable is calculated depending on the parameter set in use. $psCmdlet.ParameterSetName can only be used in advanced functions which is why we need [CmdletBinding()] as the first statement. If the Index parameter set is used $tzi is equated to the $index parameter. If the timezone parameter is used $tzi is set to the index of the timezone. If neither is defined an error is used an error is thrown.

The new time zone is set using the SetTimeZone() method.

code34

The new time zone is then set to be the current time zone in $timezones.

Our $timezones variable is important to the functioning of this module so it’s a good idea to hide it so it’s only available to the module:

code35

By default, all functions and variables are exported from a module and made available. By using Export-ModuleMember only the three functions are exported. If you want the $timezones variable exporting, then uncomment the end of the line.

The full module looks like this:

code33 code32I’ve not put any error checking, help or other features to make it production ready to emphasise the working parts of the module and make the explanations simpler.

Using tzutil directly may be simpler (assuming you can remember the syntax) but this makes a nice example of using PowerShell classes and a good introduction to PowerShell modules.

If you are interested in the PowerShell language, how it works and why it works that way I would recommend PowerShell in Action, third edition by Bruce Payette and Richard Siddaway. Bruce was half of the language design team is a leading developer on the PowerShell team and I’ve been using PowerShell since the first beta versions became available.

This article has concentrated on PowerShell classes and modules. If there is a particular PowerShell topic, you’d like covered in more detail leave a comment and I’ll see what can be arranged.