PSObject is the foundational type in PowerShell that enables rich, dynamic manipulation of objects on the pipeline. Understanding how PSObject works helps you inspect properties, methods, and extended type data in a way that is consistent across cmdlets and providers.
Below is a compact reference that outlines how PSObject behaves in different scenarios, supported by a structured specification table and deeper examples.
| Scenario | PSObject Type | Base Object | Accessible Properties |
|---|---|---|---|
| Plain string in the pipeline | PSObject | String | Length, Chars, Text (NoteProperty) |
| Custom .NET object | PSObject | YourClass | Exported properties plus PS adapted members |
| Hashtable converted | PSObject | Hashtable | Keys mapped to note properties |
| Extended Type Data (ETW) | PSObject | Wrapped base object | Added properties/methods from type files |
Understanding PSObject as the Pipeline Wrapper
Every object that flows through the PowerShell pipeline is automatically wrapped in a PSObject instance. This wrapper adds a consistent layer of metadata, allowing you to inspect an object’s type name set, immediate properties, and available adapters regardless of whether you started with a string, integer, or complex .NET class.
Because PSObject preserves the original base object, you can still cast back to the source type when needed. The wrapper also enables extended type data, which means modules can attach new properties, methods, or type converters to existing types without modifying their original definition.
Property Access and Adaptation Mechanics
Accessing a property on a PSObject triggers the underlying adaptation layer, which first checks native properties, then falls back to extended type data if present. This ensures that cmdlets like Get-Member and Select-Object show a unified view that combines intrinsic members with any augmented definitions supplied by modules.
When you use dot notation or Select-Object to retrieve a subset of properties, PSObject handles the resolution and returns new PSObject wrappers for the resulting subset. This behavior is key when you need to shape output for downstream consumers or export to formats like CSV without losing type fidelity.
Working with BaseObject and ImmediateBaseObject
PSObject exposes properties such as BaseObject and ImmediateBaseObject, which let you reach the original unwrapped instance. This is useful when you need to pass the underlying object to APIs that do not accept PSObject or when you want to avoid unintended property expansions introduced by the wrapper.
By comparing BaseObject with the PSObject itself, you can verify whether additional note properties have been added via extended type data or by manually adding members using Add-Member. This pattern is common in advanced debugging and in scenarios where you must ensure that only the core data is being serialized.
Methods, TypeNames, and Member Resolution
PSObject also exposes Methods and TypeNames, which reveal executable members and the full type hierarchy used for resolution. TypeNames are especially important because they determine which Extended Type Data files PowerShell applies, allowing modules to customize formatting, filtering, and behavior for specific object types.
When you call a method on a PSObject, the wrapper locates the method on the base object or via adapted members. Inspecting PSObject.Methods is a practical way to discover what operations are available without relying on static documentation, especially for dynamic or COM objects.
Key Takeaways for Effective PowerShell Object Handling
- Always assume pipeline objects arrive as PSObject wrappers, even when the source type appears simple.
- Use BaseObject when you need the raw instance to avoid redundant adaptation overhead.
- Inspect TypeNames to understand which Extended Type Data applies to an object.
- Leverage Add-Member and custom PSObject properties to shape output without altering source data.
- Be aware that Select-Object and Format-Table produce new PSObject instances, which can affect property access patterns in advanced scripts.
FAQ
Reader questions
How does PSObject affect property enumeration in Format-Table and Export-Csv?
PSObject determines which properties are visible to formatting and export cmdlets, because they read the adapted property set. If you add note properties with Add-Member or rely on extended type data, those properties will appear in the output when you pipe to Format-Table or Export-Csv, assuming they are included in the default enumeration set.
Why does Select-Object sometimes produce nested PSObject wrappers?
Select-Object creates new PSObject wrappers for the selected subset of properties to preserve pipeline semantics. This means each row in the output is itself a PSObject, which can affect downstream scripts that rely on strict type checks or direct property access without unwrapping.
What happens when a module adds properties via Extended Type Data to a PSObject?
PowerShell merges Extended Type Data into the PSObject’s type name resolution, exposing additional properties and methods without changing the original base object. This enables rich customization for formatting, filtering, and scripting while keeping the source types untouched.
How can I unwrap a PSObject to work with the original instance in performance-sensitive code?
Use the BaseObject or ImmediateBaseObject properties to obtain the underlying object. For value types, casting directly to the expected type avoids overhead from double-wrapping and ensures that you work with the original data structure in tight loops or high-throughput pipelines.