Expanding on your first PowerShell class
- The many layers of an onion
- Building your first PowerShell Class
- Deconstructing the components of a Class
- Expanding on your first PowerShell class
- Unit Testing PowerShell Classes with Pester
This post is a continuation of Building your first PowerShell Class. If you haven’t read the earlier post, then I would definitely suggest starting there. Today, we are going to continue developing our little class into something with a bit more pizzazz. We will start from our previous module and continue adding new methods (Get the code from yesterday). Let’s review what we would want our class to do at the end of the day.
- Display a list of Toy names and their Location. Maybe assign preferential treatment to some of the toys
- Add/Remove/Update toys
- Export and import my toys (I don’t want to have to create the list every time and it is overkill to use a database)
Today, we are going to add some more methods to our code and talk about Overloading a method. From time to time, you have probably run into an error similar to Cannot find an overload for “Get” and the argument count: “1”. In PowerShell, an overload is how we handle passing parameters into a method. Let’s say we want to return just a single toy from our ToyBox class. We add an overload to the method list. Take a look at this example:
[ToyBox] Get([string] $Name) { $___Results = $this.GetMyItems() $___thisResult = $___Results | ?{$_.Name -eq $Name} if (-not [string]::IsNullOrEmpty($___thisResult)) { $this.Name = $___thisResult.Name $this.State = $___thisResult.State $this.Favorite = $___thisResult.Favorite } else { Write-Warning ("The toy [{0}] was not found." -f $Name) } return $this }
Here we added a second method definition in our class to handle what would happen if we passed an argument $Name as part of the call. We want to pull the item and if it is found, then return the output with all the relevant information. Otherwise, we will print a warning that the toy doesn’t exist. Now that we have added the ability to pull a single item, we should include the ability to do something with that item. Notice that we are returning a single object with this method. Our original GET() method returned an array of our ToyBox class (ToyBox[]) and in this case, we return just one object (ToyBox). Now that we have an object returned, we can change the properties and SAVE() as before.
Let’s talk about chaining methods and performing multiple actions. You are very familiar with this concept when you pipeline PowerShell cmdlets. Let’s look at an example:
[ToyBox] Play() { $this.State = "Playing" return $this } [ToyBox] Store() { $this.State = "Stored" return $this } [ToyBox] Lost() { $this.State = "Missing" return $this } [ToyBox] Like() { $this.Favorite = $true return $this } [ToyBox] Hate() { $this.Favorite = $false return $this }
Each of our new methods alter the current value of a property and returns the object back to the variable. We can use this to streamline how we interact and update data. Let’s consider the follow way that we can interact with the toys. I was to Play with my new Favorite toy named Dragon.
Just like that we can quickly and easily add behaviors to our class. We don’t need to create multiple cmdlets to facilitate the work. Everything can succinctly exist in our class and ultimately become an extremely powerful tool in our ability to manage resources and infrastructure or even toys.
Now that you have had a chance to see the power of classes, we will start to look at how to test your classes using Pester. As we have learned in previous articles, Unit Testing is a vital part to the development process. Until next time.
Here is all the code from today. Feel free to download and play with it.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# Example PowerShell v5 class | |
# | |
# Jeremy Goodrum – ExosphereData, LLC 2017 | |
# | |
enum EnumState | |
{ | |
Missing | |
Stored | |
Playing | |
} | |
class ToyBox | |
{ | |
# Name of the Toy or Item to be stored | |
[string] $Name | |
# Current State or Location of the Toy | |
[EnumState] $State | |
# This is currently a Favorited item or toy | |
[boolean] $Favorite | |
hidden [System.Object[]] $stored_Items | |
[ToyBox[]] Get() | |
{ | |
return $this.GetMyItems() | |
} | |
[ToyBox] Get([string] $Name) | |
{ | |
$___Results = $this.GetMyItems() | |
$___thisResult = $___Results | ?{$_.Name -eq $Name} | |
if (-not [string]::IsNullOrEmpty($___thisResult)) | |
{ | |
$this.Name = $___thisResult.Name | |
$this.State = $___thisResult.State | |
$this.Favorite = $___thisResult.Favorite | |
} else { | |
Write-Warning ("The toy [{0}] was not found." -f $Name) | |
} | |
return $this | |
} | |
[void] Save() | |
{ | |
$this.SaveMyItems() | |
} | |
[void] Remove() | |
{ | |
$this.RemoveThisToy($this.Name) | |
} | |
[void] Remove([string]$___Name) | |
{ | |
$this.RemoveThisToy($___Name) | |
} | |
[void] Save([string]$___Name) | |
{ | |
$this.Name = $___Name | |
$this.State = "Missing" | |
$this.Favorite = $false | |
$this.SaveMyItems() | |
} | |
[void] Save([string]$___Name,[EnumState]$___State) | |
{ | |
$this.Name = $___Name | |
$this.State = $___State | |
$this.Favorite = $false | |
$this.SaveMyItems() | |
} | |
[void] Save([string]$___Name,[EnumState]$___State,[boolean]$___Favorite) | |
{ | |
$this.Name = $___Name | |
$this.State = $___State | |
$this.Favorite = $___Favorite | |
$this.SaveMyItems() | |
} | |
[ToyBox] Play() | |
{ | |
$this.State = "Playing" | |
return $this | |
} | |
[ToyBox] Store() | |
{ | |
$this.State = "Stored" | |
return $this | |
} | |
[ToyBox] Lost() | |
{ | |
$this.State = "Missing" | |
return $this | |
} | |
[ToyBox] Like() | |
{ | |
$this.Favorite = $true | |
return $this | |
} | |
[ToyBox] Hate() | |
{ | |
$this.Favorite = $false | |
return $this | |
} | |
[void] Export () | |
{ | |
$this.stored_Items | ConvertTo-Json | Out-File –FilePath .\my_toys.txt | |
} | |
[void] Import () | |
{ | |
$___saveFile = (Get-Content –Path .\my_toys.txt) | |
$this.stored_Items = ( $___saveFile | ConvertFrom-Json ) | |
} | |
hidden [string[]] SaveMyItems () | |
{ | |
$___storedItems = $this.GetMyItems() | |
$__existingItem = $___storedItems | ?{$_.Name -eq $this.Name} | |
if (-not [string]::IsNullOrEmpty($__existingItem) ){ | |
$__existingItem.Name = $this.Name | |
$__existingItem.State = $this.State | |
$__existingItem.Favorite = $this.Favorite | |
$__currentList = ($___storedItems | ?{$_.Name -ne $this.Name}) | |
$__currentList += $__existingItem | |
$this.stored_Items = ($__currentList | ConvertTo-Json) | |
} | |
else{ | |
$__newItem = @{ | |
Name = $this.Name | |
State = $this.State | |
Favorite = $this.Favorite | |
} | |
$___storedItems += $__newItem | |
$this.stored_Items = ($___storedItems | ConvertTo-Json) | |
} | |
return $this.stored_Items | |
} | |
hidden [void] RemoveThisToy ([string] $___Name) | |
{ | |
$___storedItems = $this.GetMyItems() | |
$__existingItem = $___storedItems | ?{$_.Name -eq $___Name} | |
if (-not [string]::IsNullOrEmpty($__existingItem) ){ | |
$__currentList = ($___storedItems | ?{$_.Name -ne $this.Name}) | |
$this.stored_Items = ($__currentList | ConvertTo-Json) | |
} | |
} | |
hidden [ToyBox[]] GetMyItems() | |
{ | |
if (-not [string]::IsNullOrEmpty($this.stored_Items) ){ | |
return ($this.stored_Items | ConvertFrom-Json) | |
} else { | |
return $null | |
} | |
} | |
static [ToyBox[]] op_Addition([ToyBox] $Left, [ToyBox] $Right) | |
{ | |
$__current = @() | |
$__current += ($Left | ConvertTo-Json) | |
$__current += ($Right | ConvertTo-Json) | |
return ($__current | ConvertFrom-Json) | |
} | |
} | |
function New-ToyBox{ | |
param( | |
[string] $Name, | |
[EnumState] $State, | |
[boolean] $Favorite | |
) | |
$__toyBox = [ToyBox]::new() | |
if($Name) {$__toyBox.Name = $Name} | |
if($State) {$__toyBox.State = $State} | |
if($Favorite) {$__toyBox.Favorite = $Favorite} | |
return $__toyBox | |
} |