2016-12-16

If PowerShell Were School, I would Ditch Classes Regularly



Introduction

When PowerShell v5 was in pre-release, I was still fairly new to PowerShell. I remember being very excited that PowerShell would be adding native classes as I had just discovered that the only way to classes in PowerShell was to write them in C#, which I had not yet begun to learn. Classes are something I have worked with in PHP, Python, PERL, C++, and C#. When I was learning C++ I fell in love with classes and object oriented programming. Most of my exposure to classes has been as a consumer and not an author, but I have written a few classes over the years for various things.

When PowerShell v5 was released, the first thing I did was go and learn how to make v5 classes. This was painful because there was not much documentation beyond basic class structures. As I had no urgent need for classes I let these sit on the back burner for quite some time. I was quite disappointed. A few months ago, I started seeing some of the more experienced PowerShell redditors advocating the use of classes for the purpose of controlling the “shape” of objects. I decided it was probably time to commit to learning PowerShell v5 classes. I’m still disappointed.

As I understand it, the initial use case for inclusion of native classes in v5 is their use within DSC. I spent about a month acclimating to DSC and from that exposure to the DSC syntax, I can certainly see how class based DSC resources would be somewhat easier to write. Consequently, this use case is also the only one that is decently documented. Before I go off on why v5 classes suck, I wanted to make it clear here that I do acknowledge there is a legitimate use case for them where they do shine brilliantly.

BUT…

As the title of this blog suggest, I would recommend avoiding PowerShell v5 classes for now. The implementation of native classes is a mere shadow of .NET classes.  As the implementation is limited that makes complex objects less likely candidates as classes. The already uber-flexible PSCustomObject objects can already meet the needs of most objects while also offering a higher level of compatibility. 

This post will not have much code to it. It is mostly an opinion post with some facts sprinkled in. This post may also be a bit rough for novice coders or those new to PowerShell. If you came here looking for an intro to PowerShell v5 classes, this post will surely and sorely disappoint you.

Anyway, on to my specific gripes…

Version Locking

The first obvious problem with PowerShell v5 Classes is that they version lock you to PowerShell v5 or newer. Features do have to start somewhere and I can’t speak ill of the developers for not making v5 classes backwards compatible. However, this is a concern as v5 is still “new”. If you work at a smaller shop, or you happen to work at a large shop that has a very good Continuous Delivery pipeline, getting to v5 is not a huge hurtle. But, in a more traditional slow moving enterprise, getting to v5 is still a long way off just to get past all the red tape, let alone implement. Before you rush out and start writing v5 classes you need to be cognizant of your current PowerShell ecosystem. Again, this isn’t a real criticism of v5 Classes, but more of a caution against them for now.

No Private Properties and Methods

PowerShell v5 Classes do not support Private Properties or Private Methods. PowerShell v5 does offer a hidden keyword which can be applied to properties and methods. This hides the property or method from the default list of properties and methods from Get-Member and from autocompletion and intellisense. The properties and methods can still be directly accessed by the class consuming code and they can still be listed by Get-Member with the -Force parameter switch. Effectively, hidden is a cosmetic change to the class.

One of the benefits of classes in other languages is that they provide strict control over the class data by having real values in Private Properties acted on by Private Methods. This keeps the validation logic in class consuming functions to a minimum because they only need to validate the object type being passed to it. The rightful assumption is that the class is handing its own validation so the consumers can relax.

Absent private properties and methods, PowerShell v5 classes cannot be assumed to be tightly controlled. This means class consuming code must still include validation logic, error handling, or just let everything fail.

I realize that with some work it is possible to manipulate private data on .NET classes in both PowerShell and C#. The problem is that this is not as easy as simply assigning a value to a property or directly calling the method, as is possible with a hidden property on PowerShell v5 classes. If you begin with a standard property and later make it to hidden without renaming it, legacy class consuming code may still be manipulating the now hidden properties directly and causing undesired results in your class behaviors.

No Get or Set Accessors

PowerShell v5 Class Properties do not have getter and setter accessors. I could be wrong here, but I have dug around for days and have asked in several forums and if people even knew what I was talking about they certainly didn’t know how to do this with PowerShell v5 classes. Since it seems to be a foreign concept in the PowerShell ecosystem, I recommend that if you do not know what they are that you read up on C# Class accessors before continuing.
 
