PowerShell 5.0 introduced a game-changing feature that transformed the way administrators and developers write scripts: native class support. This addition brought true object-oriented programming (OOP) capabilities to PowerShell, enabling you to create reusable, maintainable, and professionally structured code.
This comprehensive guide explores PowerShell classes and custom types, providing practical examples and best practices for leveraging these powerful features in your automation workflows.
Understanding PowerShell Classes
PowerShell classes allow you to define custom object blueprints with properties, methods, constructors, and inheritance. Unlike PSCustomObject, classes provide compile-time type checking, inheritance support, and a more structured approach to code organization.
Basic Class Syntax
Creating a basic class in PowerShell follows a straightforward syntax pattern:
class Server {
# Properties
[string]$Name
[string]$IPAddress
[int]$Port
[bool]$IsOnline
# Constructor
Server([string]$name, [string]$ip) {
$this.Name = $name
$this.IPAddress = $ip
$this.Port = 443
$this.IsOnline = $false
}
# Method
[void]Connect() {
Write-Host "Connecting to $($this.Name) at $($this.IPAddress):$($this.Port)"
$this.IsOnline = $true
}
# Method with return value
[string]GetStatus() {
return "$($this.Name) is $(if($this.IsOnline){'Online'}else{'Offline'})"
}
}
# Create instance
$webServer = [Server]::new("WebServer01", "192.168.1.100")
$webServer.Connect()
Write-Host $webServer.GetStatus()
Output:
Connecting to WebServer01 at 192.168.1.100:443
WebServer01 is Online
Properties and Property Validation
PowerShell classes support various property types and validation attributes to ensure data integrity.
Property Types and Attributes
class User {
# Strongly typed properties
[string]$Username
# Validation attributes
[ValidateRange(18, 120)]
[int]$Age
[ValidateSet("Admin", "User", "Guest")]
[string]$Role
[ValidatePattern("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")]
[string]$Email
# Hidden property (not displayed by default)
hidden [string]$PasswordHash
# Static property (shared across all instances)
static [int]$TotalUsers = 0
# Constructor
User([string]$username, [int]$age, [string]$email) {
$this.Username = $username
$this.Age = $age
$this.Email = $email
$this.Role = "User"
[User]::TotalUsers++
}
}
# Create users
$user1 = [User]::new("john_doe", 30, "[email protected]")
$user2 = [User]::new("jane_smith", 25, "[email protected]")
Write-Host "Total Users Created: $([User]::TotalUsers)"
Write-Host "User1 Role: $($user1.Role)"
Output:
Total Users Created: 2
User1 Role: User
Constructors and Overloading
PowerShell classes support multiple constructors with different parameter signatures, enabling flexible object instantiation.
class DatabaseConnection {
[string]$Server
[string]$Database
[int]$Port
[string]$ConnectionString
# Default constructor
DatabaseConnection() {
$this.Server = "localhost"
$this.Database = "master"
$this.Port = 1433
$this.BuildConnectionString()
}
# Constructor with server and database
DatabaseConnection([string]$server, [string]$database) {
$this.Server = $server
$this.Database = $database
$this.Port = 1433
$this.BuildConnectionString()
}
# Constructor with all parameters
DatabaseConnection([string]$server, [string]$database, [int]$port) {
$this.Server = $server
$this.Database = $database
$this.Port = $port
$this.BuildConnectionString()
}
hidden [void]BuildConnectionString() {
$this.ConnectionString = "Server=$($this.Server),$($this.Port);Database=$($this.Database)"
}
[string]ToString() {
return $this.ConnectionString
}
}
# Different ways to instantiate
$conn1 = [DatabaseConnection]::new()
$conn2 = [DatabaseConnection]::new("sql-server", "ProductionDB")
$conn3 = [DatabaseConnection]::new("sql-server", "ProductionDB", 1435)
Write-Host "Connection 1: $conn1"
Write-Host "Connection 2: $conn2"
Write-Host "Connection 3: $conn3"
Output:
Connection 1: Server=localhost,1433;Database=master
Connection 2: Server=sql-server,1433;Database=ProductionDB
Connection 3: Server=sql-server,1435;Database=ProductionDB
Methods and Method Types
PowerShell classes support instance methods, static methods, and method overloading for versatile functionality.
class Calculator {
[double]$LastResult
static [string]$Version = "1.0"
# Instance method
[double]Add([double]$a, [double]$b) {
$this.LastResult = $a + $b
return $this.LastResult
}
# Method overloading - same name, different parameters
[double]Add([double[]]$numbers) {
$this.LastResult = ($numbers | Measure-Object -Sum).Sum
return $this.LastResult
}
# Static method (no access to instance properties)
static [double]Multiply([double]$a, [double]$b) {
return $a * $b
}
# Method with complex logic
[hashtable]Calculate([string]$operation, [double]$a, [double]$b) {
$result = switch ($operation) {
"add" { $a + $b }
"subtract" { $a - $b }
"multiply" { $a * $b }
"divide" {
if ($b -eq 0) { throw "Division by zero" }
$a / $b
}
default { throw "Unknown operation: $operation" }
}
$this.LastResult = $result
return @{
Operation = $operation
Result = $result
Timestamp = Get-Date
}
}
}
$calc = [Calculator]::new()
# Instance methods
Write-Host "5 + 3 = $($calc.Add(5, 3))"
Write-Host "Sum of array: $($calc.Add(@(10, 20, 30, 40)))"
# Static method (no instance needed)
Write-Host "Static multiply: $([Calculator]::Multiply(7, 6))"
# Complex method
$result = $calc.Calculate("divide", 100, 4)
Write-Host "Division result: $($result.Result) at $($result.Timestamp)"
Output:
5 + 3 = 8
Sum of array: 100
Static multiply: 42
Division result: 25 at 10/22/2025 16:52:00
Inheritance and Polymorphism
PowerShell classes support single inheritance, allowing you to build hierarchical class structures and implement polymorphic behavior.
class Animal {
[string]$Name
[int]$Age
[string]$Species
Animal([string]$name, [int]$age) {
$this.Name = $name
$this.Age = $age
}
# Virtual method (can be overridden)
[string]MakeSound() {
return "Some generic animal sound"
}
[string]GetInfo() {
return "$($this.Name) is a $($this.Age) year old $($this.Species)"
}
}
class Dog : Animal {
[string]$Breed
Dog([string]$name, [int]$age, [string]$breed) : base($name, $age) {
$this.Species = "Dog"
$this.Breed = $breed
}
# Override parent method
[string]MakeSound() {
return "Woof! Woof!"
}
[void]Fetch() {
Write-Host "$($this.Name) is fetching the ball!"
}
}
class Cat : Animal {
[bool]$IsIndoor
Cat([string]$name, [int]$age, [bool]$isIndoor) : base($name, $age) {
$this.Species = "Cat"
$this.IsIndoor = $isIndoor
}
# Override parent method
[string]MakeSound() {
return "Meow!"
}
[void]Scratch() {
$location = if ($this.IsIndoor) { "furniture" } else { "tree" }
Write-Host "$($this.Name) is scratching the $location"
}
}
# Polymorphism in action
$animals = @(
[Dog]::new("Rex", 5, "German Shepherd"),
[Cat]::new("Whiskers", 3, $true),
[Dog]::new("Buddy", 2, "Golden Retriever")
)
foreach ($animal in $animals) {
Write-Host $animal.GetInfo()
Write-Host "Sound: $($animal.MakeSound())"
Write-Host "---"
}
# Type-specific methods
$dog = $animals[0]
$dog.Fetch()
Output:
Rex is a 5 year old Dog
Sound: Woof! Woof!
---
Whiskers is a 3 year old Cat
Sound: Meow!
---
Buddy is a 2 year old Golden Retriever
Sound: Woof! Woof!
---
Rex is fetching the ball!
Implementing Interfaces with PowerShell Classes
While PowerShell doesn’t have native interface support like C#, you can implement interface-like patterns using base classes and method contracts.
class ILogger {
[void]LogInfo([string]$message) {
throw "Method must be implemented by derived class"
}
[void]LogError([string]$message) {
throw "Method must be implemented by derived class"
}
}
class FileLogger : ILogger {
[string]$LogPath
FileLogger([string]$logPath) {
$this.LogPath = $logPath
}
[void]LogInfo([string]$message) {
$entry = "[INFO] $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $message"
Add-Content -Path $this.LogPath -Value $entry
Write-Host $entry -ForegroundColor Green
}
[void]LogError([string]$message) {
$entry = "[ERROR] $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $message"
Add-Content -Path $this.LogPath -Value $entry
Write-Host $entry -ForegroundColor Red
}
}
class ConsoleLogger : ILogger {
[void]LogInfo([string]$message) {
Write-Host "[INFO] $message" -ForegroundColor Cyan
}
[void]LogError([string]$message) {
Write-Host "[ERROR] $message" -ForegroundColor Red
}
}
# Usage with polymorphism
function Process-WithLogging([ILogger]$logger) {
$logger.LogInfo("Process started")
try {
# Simulate work
Start-Sleep -Milliseconds 100
$logger.LogInfo("Task completed successfully")
}
catch {
$logger.LogError("Task failed: $_")
}
}
$fileLogger = [FileLogger]::new("C:\temp\app.log")
$consoleLogger = [ConsoleLogger]::new()
Process-WithLogging -logger $fileLogger
Process-WithLogging -logger $consoleLogger
Real-World Example: Building a Configuration Manager
This comprehensive example demonstrates how to build a practical configuration management system using PowerShell classes.
class ConfigItem {
[string]$Key
[object]$Value
[string]$Type
[bool]$IsRequired
[string]$Description
ConfigItem([string]$key, [object]$value, [string]$type, [bool]$isRequired) {
$this.Key = $key
$this.Value = $value
$this.Type = $type
$this.IsRequired = $isRequired
}
[string]ToString() {
return "$($this.Key) = $($this.Value) [$($this.Type)]"
}
}
class ConfigValidator {
static [bool]ValidateType([ConfigItem]$item) {
switch ($item.Type) {
"string" { return $item.Value -is [string] }
"int" { return $item.Value -is [int] }
"bool" { return $item.Value -is [bool] }
"array" { return $item.Value -is [array] }
default { return $true }
}
}
static [string[]]ValidateAll([ConfigItem[]]$items) {
$errors = @()
foreach ($item in $items) {
if ($item.IsRequired -and [string]::IsNullOrEmpty($item.Value)) {
$errors += "Required configuration '$($item.Key)' is missing"
}
if (-not [ConfigValidator]::ValidateType($item)) {
$errors += "Configuration '$($item.Key)' has invalid type. Expected: $($item.Type)"
}
}
return $errors
}
}
class ConfigManager {
hidden [hashtable]$Configurations
[string]$ConfigPath
ConfigManager([string]$configPath) {
$this.ConfigPath = $configPath
$this.Configurations = @{}
$this.InitializeDefaults()
}
hidden [void]InitializeDefaults() {
$this.AddConfig([ConfigItem]::new("ServerName", "localhost", "string", $true))
$this.AddConfig([ConfigItem]::new("Port", 8080, "int", $true))
$this.AddConfig([ConfigItem]::new("EnableLogging", $true, "bool", $false))
$this.AddConfig([ConfigItem]::new("MaxConnections", 100, "int", $false))
}
[void]AddConfig([ConfigItem]$item) {
$this.Configurations[$item.Key] = $item
}
[object]GetValue([string]$key) {
if ($this.Configurations.ContainsKey($key)) {
return $this.Configurations[$key].Value
}
throw "Configuration key '$key' not found"
}
[void]SetValue([string]$key, [object]$value) {
if ($this.Configurations.ContainsKey($key)) {
$this.Configurations[$key].Value = $value
} else {
throw "Configuration key '$key' not found"
}
}
[bool]Validate() {
$items = $this.Configurations.Values
$errors = [ConfigValidator]::ValidateAll($items)
if ($errors.Count -gt 0) {
Write-Host "Configuration validation failed:" -ForegroundColor Red
$errors | ForEach-Object { Write-Host " - $_" -ForegroundColor Red }
return $false
}
Write-Host "Configuration validation passed" -ForegroundColor Green
return $true
}
[void]DisplayConfig() {
Write-Host "`nCurrent Configuration:" -ForegroundColor Cyan
Write-Host ("=" * 50)
foreach ($key in $this.Configurations.Keys | Sort-Object) {
$item = $this.Configurations[$key]
$required = if ($item.IsRequired) { "[Required]" } else { "[Optional]" }
Write-Host "$($item.Key): $($item.Value) $required"
}
Write-Host ("=" * 50)
}
[hashtable]ExportConfig() {
$export = @{}
foreach ($key in $this.Configurations.Keys) {
$export[$key] = $this.Configurations[$key].Value
}
return $export
}
}
# Example usage
$configMgr = [ConfigManager]::new("C:\config\app.json")
Write-Host "Initial Configuration:"
$configMgr.DisplayConfig()
# Modify configuration
Write-Host "`nModifying configuration..."
$configMgr.SetValue("Port", 9090)
$configMgr.SetValue("MaxConnections", 200)
Write-Host "`nUpdated Configuration:"
$configMgr.DisplayConfig()
# Validate
Write-Host ""
$isValid = $configMgr.Validate()
# Export
if ($isValid) {
$exported = $configMgr.ExportConfig()
Write-Host "`nExported Configuration:"
$exported.GetEnumerator() | Sort-Object Name | ForEach-Object {
Write-Host " $($_.Key): $($_.Value)"
}
}
Advanced Patterns: Factory and Singleton
PowerShell classes support advanced design patterns for building scalable applications.
Factory Pattern
class DatabaseConnection {
[string]$ConnectionString
[string]$Type
DatabaseConnection([string]$connectionString, [string]$type) {
$this.ConnectionString = $connectionString
$this.Type = $type
}
[void]Connect() {
Write-Host "Connecting to $($this.Type) database..."
}
}
class SqlServerConnection : DatabaseConnection {
SqlServerConnection([string]$server, [string]$database) : base(
"Server=$server;Database=$database;Integrated Security=true",
"SQL Server"
) {}
[void]Connect() {
Write-Host "Opening SQL Server connection: $($this.ConnectionString)"
}
}
class MySqlConnection : DatabaseConnection {
MySqlConnection([string]$server, [string]$database) : base(
"Server=$server;Database=$database;Uid=root;Pwd=password",
"MySQL"
) {}
[void]Connect() {
Write-Host "Opening MySQL connection: $($this.ConnectionString)"
}
}
class DatabaseFactory {
static [DatabaseConnection]CreateConnection([string]$type, [string]$server, [string]$database) {
switch ($type.ToLower()) {
"sqlserver" { return [SqlServerConnection]::new($server, $database) }
"mysql" { return [MySqlConnection]::new($server, $database) }
default { throw "Unknown database type: $type" }
}
}
}
# Usage
$sqlConn = [DatabaseFactory]::CreateConnection("sqlserver", "localhost", "TestDB")
$sqlConn.Connect()
$mysqlConn = [DatabaseFactory]::CreateConnection("mysql", "192.168.1.50", "AppDB")
$mysqlConn.Connect()
Singleton Pattern
class Logger {
static hidden [Logger]$Instance
hidden [System.Collections.ArrayList]$Logs
# Private constructor
hidden Logger() {
$this.Logs = [System.Collections.ArrayList]::new()
}
static [Logger]GetInstance() {
if ($null -eq [Logger]::Instance) {
[Logger]::Instance = [Logger]::new()
}
return [Logger]::Instance
}
[void]Log([string]$message) {
$entry = "[$(Get-Date -Format 'HH:mm:ss')] $message"
$this.Logs.Add($entry) | Out-Null
Write-Host $entry
}
[string[]]GetLogs() {
return $this.Logs.ToArray()
}
[int]GetLogCount() {
return $this.Logs.Count
}
}
# Usage - same instance everywhere
$logger1 = [Logger]::GetInstance()
$logger1.Log("First message")
$logger2 = [Logger]::GetInstance()
$logger2.Log("Second message")
# Both references point to same instance
Write-Host "`nTotal logs: $($logger2.GetLogCount())"
Write-Host "All logs:"
$logger2.GetLogs() | ForEach-Object { Write-Host " $_" }
Best Practices and Performance Considerations
Type Safety and Validation
Always use strongly typed properties and validation attributes to prevent runtime errors:
class BestPracticeExample {
# Good: Strongly typed with validation
[ValidateNotNullOrEmpty()]
[string]$Name
[ValidateRange(1, 100)]
[int]$Priority
# Good: Use enums for limited options
[ValidateSet("Low", "Medium", "High")]
[string]$Severity
# Bad: Avoid untyped properties
# $GenericProperty
}
Memory Management
class ResourceManager {
[System.IO.FileStream]$FileHandle
ResourceManager([string]$path) {
$this.FileHandle = [System.IO.File]::OpenRead($path)
}
# Implement cleanup
[void]Dispose() {
if ($null -ne $this.FileHandle) {
$this.FileHandle.Dispose()
$this.FileHandle = $null
}
}
}
# Usage with proper cleanup
$resource = [ResourceManager]::new("C:\data.txt")
try {
# Use resource
}
finally {
$resource.Dispose()
}
Performance Tips
- Use static methods for utility functions that don’t need instance state
- Mark internal helper methods as hidden to reduce memory overhead
- Prefer strongly typed collections over generic arrays
- Implement ToString() for better debugging experience
- Cache expensive computations in properties rather than recalculating
Debugging and Testing Classes
class TestableCalculator {
[double]$LastResult
hidden [System.Collections.ArrayList]$History
TestableCalculator() {
$this.History = [System.Collections.ArrayList]::new()
}
[double]Add([double]$a, [double]$b) {
$result = $a + $b
$this.RecordOperation("Add", $a, $b, $result)
return $result
}
hidden [void]RecordOperation([string]$op, [double]$a, [double]$b, [double]$result) {
$this.History.Add(@{
Operation = $op
Operand1 = $a
Operand2 = $b
Result = $result
Timestamp = Get-Date
}) | Out-Null
$this.LastResult = $result
}
[object[]]GetHistory() {
return $this.History.ToArray()
}
[void]ClearHistory() {
$this.History.Clear()
}
}
# Testing
$calc = [TestableCalculator]::new()
$calc.Add(10, 5)
$calc.Add(20, 15)
Write-Host "Operation History:"
$calc.GetHistory() | ForEach-Object {
Write-Host "$($_.Operation): $($_.Operand1) + $($_.Operand2) = $($_.Result)"
}
Common Pitfalls and Solutions
Avoiding Circular References
# Problem: Circular reference
class Parent {
[Child]$Child
}
class Child {
[Parent]$Parent # This creates circular reference
}
# Solution: Use weak references or redesign
class Parent {
[string]$ChildId # Store reference ID instead
}
class Child {
[string]$Id
[string]$ParentId
}
Proper Error Handling
class RobustService {
[void]ProcessData([string]$data) {
try {
if ([string]::IsNullOrEmpty($data)) {
throw [System.ArgumentException]::new("Data cannot be null or empty")
}
# Process data
Write-Host "Processing: $data"
}
catch [System.ArgumentException] {
Write-Error "Invalid argument: $($_.Exception.Message)"
throw
}
catch {
Write-Error "Unexpected error: $($_.Exception.Message)"
throw
}
}
}
Conclusion
PowerShell classes and custom types provide powerful object-oriented programming capabilities that transform script development. By leveraging classes, you can create reusable, maintainable code that follows industry best practices. Whether you’re building configuration managers, implementing design patterns, or creating domain-specific abstractions, PowerShell classes offer the structure and type safety needed for professional automation solutions.
Start incorporating classes into your PowerShell scripts today to improve code organization, enable better testing, and build more scalable automation frameworks. The examples and patterns demonstrated in this guide provide a solid foundation for mastering object-oriented PowerShell development.








