Readable Objects with Update-TypeData in PowerShell: Teach Your Types to Speak
If you want cleaner consoles, clearer logs, and command output that explains itself, design your PowerShell objects to be readable by default. With Update-TypeData, you can set a DefaultDisplayPropertySet and add small ScriptMethods that make your objects "speak" without extra formatting. The data stays rich under the hood; the noise disappears from your terminals and pipelines.
In this post, you will learn how to use Update-TypeData to make objects self-explanatory, package these improvements into modules, and apply them safely in team and CI/CD environments.
Why teach your objects to speak
- Less formatting boilerplate: stop piping to Format-Table for every command.
- Clearer logs and reviews: defaults highlight intent and key fields.
- Reusable types: once defined, every command returning the type benefits.
- DevOps-friendly: consistent output in pipelines reduces flakiness in parsing and improves signal-to-noise in build logs.
The core pattern: Update-TypeData
Below is a minimal end-to-end example that introduces a custom PSTypeName, a default display set, and a small ScriptMethod for quick summaries.
$items = 1..3 | ForEach-Object {
[pscustomobject]@{
PSTypeName = 'Demo.Record'
Name = ('item{0}' -f $_)
Value = $_ * 10
Created = Get-Date
}
}
Update-TypeData -TypeName 'Demo.Record' -DefaultDisplayPropertySet 'Name','Value' -Force
Update-TypeData -TypeName 'Demo.Record' -MemberType ScriptMethod -MemberName 'Describe' -Value {
'{0}={1}' -f $this.Name, $this.Value
} -Force
$items
$items[0].Describe()What happens here:
- PSTypeName marks the object as Demo.Record to the Extended Type System (ETS).
- DefaultDisplayPropertySet reduces the default console view to Name and Value. The object still carries Created; it just stops cluttering the default view.
- ScriptMethod Describe() gives you a quick, intentional summary for logs and debugging.
DefaultDisplayPropertySet: the quiet power move
Without changing your commands, the default output becomes concise. You can still request more when you need it:
$items # shows Name,Value by default
$items | Format-List * # shows everything
$items | Select-Object Name,Value,Created # be explicit where neededAdd tiny ScriptMethods and ScriptProperties
Use ScriptMethods for short, deterministic helpers that explain the object. You can also add lightweight properties for frequent calculations.
Update-TypeData -TypeName 'Demo.Record' -MemberType ScriptProperty -MemberName 'AgeMinutes' -Value {
[math]::Round(((Get-Date) - $this.Created).TotalMinutes,2)
} -Force
$items[0].AgeMinutes
$items[0].Describe()Guidelines:
- Keep them fast and side-effect free.
- Prefer pure calculations based on existing properties.
- Avoid network I/O, filesystem probes, or secrets access in anything that might run during display.
Package for reuse: modules, types.ps1xml, and CI
To make readable types available everywhere, put your type data in a module. You can apply Update-TypeData in the module's .psm1 on import, or ship a types.ps1xml file and reference it in the module manifest.
Option A: Apply type data in your module's .psm1
# MyModule.psm1
$tdParams = @{ TypeName = 'Demo.Record'; Force = $true }
Update-TypeData @tdParams -DefaultDisplayPropertySet 'Name','Value'
Update-TypeData @tdParams -MemberType ScriptMethod -MemberName 'Describe' -Value {
'{0}={1}' -f $this.Name, $this.Value
}
Update-TypeData @tdParams -MemberType ScriptProperty -MemberName 'AgeMinutes' -Value {
[math]::Round(((Get-Date) - $this.Created).TotalMinutes,2)
}Option B: Use types.ps1xml (declarative, great for distribution)
A types.ps1xml file travels well, is easily reviewed, and avoids runtime code in type definitions.
<Types>
<Type>
<Name>Demo.Record</Name>
<Members>
<ScriptMethod>
<Name>Describe</Name>
<Script>'{0}={1}' -f $this.Name, $this.Value</Script>
</ScriptMethod>
<ScriptProperty>
<Name>AgeMinutes</Name>
<GetScriptBlock>[math]::Round(((Get-Date) - $this.Created).TotalMinutes,2)</GetScriptBlock>
</ScriptProperty>
</Members>
<DefaultDisplayPropertySet>
<ReferenceName>Name</ReferenceName>
<ReferenceName>Value</ReferenceName>
</DefaultDisplayPropertySet>
</Type>
</Types>Reference this file in your module manifest (.psd1):
@{
RootModule = 'MyModule.psm1'
ModuleVersion = '1.0.0'
TypesToProcess = @('types.ps1xml')
# Optionally, add custom format views via FormatsToProcess
}DevOps pipelines and logs: keep it readable and structured
Readable objects shine in CI/CD, scheduled jobs, and containerized automation:
- Short defaults keep console logs compact and tail-friendly.
- Objects remain fully structured for durable logging (e.g., JSON).
- The same objects are readable in both local and remote sessions.
Example: concise console, rich structured log.
$items | ForEach-Object {
# Console-friendly single line
Write-Host $_.Describe()
# Machine-friendly structured record (script methods are not serialized)
$_ | Select-Object Name,Value,Created | ConvertTo-Json -Depth 3
}Notes:
- DefaultDisplayPropertySet affects console formatting, not serialization.
- ScriptMethods and ScriptProperties are not included by ConvertTo-Json unless you explicitly Select-Object them.
- For durable logs, select explicit properties so log schema stays stable.
Practical tips, performance, and safety
Do
- Keep default display sets small (2–5 properties).
- Favor value-like 'Demo.Record'
Remove-TypeData -TypeName 'Demo.Record' -ErrorAction SilentlyContinue
- When multiple definitions exist, later updates win unless constrained by scope. Use -Force to overwrite in the current session.
Advanced: teach ToString() carefully
You can add a custom ToString for single-column displays or quick stringification. Use sparingly and keep it stable.
Update-TypeData -TypeName 'Demo.Record' -MemberType ScriptMethod -MemberName 'ToString' -Value { '{0} (Value={1}, Age={2}m)' -f $this.Name, $this.Value, [math]::Round(((Get-Date) - $this.Created).TotalMinutes,0) } -Force # Now, string contexts show a concise summary 'this is ' + $items[0]Considerations:
- Do not put secrets or environment data in ToString. Strings may end up in logs unexpectedly.
- Keep it fast and deterministic.
Real-world use cases
- Infrastructure automation: resources returned by provisioning scripts (clusters, queues, web apps) show Name and Status by default, while retaining IDs and metadata for export.
- Release pipelines: deployment result objects display Target, Version, and Outcome in a readable row; logs capture full details in JSON artifacts.
- Observability: health check results show Check, State, and Duration; teams use Describe() for compact console banners, while structured sinks receive the full object.
Bringing it all together
- Give your objects a PSTypeName.
- Define a DefaultDisplayPropertySet to reduce noise.
- Add tiny ScriptMethods/ScriptProperties for intent-revealing helpers.
- Package type data in a module (types.ps1xml or Update-TypeData in .psm1).
- Use explicit property selection for machine logs; let defaults serve humans.
- Test in CI and watch for performance and safety pitfalls.
Sharpen your object design in PowerShell and make every command tell a clear story. Explore the PowerShell Advanced CookBook here: PowerShell Advanced CookBook.