Building your first PowerShell Class
Yesterday, we talked about the components of a class. Today, I want to go through building a new PowerShell v5 Class and showcase some of the interesting nuances. Last night, I played with my toddler son in his playroom. To say that he has too many toys is probably an understatement. Since the oldest is off to college and Momma buys him too many toys, the little one has run of the house with toy boxes everywhere. It is a site to see. So there we sat on the floor, playing with some of his favorite stuffed animals. He loves when I do the crazy voices for the animals and it is such a highlight to watch his eyes light up as he interacts. It had me thinking about all the toys scattered across the house and how in the world would we be able to keep track of them. So after he went to bed, I decided to bang out this article and write a new ToyBox class.
Declaring our class
In this case, I want a class that will handle some basic and more advanced concepts. Remember, we are learning about PowerShell classes because we ultimately want to use them for our PowerShell DSC projects. So we will start with some basic concepts of a ToyBox. Let’s consider 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)
Now that we have a plan of what we want to create, we should start with a basic code structure. Today, we are going to introduce a couple of interesting features. I would like to introduce a get new feature in PowerShell v5 called Enum. Traditionally, we would need to create some very complicated ValidateSet() configurations in our parameters and managing these across multiple states was a challenge. Even worse is when we want to make sure that a new compiled or returned value fits our needs. The benefit of Enum is that it is a type-cast and is used anywhere that you want to apply that validation. Later you will see how we leverage this in the code.
enum EnumState { Missing Stored Playing }
We next need to declare our class name – ToyBox and the list of our properties. I decided to go with three options – Name, State, and Favorite. When I declare the class, these properties would all be visible and is set by the user. I can then assign the class object of [ToyBox]::new() to a variable and play with the outcome.
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 # In memory container for all of the items that we have hidden [System.Object[]] $stored_Items # New method to return all an object of Type [ToyBox] containing # the currently declared object. [ToyBox] Get() { Write-Host "We will return the object" return $this } }
You will also notice that I included a function called New-ToyBox as part of the declaration. This is important and helpful so that we can declare a new invocation of the class and assign it to a variable.
function New-ToyBox{ param( [string] $Name, [string] $State, [boolean] $Favorite ) $__toyBox = [ToyBox]::new() if($Name) {$__toyBox.Name = $Name} if($State) {$__toyBox.State = $State} if($Favorite) {$__toyBox.Favorite = $Favorite} return $__toyBox }
Now, the code that we created doesn’t do much but it is a good place to start. We still need a way to store new items in the toy box. For this, we will use a new method called Save() and use it to add items to our $stored_items property (Note: hidden properties are used inside of the class and are very handy to store extra data that the user doesn’t need to see). The code will need to handle two situations – A new addition to the toy box and updating the item if it exists. Let’s take a look at how we can do this. You will notice that we added to methods Save() and SaveMyItems(). The Save() method is a public method that will return nothing. A [void] essentially says to expect nothing in return and essentially pipe-out an empty string. A hidden method is a private method that would not be available to any one that instantiated the class but rather could be called by other methods. It is always a good idea to move any code that you will likely reuse many types in a class over to a different private (aka hidden) method. In this case, I am moving all the saving logic to the hidden method so I can use it for other methods. You will also notice that I am returning an array of objects back. This is not required but if I wanted a different method that is not [void] to use this method, then I could. I also added a second private method called GetMyItems() to all me the ability to process the stored items and return the object.
[ToyBox[]] Get() { return $this.GetMyItems() } [void] Save() { $this.SaveMyItems() } hidden [string[]] SaveMyItems () { # Pull all of the stored_Items and return them for processing. $___storedItems = $this.GetMyItems() # Private Method to process the stored_Items $__existingItem = $___storedItems | ?{$_.Name -eq $this.Name} if (-not [string]::IsNullOrEmpty($__existingItem) ){ # If the item is already in the object, then we will want to # update the values based on the current state $__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{ # Since this item doesn't exist, we will just need to go ahead # create it here. First, we will declare a hash of the object # properties and then add it to our stored items. Finally, # we will convert the entire thing into a JSON object to be stored # in our hidden property. $__newItem = @{ Name = $this.Name State = $this.State Favorite = $this.Favorite } $___storedItems += $__newItem $this.stored_Items = ($___storedItems | ConvertTo-Json) } return $this.stored_Items } 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) }
NOTE: In order to handle adding one [ToyBox] object to another, I had to add the op_Addition method. This is a basic example using an array and JSON to build everything.
Ok, we have done a lot and we should have a working model. Below you can see some of my tests. Tomorrow, we will dive further into the toy box and complete this project by adding some more advanced features and options.
Want to try this at home?
Here is all of 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 for a ToyBox | |
# | |
# Jeremy Goodrum – ExosphereData, LLC 2017 | |
# http://www.exospheredata.com/2017/01/12/building-your-first-powershell-class | |
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 | |
# New method to return all an object of Type [ToyBox] containing | |
# the currently declared object. | |
[ToyBox[]] Get() | |
{ | |
return $this.GetMyItems() | |
} | |
[void] Save() | |
{ | |
$this.SaveMyItems() | |
} | |
hidden [string[]] SaveMyItems () | |
{ | |
# Pull all of the stored_Items and return them for processing. | |
$___storedItems = $this.GetMyItems() | |
$__existingItem = $___storedItems | ?{$_.Name -eq $this.Name} | |
if (-not [string]::IsNullOrEmpty($__existingItem) ){ | |
# If the item is already in the object, then we will want to | |
# update the values based on the current state | |
$__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{ | |
# Since this item doesn't exist, we will just need to go ahead | |
# create it here. First, we will declare a hash of the object | |
# properties and then add it to our stored items. Finally, | |
# we will convert the entire thing into a JSON object to be stored | |
# in our hidden property. | |
$__newItem = @{ | |
Name = $this.Name | |
State = $this.State | |
Favorite = $this.Favorite | |
} | |
$___storedItems += $__newItem | |
$this.stored_Items = ($___storedItems | ConvertTo-Json) | |
} | |
return $this.stored_Items | |
} | |
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 | |
} |