The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Create an ordered record | $Item = [pscustomobject][ordered]@{ Name = 'api'; State `
= 'Running' } | View examples |
| Add a semantic type name | $Item.PSObject.TypeNames.Insert(0, `
'Contoso.ServiceStatus') | View examples |
| Declare a class | class ServiceSpec { [string] $Name; [bool] $Enabled } | View examples |
| Invoke a constructor | $Spec = [ServiceSpec]::new('api', $true) | View examples |
| Declare an enum | enum HealthState { Unknown; Healthy; Degraded; Failed } | View examples |
| Validate a property value | [ValidateSet('Dev','Test','Prod')] [string] $Environment | View examples |
| Hide an implementation member | hidden [string] $CacheKey | View examples |
| Call a static method | [Widget]::NormalizeName(' API ') | View examples |
| Add a calculated property | $Item |
Add-Member -NotePropertyName Healthy -NotePropertyValue $true | View examples |
| Project calculated output | $Item |
Select-Object Name,@{n='Label';e={"$($_.Name):$($_.State)"}} | View examples |
| Accept typed pipeline input | param([Parameter(ValueFromPipeline)][ServiceSpec] `
$InputObject) | View examples |
| Document output type | [OutputType([ServiceSpec])] param() | View examples |
| Serialize with explicit depth | $Item | ConvertTo-Json -Depth 5 | View examples |
| Persist PowerShell data | $Item | Export-Clixml -LiteralPath '.\item.clixml' | View examples |
| Load module types at parse time | using module Contoso.Models | View examples |
| Set default display properties | Update-TypeData -TypeName Contoso.ServiceStatus `
-DefaultDisplayPropertySet Name,State -Force | View examples |
PowerShell objects range from lightweight pipeline records to formal user-defined types. PSCustomObject is usually best for shaped output and data transfer; classes add constructors, methods, inheritance, and stronger property contracts when behavior and identity matter. Understand parse-time type loading, pipeline binding, method side effects, and serialization before using classes across modules, jobs, remoting, or persistent formats.
Step by step
Detailed examples
Choose records for data and classes for behavior
Use PSCustomObject for pipeline records, reports, API payloads, and short-lived structured data. Use a class when callers benefit from constructors, methods, inheritance, or an explicit runtime type. PowerShell classes require version 5.0 or newer; these in-memory examples need no Windows role, domain, elevation, remoting, reboot, or WhatIf. Neither choice creates a security boundary. Ordered hashtables make initial property display deterministic, while a custom PSTypeName lets format and type data target records without requiring a class. Prefer small output objects over returning live administrative objects whose methods can mutate systems.
$Status = [pscustomobject][ordered]@{
Name = 'api'
State = 'Running'
CheckedAt = [datetimeoffset]::UtcNow
}
$Status.PSObject.TypeNames.Insert(0, 'Contoso.ServiceStatus')
$Status Define constructors with explicit invariants
PowerShell classes arrived in version 5.0. Properties default to the type's default value, and constructors do not automatically chain arbitrary overloads. Validate required values and establish all invariants in every public constructor. Static New methods can provide readable factories. Class definitions affect the current session and generally cannot be redefined without starting a new session, which matters during iterative module development.
class ServiceSpec {
[string] $Name
[bool] $Enabled
ServiceSpec([string] $name, [bool] $enabled) {
if ([string]::IsNullOrWhiteSpace($name)) { throw 'Name is required' }
$this.Name = $name.Trim()
$this.Enabled = $enabled
}
}
$Spec = [ServiceSpec]::new('api', $true) Use type conversion and validation knowingly
Typed properties convert compatible assigned values and reject incompatible ones, but conversion may be surprising: strings, numbers, collections, and enums follow PowerShell conversion rules. ValidateSet and other validation attributes can strengthen assignments. Enums improve readability but serialize as names or numbers depending on the serializer and target contract. Avoid using a type annotation as proof that untrusted input is safe; validate semantics, ranges, paths, and authorization separately.
enum HealthState { Unknown; Healthy; Degraded; Failed }
class ProbeResult {
[ValidateNotNullOrEmpty()] [string] $Name
[HealthState] $State = [HealthState]::Unknown
[ValidateRange(0, 60000)] [int] $LatencyMs
}
$Result = [ProbeResult]::new()
$Result.Name = 'api'
$Result.State = 'Healthy' Keep methods predictable and hidden members non-secret
Instance methods receive $this; static members belong to the type. Explicit return types convert returned values and require all paths to satisfy the contract. Hidden only suppresses normal Get-Member, completion, and display; reflection and direct access can still reveal the member, so never store secrets there expecting confidentiality. Keep methods free of unexpected network or administrative side effects, and name mutating behavior clearly.
class Widget {
[string] $Name
hidden [string] $CacheKey
static [string] NormalizeName([string] $value) { return $value.Trim().ToLowerInvariant() }
[string] ToString() { return $this.Name }
}
$Normalized = [Widget]::NormalizeName(' API ') Project output instead of mutating shared input
Add-Member extends an individual PSObject wrapper and is useful when enrichment is intentional. Select-Object calculated properties create a new projected record, which is usually safer inside reusable functions because the caller's input remains untouched. Script properties and script methods execute code when accessed and can surprise consumers or serializers; reserve them for controlled objects. Use property names that remain stable for automation, then configure presentation separately.
$Service = Get-Service -Name 'EventLog'
$View = $Service | Select-Object Name, Status, @{
Name = 'IsRunning'
Expression = { $_.Status -eq [ServiceProcess.ServiceControllerStatus]::Running }
}
$View Design class-aware pipeline contracts
ValueFromPipeline binds whole objects; ValueFromPipelineByPropertyName binds compatible properties. A class-typed parameter rejects deserialized or shape-compatible objects that are not actual instances, so public remoting commands often work better with primitive parameters or validated PSCustomObject input. OutputType is documentation and tooling metadata only—it does not restrict emitted objects. Emit objects one at a time and avoid Format-Table inside functions because formatting records are not reusable data.
function Test-ServiceSpec {
[CmdletBinding()]
[OutputType([pscustomobject])]
param([Parameter(Mandatory, ValueFromPipeline)][ServiceSpec] $InputObject)
process {
[pscustomobject]@{ Name = $InputObject.Name; Valid = $InputObject.Name.Length -le 256 }
}
} Assume serialization preserves data, not behavior
JSON has no PowerShell class identity, methods, SecureString semantics, or rich type fidelity. ConvertTo-Json truncates beyond its Depth and may represent enums or dates differently than an API expects. CLIXML preserves more PowerShell metadata, but remoting and jobs commonly return deserialized objects whose type names begin with Deserialized and whose methods are gone. Never deserialize untrusted formats that can instantiate arbitrary types; JSON is preferable for cross-system data contracts, with explicit schema validation after import.
$Payload = [pscustomobject]@{ Name = 'api'; Enabled = $true; Version = 2 }
$Json = $Payload | ConvertTo-Json -Depth 3 -Compress
$Copy = $Json | ConvertFrom-Json
$Copy | Select-Object Name, Enabled, Version Package classes with parse-time loading in mind
A class in a script module must be available while callers are parsed. using module is a parse-time statement and is the supported way to consume exported PowerShell classes; Import-Module alone may not make type literals usable during parsing. Changing a loaded class generally requires a fresh PowerShell process. Avoid exposing internal implementation classes unnecessarily because public types become compatibility commitments. Use type and format data for display rather than overriding ToString for every presentation need.
using module Contoso.Models
Update-TypeData -TypeName 'Contoso.ServiceStatus' `
-DefaultDisplayPropertySet Name, State, CheckedAt -Force
Get-TypeData -TypeName 'Contoso.ServiceStatus' Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
- Microsoft Learnabout Classeslearn.microsoft.com
- Microsoft Learnabout Class Constructorslearn.microsoft.com
- Microsoft Learnabout Class Methodslearn.microsoft.com
- Microsoft LearnEverything you wanted to know about PSCustomObjectlearn.microsoft.com
- Microsoft LearnAdd-Memberlearn.microsoft.com
- Microsoft Learnabout Type Acceleratorslearn.microsoft.com
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



