Introduction To PowerShell Classes

This is going to be the first in a series of posts regarding classes. I want to talk more about DSC and especially some of the cool things you can do with class based resources. Before we get to the advanced use cases, we need to cover the basics.

The Good Stuff: An introduction to PowerShell classes.

Why Classes

For me there are two big reasons to use PowerShell classes. The first is if you’re creating your own modules and functions. You can use classes to represent complex data structures. Once the classes are defined, they work just like any other type in PowerShell. This is incredibly helpful when multiple functions need to pass the same data around. You can bind the functions to the specific class type and be done with it. The other big use case is DSC. DSC is gaining more and more traction everyday. With this increased adoption, there is an even larger gap for new resources. DSC Class-based resources are just easier to to develop and maintain. I will detail this process further in an upcoming post.

Class Basics

What Is A Class?

A class is just a template for an object. Classes define how an object should look, what is does and potentially what it takes to create a new one.

Classes And Objects

When we create an instance of a class, it becomes an object made from that template. I always found this concept confusing and want to make sure we define the terms early. A class is a template for what an object should look like. It’s not until we instantiate an instance of that class, that we have an object. For example, we are going to create a Human class. We then use that class to create a Human object. Here $David is an instance (object) of the Human class.

Creating A Class

Classes are defined using the new keyword class.

class Human
{

}

Now that we have our class defined we can create instances of it. There’s a couple of different ways to do this. The first is to use New-Object with the -TypeName switch.

$david = New-Object -TypeName Human

Another way to instantiate a class is to call the static constructor of the class. If you’re unsure what a “static constructor” is, it’s ok. We will cover these concepts later in the article. For now, just familiarize yourself with the syntax below.

$david = [Human]::New()

Thanks to /u/Lee_Dailey for pointing out the speed difference in the two techniques. While New-Object feels like more traditional PowerShell, it is slower than the dot net syntax.

Measure-Command {
    1..10000  | %  {$blah = New-Object -TypeName Human}
}

Measure-Command {
    1..10000  | %  {$blah = [human]::new()}
}

Output:

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 609
Ticks             : 6096586
TotalDays         : 7.0562337962963E-06
TotalHours        : 0.000169349611111111
TotalMinutes      : 0.0101609766666667
TotalSeconds      : 0.6096586
TotalMilliseconds : 609.6586

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 53
Ticks             : 535324
TotalDays         : 6.19587962962963E-07
TotalHours        : 1.48701111111111E-05
TotalMinutes      : 0.000892206666666667
TotalSeconds      : 0.0535324
TotalMilliseconds : 53.5324

Describing The Class

Properties

Properties are things about an object. If we were describing a Human, properties might be height and weight. We add properties to a class by adding variables inside the class. While not required, it is a good idea to define the variable type.

class Human
{
    [string]
    $Name
    
    [int]
    $Height

    [int]
    $Weight
}

Property Validation

Classes also support property validation. Let’s add some validation to make sure we are getting good data.

class Human
{
    [ValidatePattern('^[a-z]')]
    [ValidateLength(3,15)]
    [string]
    $Name
    
    [ValidateRange(0,100)]
    [int]
    $Height

    [ValidateRange(0,1000)]
    [int]
    $Weight
}

Most of the parameter validation you are use to in functions is available to classes properties.