Not all languages with classes have this functionality. But, for those that do it allows for greater control over the property values. As an example, let’s say you had a class that defined a Honda Civic. Honda Civics come in 2 varieties: a 2-door coupe and a 4-door sedan. Honda Civics never have more than 4 doors and they certainly cannot have less than 0 doors. On our HondaCivic class, we would define the Doors property as an Int. Since a PowerShell v5 class cannot implement a set accessor for the Doors property, someone could set it to -80 (negative eighty). Now all the methods on the class or consumers of the class must implement sanity checks to ensure the Doors property is within the bounds of 0-4 inclusive.

Another use case is to have that class adjust other properties when another changed. Continuing with the HondaCivic class example, lets say that the class also has an Enum property CarType. This Enum has 2 possible settings: Sedan or Coupe. In a class that supports get and set accessors on properties, the class could be coded such that setting the Doors property to 4 would change the CarType property to Sedan and when the CarType is set to Coupe the Doors property would be set to 2.

One work around is to use ScriptProperty or CodeProperty properties. A ScriptProperty is a PowerShell property type that allows for setting a get ScriptBlock and a Set ScriptBlock. A CodeProperty allows for a CLR based property that supports both a setter and getter (I only learned about these in researching for this post and there are not many examples readily available). Both ScriptProperty and CodeProperty are available for PSCustomObject and any other object, so it is not unique to classes. 

The problem with a ScriptProperty is that it does not emit a type. What type emission provides is the ability to resolve the type specific methods and properties. For command line, this allows for tab completion and discovery of the properties and methods of an object (the object in this case being the property of a class). When programming, it can be used to call out typos of properties and methods as well as intellisense completion and selection. All in all, type emission is very handy, but not critical.

The problem with a CodeProperty is that you must pull it from compiled CLR code. In other words, you must write in something like C# and either import the compiled DLL or runtime compile it using Add-Type. With that much work, you might as well write the whole class in C#. But, it is handy if you want to borrow properties from existing .NET classes.

Again, the problem with both workarounds is that they are not native or unique to classes. They can be added to classes in exactly the same ways as they can be added to PSCustomObject or any other object.

It’s rather disappointing that the C# parent language supports this functionality but it is absent from PowerShell v5 classes. But, to a degree it makes sense. Since get and set accessors usually update and call private values from the class, and PowerShell v5 does not support private values, it probably made sense not to include the functionality. The problem I have is that it makes no difference to use a PSCustomObject. If Classes supported the property accessors, it would be a strong argument for using classes to more tightly control the class data.

Module Based Classes are a Pain

One of the problems I ran into with PowerShell v5 classes that ultimately led me to writing this blog entry was my experience trying to include classes in a module. The way I like to do modules is to separate out code into individual files. For example, each function in the module is in its own file. I then call these as nested modules in the psd1 of the module. Other module authors use a dot sourcing code block in their psm1.  One of the benefits of making a module out of your code to break it up into more digestible and easier to maintain pieces. 

While working on my Bencode module, I ran into a roadblock trying to get the classes defined in the module to be properly recognized in the calling scope. I had originally defined the classes as I normally do with calling them as nested modules from the psd1. When I would import the module and run a function with one of my classes defined as the output type I would get the error message about an unknown type.

No problem, right? You can just use the Using statement to use the modules types. WRONG! In this configuration, the Using command does not import the class from the module. I could call the Using statement directly against the ps1 where the class is stored, obviously, but that defeats the purpose.
I then tried to dot source the class files from my psm1 and I got the same results. The functions failed with an unknown output type and the Using statement failed as well.

Next, I tried directly defining the classes in the psm1 file. This time it worked. The functions recognized the output type without a Using statement and if I wanted to just use the classes from my module in a script, the Using statement worked as expected. But, now I had a ton of code in my psm1 file for the classes each which have their own complex set of methods and constructors and their many overloads (another minor gripe about classes is that you can’t include source form other files in the class, the entire class must be defined in a single file). This makes the maintainability of the psm1 a nightmare.

I tried several other things that would result in either breaking one or both of either the ability to import classes with the Using statement or the functions properly emitting the type. Ultimately I found that if I defined the ps1 files for each class in the ScriptsToProcess section of the psd1 file, both would work. This kind of cool, but also bad module practice. The files run in the ScriptsToProcess section are done so in the calling scope rather than the module scope. This means unloading your module leaves these bits behind. I haven’t tested it, but believe it will cause issues when you call your module from another module and then try to call the functions of your module from the global scope. In any case, it is a messy and terrible work around.

I read somewhere that loading multiple psm1 files when a module loaded will be supported in the future which will open up the ability to separate out classes into their own files. I think this will be a neat feature regardless of its impact on classes, but, it probably will only work with newer version likely leaving 5.0 and 5.1 systems in an odd spot where newer modules aren't loading classes from the extra psm1 files.

