You should be using PowerShell classesLeveraging classes while keeping code functionalChristopher KuechBlockedUnblockFollowFollowingApr 14Before you continue, learn the basic semantics of using and defining classes: About ClassesLanguages like Java and C# force developers to organize their code in methods within classes, while languages like PowerShell encourage developers to organize their code in functions within modules.
Instead of using classes for organizing code, we will use classes to amplify our functional code, providing typing and defensive programming to our code without sacrificing our functional programming flow.
No more surprisesPowerShell has powerful but sometimes unintuitive programming features that can cause headaches for new users.
PowerShell classes follow rules more familiar to programmers from conventional programming languages and can help avoid headaches.
Equivalent implementations of a trivial function using static method vs a functionSimplified inputsPowerShell functions are extremely powerful.
With a few lines of parameter attributes, you can have a full featured interface for your function.
These interfaces are great for user-facing functions, but this user-facing presentation logic should not be kept with our core implementation logic.
Method inputs are much simpler, with each parameter being mandatory by default, and can therefore keep internal business logic simple.
By isolating business logic, you can implement unit tests around only your core business logic and lean on the declarative parameter validation for code hygiene on your presentational cmdlets.
Simplified outputsOne of the biggest surprises for people learning PowerShell is that return statements aren’t the only statements that output a value from the function — every non-void expression writes a value to the output.
In methods, only return statements output a value, making them easier to test, debug, and comprehend.
Methods also support simple return type declaration in the method headers, ensuring your function always returns the type you expect (or throws a type error).
Casting magicCasting is one of the most powerful features in PowerShell.
When you cast a value, you can trigger abstracted initialization and validation code in your application.
For example, just casting a string with [xml] will trigger code to parse the string into a complete xml tree.
We can leverage classes to implement the same features in our own code.
To learn more about validation attributes, read Defensive PowerShell.
Casting hashtablesIf you don’t have a constructor, you can cast hashtables to your class type without any modification to your class.
Be sure to add validation attributes to fully leverage this pattern.
We can also use custom types for the type of our class properties, to trigger even more validation and initialization logic.
Casting also enables cleaner output.
Compare the output from an array of Cluster hashtables piped to Format-Table, versus casting those hashtables to [Cluster] before for piping to Format-Table.
The properties are always listed in the order they are defined on the class.
Be sure to include the hidden keyword before any properties that should not be visible when outputting the table.
Casting valuesIf you have a single-argument constructor, casting a value to your class type will pass the value to your single-argument constructor, where you can initialize your class instance.
Casting to stringYou can also override the [string] ToString() method on the class to define the conversion logic for converting the object to a string, such as when including the object in string interpolation.
Casting serialized instancesCasting enables safe input from serialization formats.
The examples below will fail if the data does not meet our specifications in Cluster.
Validating serialized dataKeep it functionalFunctional programs define data structures first, then implement the program as a series of transformations upon the immutable data structures.
While it may seem contradictory, classes actually facilitate functional code in PowerShell.
Is my PowerShell functional?Many people coming from a C# or similar background write PowerShell that resembles C#.
If you do this, you are not leveraging functional concepts and would probably benefit from doubling down on object-oriented programming in PowerShell or learning more about functional programming.
If you rely heavily on transforming immutable data using pipelines (|) , Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object, etc, you have a more functional PowerShell style and will benefit from using PowerShell classes in a functional way.
Using classes functionallyCasting, though it uses different syntax, is just a mapping function between two domains.
We can map values of an array in a pipeline using ForEach-Object.
In the example below, the Node constructor is run every time we cast to Datum, allowing us to abstract away a fair amount of code.
As a result, our pipeline only focusses on declarative data querying and aggregation while our classes focus on data parsing and validation.
Example combining classes with pipelines for implementing separation of concerns in pipelinesPackaging the class for reuseNothing is as good as it seemsUnfortunately, classes cannot be exported from modules in the same way as functions or variables; however, there are some workarounds.
Assuming your classes are defined in a file .
ps1:You can dot-source the file containing the classes: .
This will execute my-classes.
ps1 in your current scope, defining all the classes in your file.
You can create a PowerShell module that exports all your user-facing cmdlets and set ScriptsToProcess = ".
ps1" in your module manifest file, which will similarly run .
ps1 in your environment.
Whichever approach you take, keep in mind that the PowerShell type system cannot resolve types if they come from two separate places.
Even though you have two identical classes with the same names and all the same properties, if they are loaded from two separate locations, you might find yourself facing confusing type issues.
The path forwardThe best way to avoid type resolution issues is to never expose your classes to users.
Rather than expect your user to import the class type, instead export a function from your module that abstracts away the need for directly accessing the class.
For example, for Cluster, we would export a function New-Cluster that supports user-friendly parameter sets and returns a Cluster.
Further ReadingAbout ClassesDefensive PowerShell.