[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[ValidateCount()]
[ValidateSet()]
[AllowNull()]
[AllowEmptyCollection()]
[AllowEmptyString()]
[ValidateRange()]
[ValidatePattern()]
[ValidateLength()] 

Interestingly enough [ValidateScript()] did not work. Any value I tried produced the same error message, you must provide a constant.

+     [ValidateScript({$true})]
+                     ~~~~~~~
Attribute argument must be a constant.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : ParameterAttributeArgumentNeedsToBeConstant
 

Hidden Properties

PowerShell classes also support hidden properties. To hide a property use the hidden keyword just before the property name. Here we will make the ID property a GUID and have it hidden from the user.

class Human
{
    [Guid]
    hidden $ID

    [ValidatePattern('^[a-z]')]
    [ValidateLength(3,15)]
    [string]
    $Name
    
    [ValidateRange(0,100)]
    [int]
    $Height
    
    [ValidateRange(0,1000)]
    [int]
    $Weight
}

Now if we create a new Human object and look at its properties, the $ID property will not be shown.

$someGuy = [Human]::new()
$someGuy

Output:

Name Height Weight
---- ------ ------
          0      0

By default not even Get-Member can see it.

$someGuy = [Human]::new()
$someGuy | Get-Member -MemberType Properties

Output:

   TypeName: Human

Name         MemberType Definition
----         ---------- ----------
Height       Property   int Height {get;set;}
Name         Property   string Name {get;set;}
Weight       Property   int Weight {get;set;}
   

To view the property with Get-Member, you have to include the -force switch.

$someGuy = [Human]::new()
$someGuy | Get-Member -MemberType Properties -Force

Output:

   TypeName: Human

Name         MemberType   Definition
----         ----------   ----------
pstypenames  CodeProperty System.Collections.ObjectModel.Collection`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] pstypenames{get=PSTypeNames;}
Height       Property     int Height {get;set;}
ID           Property     guid ID {get;set;}
Name         Property     string Name {get;set;}
Weight       Property     int Weight {get;set;}                                                                                                                 

One important thing to note with hidden properties is that nothing prevents a user from interacting with them. If a user specifically calls the property it will be displayed. This works when called from Select-Object, any of the Format commands or if the property is referenced by dot notation.

$someGuy = [Human]::new()
$someGuy.ID = (New-Guid).Guid
$someGuy.ID

Output:

Guid                                
----                                
25051c08-66e8-4f7e-a283-7b960a10371a

Default Properties

We can also set default values for a property. In the below example, we’ll set a default value for the ID property.

class Human
{
    [Guid]
    hidden $ID = (New-Guid).Guid

    [ValidatePattern('^[a-z]')]
    [ValidateLength(3,15)]
    [string]
    $Name
    
    [ValidateRange(0,100)]
    [int]
    $Height
    
    [ValidateRange(0,1000)]
    [int]
    $Weight
}

Now if we create a new instance of our class, it will already have a value for the ID.

$someOtherGuy = [Human]::new()
$someOtherGuy.ID

Output:

Guid
----
ab4bdcd9-b076-4869-bdbc-dde6be724b1a

Static Properties

You can also create static properties. Static properties are properties that can be referenced from the class itself, not an instance of that class. What this means, is that instance objects will not have this property. While this seems counter intuitive, it can be useful for helper classes (the math class is a great example of a helper class). To define a static property, we use the Static keyword

class TimeUtilities
{
    static [string]$Time = "The Time is $((Get-Date).ToShortTimeString())"
}

Since we don’t need an object, we call this property directly from the class.

[TimeUtilities]::Time

Methods

Methods are things the object does. Essentially a method is just a function tied to an object. If you have ever written a PowerShell function, you can write a class method.

Return

When working with class methods you need to be explicit about what information the method will return. Due to this, all methods need to be prefixed with the type of data they will return, such as [int] or [string]. Methods that do not return any data need to be prefixed with the type of [void]. After your method has finished its processing, it needs to return an object of that type. This is done using the return keyword. In this example, the talk method will return a string. I’m also going to assign the [void] type to the Jump method since it won’t produce output.

class Human
{
    [Guid]
    hidden $ID = (New-Guid).Guid

    [ValidatePattern('^[a-z]')]
    [ValidateLength(3,15)]
    [string]
    $Name
    
    [ValidateRange(0,100)]
    [int]
    $Height
    
    [ValidateRange(0,1000)]
    [int]
    $Weight

    [void]Jump()
    {
        Write-Output -Message "You won't see this message"
        Return
    }

    [string]SayHello()
    {
        Return "Hello, nice to meet you"
    }
}

$This

When you are inside of a method, the $this variable is automatically created for you. You use the $this variable to make a reference back to your current instance. For example, say you have a Human class with a name property. One of the methods in the class could echo this name to the user. To do that, it needs to reference one of its own properties.

class Human
{
    [string]
    $Name

    [string]SayName()
    {
        Return "Hi My name is $($this.Name)"
    }
}

It’s important to note, that you can also use the $this variable to call methods of your current instance. This is helpful when a class has a helper method used by other methods.

Method Overload

Methods in PowerShell classes support overload. When we overload a method, we define that method more than once with different parameters. This is similar to defining a function with multiple parameter sets. Let’s overload the SayHello method and add a new parameter for name.

class Human
{
    [Guid]
    hidden $ID = (New-Guid).Guid

    [ValidatePattern('^[a-z]')]
    [ValidateLength(3,15)]
    [string]
    $Name
    
    [ValidateRange(0,100)]
    [int]
    $Height
    
    [ValidateRange(0,1000)]
    [int]
    $Weight

    [void]Jump()
    {
        Write-Output -Message "You won't see this message"
        Return
    }

    [string]SayHello()
    {
        
        Return "Hello, nice to meet you"
    }

    [string]SayHello([string]$Name)
    {
            
        Return "Hey $Name. Its nice to meet you"
    }
}

We can inspect the different overload signatures by calling an instance of the class with the method name. Notice there are no parenthesis after the method name so we are not actually invoking it.

$me = New-Object -TypeName Human
$me.SayHello

Output:

OverloadDefinitions                              
-------------------                              
string SayHello()                                
string SayHello(string Name)  

Method Signature

If you don’t provide a type for the parameters of your methods, they will default to System.Object. This can be important because while you can have an unlimited number of method overloads, they all have to have a unique signature. This signature is determined by the number of parameters for the method, those parameter’s types and the order they are passed. To see what I mean, try to run the below example. It should throw an error saying that the HonkHorn method is already defined.

class car 
{
    [Void]HonkHorn([string]$beep)
    {

    }

    [Void]HonkHorn([string]$boop)
    {

    }
}

This next example would be a valid signature since it has a unique order.

class car 
{
    [Void]HonkHorn([string]$beep, [int]$times)
    {

    }

    [Void]HonkHorn([int]$times,[string]$beep)
    {

    }
}

Method Property Validation - Built In

I wanted to include this for the sake of completeness. I was unable to find any type of validation modifiers for parameters to methods. What this means is you need to rely on your code to perform the checks. For example, if your method is expecting a positive number, you couldn’t just add a [ValidateRange()] attribute.

Method Property Validation - Being Clever

Special shout out to Mark Kraus who runs Get-PowerShellBlog for pointing out the below tip. One thing you can do to validate parameters for methods is to create a new class just for the parameter. This new class can only have one property with validation around it. Consider this example.

class ValidatedName 
{
    [ValidatePattern('^[a-z]')]
    [ValidateLength(3, 15)]
    [string]$Value
    
    ValidatedName() 
    {
    
    }
    ValidatedName([string]$String) 
    {
        $this.Value = $String
    }
}

Class MyClass 
{
    [void] MyMethod ([ValidatedName]$Name)
    {
       return
    }
}

$MyObject = [MyClass]::new()
$MyObject.MyMethod('Alfred')

Static Methods

Just like static properties we can define a method to be static. This again is done with the static keyword. Let’s update the TimeUtilities class to work with a new static method.

class TimeUtilities
{
    static [string]$Time = "The Time is $((Get-Date).ToShortTimeString())"

    static [Bool]IsWeekend([DateTime]$DateToTest)
    {
        $sunday = $DateToTest.DayOfWeek -eq [DayOfWeek]::Sunday 
        $saturday = $DateToTest.DayOfWeek -eq [DayOfWeek]::Saturday
        if($sunday -or $saturday)
        {
            Return $true
        }
        else
        {
            Return $false
        }
    }   
}

We could then run this method without an instance of the class.

[TimeUtilities]::IsWeekend((Get-Date))

You can find static methods and properties by piping the class name into Get-Member with the Static switch.

[TimeUtilities] | Get-Member -Static

Output:

   TypeName: TimeUtilities

Name            MemberType Definition
----            ---------- ----------
Equals          Method     static bool Equals(System.Object objA, System.Object objB)
IsWeekend       Method     static bool IsWeekend(datetime DateToTest)
ReferenceEquals Method     static bool ReferenceEquals(System.Object objA, System.Object objB)
Time            Property   static string Time {get;set;}

Constructors

Remember at the beginning of the article when we talked about creating a new object? One option available to us was to call the New static constructor.

$someGuy = [Human]::New()

This New constructor is just a method inherited from the base class. Since New is a method, we can override and overload it just like anything else. We create constructors by creating a new method with the same name as the class. Here I’ll create an overload method to assign the name property at object creation time.

class Human
{
    [Guid]
    hidden $ID = (New-Guid).Guid

    [ValidatePattern('^[a-z]')]
    [ValidateLength(3,15)]
    [string]
    $Name
    
    [ValidateRange(0,100)]
    [int]
    $Height
    
    [ValidateRange(0,1000)]
    [int]
    $Weight

    Human([string]$Name)
    {
        $this.Name = $Name
    }
}

While this works, there’s a catch. When you create your own constructor you lose the base one. Take a look at the output from this command

[Human]::New

Output:

OverloadDefinitions     
-------------------     
Human new(string name)  

In this example, I’m going to keep the original constructor as an option. To do this, I can include an empty Human method with no parameters.

class Human
{
    [Guid]
    hidden $ID = (New-Guid).Guid

    [ValidatePattern('^[a-z]')]
    [ValidateLength(3,15)]
    [string]
    $Name
    
    [ValidateRange(0,100)]
    [int]
    $Height
    
    [ValidateRange(0,1000)]
    [int]
    $Weight

    Human()
    {
        
    }

    Human([string]$name)
    {
        $this.Name = $name
    }
}

With this empty constructor in place my new method shows both signatures.

[Human]::new

Output:

OverloadDefinitions
-------------------
Human new()
Human new(string name)

Inheritance

Creating Child Classes

Inheritance allows us to define one class as a starting point for another. This is helpful when you need multiple classes to share similar methods and properties. Instead of duplicating the code, we can place the shared logic in a base class and then use inheritance to work out the details on the children. Let’s first start by creating a base class for animals.

class animal
{

    [string]
    $Name

    [int]
    $Legs

    [int]
    $Age
    
    [int]
    $WeightLbs

    [int]
    $HeightInches

    animal([string]$NewName)
    {
        $this.Name = $NewName 
    }

    animal()
    {
    
    }

    [string]Jump()
    {
       Return  "look at that $($this.ToString()) jump!"
    }

    [string]Speak()
    {
        Throw [System.NotImplementedException]::New('Speak method should be overridden in child class')
    }
}

I purposely left the Throw statement in the speak method of this base class. The reason behind this, is I want to ensure that any child classes must override it if they want to use it. Next I create a dog class that inherits from our base class of animal, To do this we use the syntax class NewClass : BaseClass.

class dog : animal
{
    [int]
    $TailLength
    
    [int]AgeDogYears()
    {
        return $this.Age * 7
    }
}

Let’s create a new dog and look at its properties and methods.

$spot = [dog]::new()
$spot | Get-Member

Output:

   TypeName: dog

Name               MemberType Definition                       
----               ---------- ----------                       
AgeDogYears        Method     int AgeDogYears()                
Equals             Method     bool Equals(System.Object obj)   
GetHashCode        Method     int GetHashCode()                
GetType            Method     type GetType()                   
Jump               Method     string Jump()                    
Speak              Method     string Speak()                   
ToString           Method     string ToString()                
Age                Property   int Age {get;set;}               
HeightInchesInches Property   int HeightInchesInches {get;set;}
Legs               Property   int Legs {get;set;}              
Name               Property   string Name {get;set;}           
TailLength         Property   int TailLength {get;set;}        
WeightLbs          Property   int WeightLbs {get;set;}    

Thanks to inheritance the child class has all the properties and methods of its parent.

Overriding Methods

We override methods by providing them the same method signature in the child class. Remember how the speak method thew an error in the base class? Let’s add an override to the dog class to make this method work a little better.

class dog : animal
{
    [int]
    $TailLength
    
    [int]AgeDogYears()
    {
        return $this.Age * 7
    }
    
    [string]Speak()
    {
        return "woof!"
    }
}    

Using The Base Keyword

Base Constructors

One thing that will not automatically be inherited is any constructors that take parameters. Before we look at the constructors for our new dog class, let’s look at its parent.

[animal]::new

Output:

OverloadDefinitions                       
-------------------                       
animal new(string NewName)                
animal new()    

Now let’s look at the child’s classes constructors.

[dog]::new

Output:

OverloadDefinitions
-------------------
dog new()          

To regain this functionality we can create a new constructor that matches the method signature of the parent class. We’ll then map this signature to the parent’s signature using : base(Params,Go,Here)

class dog : animal
{
    [int]
    $TailLength
    
    [int]AgeDogYears()
    {
        return $this.Age * 7
    }
    
    [string]Speak()
    {
        return "woof!"
    }

    dog([string]$NewName) : base($NewName)
    {
    
    }

    dog()
    {
    
    }
   
}

With these new constructors in place, the below syntax should work.

$puppy = [dog]::new("Spot")
$puppy.Name

Calling Base Methods

We can also call a base method from an override if we cast $this to the parent class. The syntax is ([baseclass]$this).Method(). In this example, I’m going to create a Kangaroo class that will override the base Jump method. I will use this technique to be able to call the parent’s jump method from inside the override.

class kangaroo : animal
{
    [string]Jump()
    {
        $originalString = ([animal]$this).Jump()
        return  $originalString * 2
    }
}

Now let’s create a new kangaroo and make him jump.

$kango = [kangaroo]::new()
$kango.Jump()

You should get this in your console.

look at that kangaroo jump!look at that kangaroo jump!

Wrapping Up

I hope this was helpful. Classes can take a while to get use to but can be incredibly powerful. In the next post, we’ll talk about how to use a class to create a DSC resource.

Written on May 16, 2017