PowerShell Classes Part 5 -- Classes or PSCustom Objects?
Introduced with PowerShell 5.0, Programming with Classes in PowerShell benefits a wide range of scripters and toolmakers, from developers experienced in object-oriented programming to sysadmins who are completely new to scripting. However, with any programming or scripting language, there may be more than one way to accomplish a task and one way may be better or more efficient than another. This article explores a traditional PowerShell programming approach using a custom object and compares it to an object-oriented approach using classes.
Devolutions Remote Desktop Manager
Devolutions RDM centralizes all remote connections on a single platform that is securely shared between users and across the entire team. With support for hundreds of integrated technologies — including multiple protocols and VPNs — along with built-in enterprise-grade password management tools, global and granular-level access controls, and robust mobile apps to complement desktop clients.
Recap and Reasons for the Comparison
There are four articles to date in this series on PowerShell classes. The first article introduces object-oriented programming terminology and the concept of a class. Part two discusses custom types using Enums, and the third article discusses class methods. Lastly, part four talks about defining the specific properties needed for a class using constructors and class inheritance. After Part 1 published, a reader asked if I was going to talk about custom objects vs. classes and I thought that would make a great topic to round out the series. To the reader who requested this, thank you for the idea!
Revisiting the Class Definition for ConstructionMaterial
As a quick recap, the ConstructionMaterial class has 4 properties: Color, Shape, Size, and Location. Size’s data type is an enum that can only be of the values Small, Medium, or Large. For this example, I am not defining any specific constructors. My example object of type ConstructionMaterial is named $ClassObj.
Using a Hash Table to Define Custom Object PropertiesTo compare an instance of a class object with a custom object, I am going to start off by building the custom object new-object and a type of psobject, defining the desired properties in a hash table.
Notice that in this code, I define the structure of the custom object as having the 4 properties Color, Shape, Size, and Location but I am also creating the object itself ($obj). Using new-object creates and instantiates the object all at once. I am not creating the structure of an object and instantiating it later. By using the $props hash table, I can use $props to define additional objects that have the same structure as $obj. The difference between defining the custom object using new-object and using the class is that the class requires a two-step process (defining the class and instantiating it) and using new-object is a single step.
Defining Custom Object Properties Using Add-MemberIf I just create the custom object with no properties instead of using the hash table, I am still only halfway there because I have an object with no properties. Using add-member after the instantiation, I still need to provide not only the property names but a value for each of the properties. However, if I create a second custom object, there is no guarantee that it will have the same properties as the first object because it is truly a custom object with no defined structure. The properties of any custom object can be whatever I define them to be. For example, below I create a custom object $obj with my desired properties for an object that defines ConstructionMaterial.
$Obj | Add-Member -MemberType NoteProperty Color -Value "Red" $Obj | Add-Member -MemberType NoteProperty Shape -Value "Square" $Obj | Add-Member -MemberType NoteProperty Size -Value "Large" $Obj | Add-Member -MemberType NoteProperty Location -Value "4" Now I define a second custom object and this object only has one property. Rather than using the property Size, I have defined the property Bigness, which is also equal to large. Although $obj and $CM are both custom objects, only one of them has the structure that I want for construction material.
A Second Look at the ThrowIt MethodIn the ConstructionMaterial class, I have defined a method named ThrowIt() that increases the value of Location by an amount specified by an integer input argument. Because this is a method of the ConstructionMaterial class, I do not need to pass in the $ClassObj object. Instead, I manipulate the object and its properties through $This.
Writing ThrowIt as a FunctionYou have seen above that when I defined the $CM custom object, 4 methods existed – Equals, GetHashCode, GetType, and ToString, but no ThrowIt method. I will not be able to use a ThrowIt method but I can define a ThrowIt function. I start off with the ThrowIt function declaration and an input parameter $arg, of type int, which will be the distance to throw, just like in the ThrowIt() method. But wait a second… I do not have the original object! I need to either have the object itself passed in or the object’s Location property passed in to know what the initial value of the location was. I am going to pass in the entire custom object.
Then, I call the ThrowIt function, passing in $obj (the whole object) as $InitialObj and I am going to increase the location by 8. I expect that $obj.location is now 12 but as I see from the results below, the function returned 48. Why? Well, the function did not know to treat $InitialObj.Location as an integer and instead, it treated the 4 as a string and concatenated 8 to it. To correct this, I need to strongly type $Initialobj.location as an integer in the function.
ThrowIt and the $CM Custom ObjectNext, I am going to take my $CM custom object and try to use it as input to ThrowIt. Remember, that $CM currently only has one property, Bigness. What do you expect will happen? Well first, what I expect is that I will get an error trying to set $CM.location equal to the result output by ThrowIt because there is no Location property for the $CM object. I need to define a location property for this custom object. Let’s say, that I define $CM.Location to be a string with a value of Wrong instead of an integer. Because $CM is a psobject, the value Wrong is perfectly acceptable for that property but what will happen when we try to call the ThrowIt function? My next expectation is that ThrowIt will throw an error saying it cannot convert Wrong to an integer.
There Is No “Better” ApproachMy intent of these examples is not to determine if one approach is right versus the other being wrong but rather, to point out the differences between the two. Custom objects are fantastic for assembling one-time, unique information together in one object and certainly, there are uses for it. The Get-WMIobject or get-CimInstance cmdlets gather tons of custom information about a machine that can then be sliced and diced into one-time custom objects for additional processing or reporting. The key is custom. If you are truly defining something custom, this implies it is without a defined structure in place. Classes provide structure and that structure is beneficial for defining the methods that can be applied to that rigid structure. Either approach will work but in my opinion, classes are for rigid structures and custom objects are for fluid structures.