Using Classes (Constructors – Part 5)

Classes can have so-called constructors. Constructors are methods that initialize a new object. Constructors are simply methods that share the name of the class. With the help of constructors, it can be much easier to create prepopulated objects. Here is an example: the class “Person” defines a person.

There is a constructor, accepting first and last name, as well as birthday.The constructor is called whenever a new object is instantiated, and prepopulates the object properties:

#requires -Version 5.0
class Person 
{ 
  [string]$FirstName
  [string]$LastName
  [int][ValidateRange(0,100)]$Age
  [DateTime]$Birthday

  # constructor
  Person([string]$FirstName, [string]$LastName, [DateTime]$Birthday)
  {
    # set object properties
    $this.FirstName = $FirstName
    $this.LastName = $LastName
    $this.Birthday = $Birthday
    # calculate person age
    $ticks = ((Get-Date) - $Birthday).Ticks
    $this.Age = (New-Object DateTime -ArgumentList $ticks).Year-1
  }
}

With this class, you can now easily create lists of person objects:

[Person]::new('Tobias','Weltner','2000-02-03')
[Person]::new('Frank','Peterson','1976-04-12')
[Person]::new('Helen','Stewards','1987-11-19')

The result looks similar to this:

 
FirstName LastName Age Birthday              
--------- -------- --- --------              
Tobias    Weltner   16 2/3/2000 12:00:00 AM  
Frank     Peterson  40 4/12/1976 12:00:00 AM 
Helen     Stewards  29 11/19/1987 12:00:00 AM
 

Twitter This Tip! ReTweet this Tip!

Using Classes (Overloading – Part 4)

Methods in classes can be overloaded: you can define multiple methods with the same name but different parameters. This works similar to parameter sets in cmdlets. Have a look:

#requires -Version 5.0
class StopWatch 
{ 
  # property is marked "hidden" because it is used internally only
  # it is not shown by IntelliSense
  hidden [DateTime]$LastDate = (Get-Date)
  
  # when no parameter is specified, do not emit verbose info

  [int] TimeElapsed()
  {
    return $this.TimeElapsedInternal($false)
  }
  
  # user can decide whether to emit verbose info or not
  [int] TimeElapsed([bool]$Verbose)
  {
    return $this.TimeElapsedInternal($Verbose)
  }
  
  # this method is called by all public methods
  
  hidden [int] TimeElapsedInternal([bool]$Verbose)
  {
    # get current date
    $now = Get-Date
    # and subtract last date, report back milliseconds
    $milliseconds =  ($now - $this.LastDate).TotalMilliseconds
    # use $this to access internal properties and methods
    # update the last date so that it now is the current date
    $this.LastDate = $now
    # output verbose information if requested
    if ($Verbose) { 
      $VerbosePreference = 'Continue'
      Write-Verbose "Last step took $milliseconds ms." }
    # use "return" to define the return value
    return $milliseconds
  }
  
  Reset()
  {
    $this.LastDate = Get-Date
  }
}

# create instance
$stopWatch = [StopWatch]::new()

# do not output verbose info
$stopWatch.TimeElapsed()


Start-Sleep -Seconds 2
# output verbose info
$stopWatch.TimeElapsed($true)

$a = Get-Service
# output verbose info
$stopWatch.TimeElapsed($true)

The result would look similar to this:

 
0
VERBOSE: Last step took  2018.1879 ms.
2018
VERBOSE: Last step took  68.8883 ms.
69
 

Twitter This Tip! ReTweet this Tip!

Using Classes (Adding Methods – Part 3)

One of the great advantages of classes vs. [PSCustomObject] is their ability to also define methods (commands). Here is an example that implements a stop watch. The stop watch can be used to measure how long code takes to execute:

#requires -Version 5.0
class StopWatch 
{ 
  # property is marked "hidden" because it is used internally only
  # it is not shown by IntelliSense
  hidden [DateTime]$LastDate = (Get-Date)

  [int] TimeElapsed()
  {
    # get current date
    $now = Get-Date
    # and subtract last date, report back milliseconds
    $milliseconds =  ($now - $this.LastDate).TotalMilliseconds
    # use $this to access internal properties and methods
    # update the last date so that it now is the current date
    $this.LastDate = $now
    # use "return" to define the return value
    return $milliseconds
  }
  
  Reset()
  {
    $this.LastDate = Get-Date
  }
}

And this is how you would use the new stop watch:

