For many years, PowerShell scripters happily lived without classes. We built objects using PSCustomObject, manipulated data through the pipeline, and automated an incredible amount of work. For many scenarios, that is still the right approach. Yet there comes a point where scripts become applications.
If you’ve ever found yourself maintaining a PowerShell script with hundreds or even thousands of lines of code, you’ve probably experienced some of these symptoms:
- Functions calling functions calling functions
- Variables passed through half the script
- Business logic scattered everywhere
- Repeated validation code
- Objects that accidentally lose properties
- Difficulty testing individual components
At some point, adding another function no longer makes the script easier to maintain. That is where classes can help.
The Traditional Approach
Let’s say we’re managing servers. A common PowerShell pattern might look like this:
$server = [PSCustomObject]@{
Name = 'SRV01'
IPAddress = '192.168.1.10'
Online = $false
}
Simple.
Need another server?
$server2 = [PSCustomObject]@{
Name = 'SRV02'
IPAddress = '192.168.1.11'
Online = $false
}
Need to check if a server is online?
function Test-ServerOnline {
param(
$Server
)
$Server.Online = Test-Connection -ComputerName $Server.Name -Count 1 -Quiet
}
Still manageable.
Now imagine handling:
- Monitoring
- Maintenance windows
- Patch status
- Backup status
- Ownership
- Environments
- Alerting
Suddenly the script turns into a monster.
Thinking in Objects
Instead of treating a server as a collection of properties, we can treat it as a thing.
A server has, for instance:
Properties
Name
IPAddress
Environment
Online
and
Behaviour
Check connectivity
Get patch status
Restart
Generate report
This could we map perfectly to a PowerShell class.
class Server {
[string]$Name
[string]$IPAddress
[bool]$Online
}
Now we have an object describing what a server is.
Giving Objects Behaviour
Objects become powerful when they can do things themselves. Think of checking whether a server is online or pingable.
class Server {
[string]$Name
[string]$IPAddress
[bool]$Online
[bool] IsOnline() {
return $(Test-Connection -ComputerName $this.Name -Count 1 -Quiet)
}
}
Creating an instance:
$server = [Server]::new()
$server.Name = "SRV01"
$server.IPAddress = "192.168.1.10"
Running a check:
$server.IsOnline()
Output:
True
Notice what’s happening. Instead of:
Test-ServerOnline -Server $server
we now have:
$server.IsOnline()
The logic now lives where it belongs, inside the object.
Constructors: Building Objects Correctly
One problem with traditional objects is that people can forget properties.
This is perfectly normal:
$server = [PSCustomObject]@{
Name = 'SRV01'
}
But …
$server.IPAddress
returns nothing.
Classes can force proper object creation. This allows us to prevent the creation of partially initialized objects.
class Server {
[string]$Name
[string]$IPAddress
Server(
[string]$Name,
[string]$IPAddress
) {
$this.Name = $Name
$this.IPAddress = $IPAddress
}
}
$server = [Server]::new(
"SRV01",
"192.168.1.10"
)
Strong Typing
Strong typing is where classes start becoming genuinely useful.
The traditional approach allows:
$server.Online = "Banana"
PowerShell custom objects allow unexpected values to creep into objects, but with classes we have strongly typed variables.
[bool]$Online
Now PowerShell enforces the correct type.
$server.Online = "Banana"
Cannot convert value "Banana" to type "System.Boolean"
The earlier we catch mistakes, the fewer production incidents we create.
The Real Benefit Isn’t Syntax
The reason where many class tutorials go wrong is that they focus entirely on syntax. The real benefit isn’t that classes are “object-oriented”, but the real benefit is organisation.
Without classes functions, variables, validation, business rules, logging etc. exist everywhere. With classes, we group everything related to the server under the object. You stop thinking in terms of scripts, you start thinking in terms of systems.
A Real Example from Automation
Imagine you have a script that checks the health of 100 servers every night. A traditional script often evolves into something resembling:
Get-Servers
Test-ServerConnectivity
Get-ServerPatchingStatus
Get-ServerBackupStatus
New-ServerReport
Send-Email
Every function depends on data created by previous functions.
When somebody asks:
“Can we also monitor certificate expiration?”
you end up modifying multiple parts of the script.
With classes:
class Server {
[string]$Name
[bool]$Online
[datetime]$LastBackup
[string]$PatchStatus
[bool] IsOnline() {
return Test-Connection $this.Name -Count 1 -Quiet
}
[string] GetPatchStatus() {
# logic here
}
[datetime] GetLastBackup() {
# logic here
}
}
Now all server-related functionality has a logical home. Adding certificate monitoring becomes an extension of the server object rather than a modification spread throughout the script.
When NOT to Use Classes
Let’s be honest. Most scripts don’t need them. I would not use classes for:
- One-off reports
- Quick administration tasks
- Data collection scripts
- Simple automation jobs
- Interactive shell work
Get-ADUser *
Is perfectly fine as is. The’re no classes required here. Or, for example,
Get-Service | Select-Object Name, Status
is already perfect for the usage intended.
Classes shine when you’re building something long-lived.
A Rule of Thumb
Ask yourself:
If I deleted all my functions, what “things” would remain?
Perhaps:
- Servers
- Users
- Applications
- Certificates
- Virtual machines
- Backups
- Database instances
- APIs
Those things are often excellent candidates for classes.
Common Objections
“PowerShell Isn’t C#”
Correct. And nobody is claiming it should be. The goal is not to turn PowerShell into an application development platform. The goal is to make complex automation easier to maintain.
“Classes Are Too Complicated”
Initially, yes. So were functions once. The learning curve is steeper than PSCustomObject, but the payoff becomes obvious once automation projects reach sufficient complexity.
“I’ve Managed Fine Without Them”
So have many experienced PowerShell professionals. But the question is not whether classes are necessary. The question is whether they reduce complexity in a given solution. Sometimes the answer is no, but sometimes the answer is absolutely yes.
Summary
Classes are not about making PowerShell look more like C#. They are about bringing structure to automation. As automation grows, complexity becomes the real enemy.
Classes provide:
- Encapsulation
- Strong typing
- Reusability
- Simplified maintenance
- Better separation of responsibilities
If your script is 50 lines long, classes are probably overkill. If your script is 2,000 lines long and has become the unofficial application nobody dares touch anymore, classes might be exactly what you need.
The best PowerShell developers are not the ones who use the most advanced features. They are the ones who choose the right level of complexity for the problem they’re solving. Sometimes that means a one-line pipeline. Sometimes that means a class. Knowing the difference is where experience comes in.
Next in This Series
Part 2 – Building Your First Real-World Class: Refactoring a traditional automation script into a class-based design.
Part 3 – Inheritance and Reuse: How to create base classes for servers, virtual machines and cloud resources.
Part 4 – Designing an Automation Framework: Creating reusable components for large-scale PowerShell solutions.
Part 5 – Unit Testing Classes with Pester: Making automation reliable and maintainable.
Part 6 – Class Anti-Patterns: When classes make your PowerShell worse instead of better.

Leave a Reply