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!

Determining Person Age

How do you calculate the age of a person, based on birthday? You can subtract the current time delivered by Get-Date from the birthday, but the result does not contain years:

#requires -Version 1.0

$birthday = Get-Date -Date '1978-12-09'
$today = Get-Date
$timedifference = $today - $birthday

$timedifference

Here is the result:

 
Days              : 13905
Hours             : 16
Minutes           : 34
Seconds           : 58
Milliseconds      : 575
Ticks             : 12014516985758198
TotalDays         : 13905.6909557387
TotalHours        : 333736.582937728
TotalMinutes      : 20024194.9762637
TotalSeconds      : 1201451698.57582
TotalMilliseconds : 1201451698575.82
 

To calculate the years, take the number of “ticks” (the smallest unit of time measurement), and convert it to a datetime, then take the year and subtract one:

#requires -Version 1.0
$birthdayString = '1978-12-09'
$birthday = Get-Date -Date $birthdayString
$today = Get-Date
$timedifference = $today - $birthday
$ticks = $timedifference.Ticks
$age = (New-Object DateTime -ArgumentList $ticks).Year -1
"Born on $birthdayString = $age Years old (at time of printing)"

And this is what the result would look like:

 
Born on 1978-12-09 = 38 Years old (at time of printing)
 

Twitter This Tip! ReTweet this Tip!

Speeding Up New-Object Synthesizer

New-Object creates new instances of objects, and you have seen one example in the past “Speech Week”: PowerShell was able to create a new speech synthesizer object, and convert text to speech:

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

The approach is always the same, so when you use a different class like System.Net.NetworkInformation.Ping, you can ping IP addresses or host names:

$ping = New-Object -TypeName System.Net.NetworkInformation.Ping
$timeout = 1000
$result = $ping.Send('powershellmagazine.com', $timeout)

$result

In PowerShell 5 or better, there is an alternative to New-Object that works faster: the static method New() which is exposed by any type. You could rewrite the above examples like this:

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

Likewise:

$ping = [System.Net.NetworkInformation.Ping]::New()
$timeout = 1000
$result = $ping.Send('powershellmagazine.com', $timeout)

$result

Or, if you prefer, even shorter:

[System.Net.NetworkInformation.Ping]::New().Send('powershellmagazine.com', 1000)

Note: once you use New() instead of New-Object, your code requires at least PowerShell 5.

Twitter This Tip! ReTweet this Tip!

Speech-Week: Using Advanced Speech Synthesizer Options Synthesizer

The .NET speech engine accepts more than just plain text. If you use SpeakSsml() instead of Speak(), you can use XML to switch languages, speak rate, and other parameters within a text.

The following example requires both an English and a German voice installed. If you don’t have a German voice installed, change the language ID in the script appropriately. Here is how you find out the language IDs available on your system:

 
PS C:> Add-Type -AssemblyName System.Speech 

PS C:> $speak.GetInstalledVoices() | Select-Object -ExpandProperty VoiceInfo | Select-Object -ExpandProperty Culture | Sort-Object -Unique

LCID             Name             DisplayName                                                                                         
----             ----             -----------                                                                                         
1031             de-DE            German (Germany)                                                                                    
1033             en-US            English (United States)
 

And here is the full example:

#requires -Version 2.0
Add-Type -AssemblyName System.Speech
$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
$ssml = '
<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" 
    xml:lang="en-US">
    <voice xml:lang="en-US">
    <prosody rate="1">
        <p>I can speak English!</p>
    </prosody>
    </voice>
    <voice xml:lang="de-DE">
    <prosody rate="1">
        <p>und ich kann auch deutsch sprechen!</p>
    </prosody>
    </voice>
    <voice xml:lang="en-US">
    <prosody rate="0">
        <p>...and sometimes I get really tired.</p>
    </prosody>
    </voice>
</speak>
'

$speak.SpeakSsml($ssml)

Twitter This Tip! ReTweet this Tip!