The internet is littered with questions about importing classes from modules and the response is usually about the Using statement. This boils my blood a bit. I import the module and I expect the module's classes to be available, just like when I import modules that include compiled DLL classes. It’s a valid assumption given the history of PowerShell and non-native classes. However, you must use the Using statement to work directly with PowerShell v5 classes defined in modules from the calling scope. 

Another fun fact about the Using statement: in a script, the Using statement(s) must be defined at the very beginning to work properly. I don’t know what all can be defined before the Using statement, but pretty much anything before the Using statement will break it. In the interactive shell, you can call Using at any time and it will work, but that is not the case with scripts.

This all makes PowerShell v5 classes very anti-pattern for what we are used to with PowerShell modules and non-native classes. It makes the v5 classes into special snowflakes. Special snowflakes have a way of not being used because they are a pain to maintain.

Lack of Arbitrary Namespaces

This is, admittedly, a cosmetic gripe and not a functional one. One nice aspect of the .NET framework is that everything is partitioned into namespaces. If you are not familiar with this concept, I’m referring to all those lovely dots in the .NET class names. For example, the System.Management.Automation namespace contains some classes you are probably familiar with, like the PSObject and PSCredential. The point of these namespaces is one of organization. A group of related classes are placed in an arbitrary namespace to make them easy to enumerate and to avoid clashes with objects of similar names in other namespaces.

A use case for this would be developing a module. You may not know what other modules and classes might be imported by consumers of your module. To avoid any name clashes between your classes and another module’s class, you might define your classes under an arbitrary name space that is the same as your module. Let’s say you were creating a CarBuilder module and you had several classes such as Door, Engine, Wheel, and Car. With .NET, you could define the CarBuilder namespace and then your classes under it as Carbuilder.Door, CarBuilder.Engine, CarBuilder.Wheel, and CarBuilder.Car.

This appears to be impossible with PowerShell v5 classes. You can still define your classes with a prefix in the name, but you cannot define them in a namespace. Continuing with the CarBuilder example, you would have to name your classes CarBuilderDoor, CarBuilderEngine, CarBuilderWheel, and CarBuilderCar. It’s somewhat cosmetic in nature, but, it also means that enumerating, autocompleting and intellisensing your classes can only be done by name matching instead of via namespace.

On Performance

One of the areas that initially turned me away from PowerShell v5 classes was that they were less performant than just creating a PSCustomObject with the type accelerator. I say “were” because I planned to repeat those tests for this blog post and I discovered something odd. This time around, a simple class with a String and Int as properties was out performing the PSCustomObject accelerator with the same properties. I’m not sure if my testing method is flawed or if they have made performance improvements in the 5.1.14393.479 version I’m testing with versus the 5.0 when I originally encountered the performance issues. The difference in my initial findings showed the PSCustomObject to be 5 times faster, but this time around the class is 2 times faster. In any case, it’s worth noting that a PowerShell v5 class may or be more or less performant than a PSCustomObject of a similar “shape”. You should test for yourself on your own systems if performance matters.

Why Not Just Use PSCustomObject?

I ask myself this often. There is very little difference between a PowerShell v5 class and a PSCustomObject at this point. Method functionality can be duplicated with ScriptMethod properties. You can set arbitrary type names on PSCustomObject with the use of PSTypeName. You can set custom object types as output types for functions. You can even filter for them in Param blocks. You can use ScriptProperty on either and you can even create hidden properties on PSCustomObject.

The one thing that classes offer over a PSCustomObject at this point is the strict type casting of properties. At least, I haven’t discovered how to duplicate this functionality with a PSCustomObject, but it may exist. Another plus for classes is the ability to explicitly document, via code, what the structure of the object looks like. Since a PSCustomObject is somewhat like an amorphous blob that is defined at creation, it can create problems if you are not strictly controlling how they are created or are creating them in more than one section of code. And finally, using PowerShell v5 classes for class based resources in DSC is an extremely good use case.

But, if you have any kind of version issues or are wanting to use classes in modules, be aware of the issue they cause.

Conclusion

I know that the PowerShell developers have many valid reasons for not implementing all the features I’m whining about here. I’m also not saying everyone should abandon classes. But, I am suggesting that it is not yet time for us to all jump on the class bandwagon. I have hopes that classes will improve in future versions and maybe some of the features lacking now will be added. There were other technical issues with classes I didn’t mention here because they are already slated to be fixed in upcoming versions. It is apparent that the devs care about the classes and won’t leave them feeble for long. But, for now, we should probably stop recommending classes all the time. I understand that they are sexy and new and everyone wants to play with the new toy. I just don’t think we are there yet.