# create instance
$stopWatch = [StopWatch]::new()

$stopWatch.TimeElapsed()

Start-Sleep -Seconds 2
$stopWatch.TimeElapsed()

$a = Get-Service
$stopWatch.TimeElapsed()

The result would look similar to this:

 
0
2018
69
 

When you define methods inside a function, there are a couple of rules:

  • If a method has a return value, the return value type must be specified
  • The return value of a method must be specified using the keyword “return”
  • Methods cannot use unassigned variables or read variables from parent scopes
  • To reference a property or other method within the class, prepend “$this.”

Twitter This Tip! ReTweet this Tip!

Using Classes (Initializing Properties – Part 2)

Class properties can be assigned a mandatory type and a default value. When you instantiate an object from a class, the properties are pre-populated and accept only the data type specified:

#requires -Version 5.0
class Info 
{ 
  # strongly typed properties with default values
  [String]
  $Name = $env:USERNAME

  [String]
  $Computer = $env:COMPUTERNAME
  
  [DateTime]
  $Date = (Get-Date)
}

# create instance
$infoObj = [Info]::new()

# view default (initial) values
$infoObj

# change value
$infoObj.Name = 'test'
$infoObj

Twitter This Tip! ReTweet this Tip!

Using Classes (Creating Objects – Part 1)

Beginning in PowerShell 5, there is a new keyword called “class”. It creates new classes for you. You can use classes as a blue print for new objects. Here is code that defines the blueprint for a new class called “Info”, with a number of properties:

#requires -Version 5.0
class Info 
{ 
  $Name
  $Computer
  $Date 
}

# generic syntax to create a new object instance
$infoObj = New-Object -TypeName Info

# alternate syntax PS5 or better (shorter and faster)
$infoObj = [Info]::new()
 

$infoObj

$infoObj.Name = $env:COMPUTERNAME
$infoObj.Computer = $env:COMPUTERNAME
$infoObj.Date = Get-Date

$infoObj
$infoObj.GetType().Name

You can use New-Object to create as many new instances of this class. Each instance represents a new object of type “Info” with three properties.

 
Name            Computer        Date               
----            --------        ----               
                                                   
DESKTOP-7AAMJLF DESKTOP-7AAMJLF 1/2/2017 2:00:02 PM
Info
 

This is just a very simple (yet useful) example of how to use classes to produce objects. If you just want to store different pieces of information in a new object, you could as well use [PSCustomObject] which was introduced in PowerShell 3:

#requires -Version 3.0
$infoObj = [PSCustomObject]@{
  Name = $env:COMPUTERNAME
  Computer = $env:COMPUTERNAME
  Date = Get-Date
}

$infoObj
$infoObj.GetType().Name

This approach does not use a blueprint (class) and instead creates individual new objects based on a hash table:

 
Name            Computer        Date               
----            --------        ----               
DESKTOP-7AAMJLF DESKTOP-7AAMJLF 1/2/2017 2:02:39 PM
PSCustomObject
 

So the type of the produced object is always “PSCustomObject” whereas in the previous example, the object type was defined by the class name.

Twitter This Tip! ReTweet this Tip!

Using “Using Namespace”

Working with .NET type names can be tiring because these names can be long. Here is an example:

#requires -Version 2.0

Add-Type -AssemblyName System.Speech
$speak = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer
$speak.Speak('Hello I am PowerShell!')

In PowerShell 5 and better, you can define the .NET namespaces you want to work with. These “using namespace” statements must be at the start of your script. Code now becomes a lot easier to read, and the using statements clarify what .NET namespaces a script is using:

#requires -Version 5.0

using namespace System.Speech.Synthesis

Add-Type -AssemblyName System.Speech

$speak = New-Object -TypeName SpeechSynthesizer
$speak.Speak('Hello I am PowerShell!')

Here is another example: the .NET namespace “System.IO.Path” contains a great number of useful path helper methods. Here are some examples:

[System.IO.Path]::ChangeExtension('test.txt', 'bat')
[System.IO.Path]::GetExtension('test.txt')

Instead of having to repeatedly use [System.IO.Path] to access these methods, you can add a “using namespace System.IO” statement and access the type via [Path] only:

#requires -Version 5.0

using namespace System.IO

[Path]::ChangeExtension('test.txt', 'bat')
[Path]::GetExtension('test.txt')

Twitter This Tip! ReTweet this Tip!