PowerShell: PSObject, custom types and the add-member cmdlet
PowerShell is .NET-based in the sense that it allows reasonably free manipulation of CLR objects. However, the shell language differs drastically from the familiar object-oriented languages that are typically used with classes and objects. In this post, I’ll discuss some of the aspects involved in handling custom objects in PowerShell.
Note: I have posted an update to this article regarding PowerShell v2, which cleans up the syntax described here. The theory still applies.
What is PSObject and why should I care?
PowerShell is a shell, and a shell is a demanding environment. Shells should be scriptable by reasonably casual IT administrators without in-depth knowledge of object oriented programming or somesuch. PowerShell’s type abstraction system aims at simplifying the syntax required for manipulation of complex object graphs.
For example, let’s say you were about to read an XML document and parse a single value from it using C#.
XmlDocument d = new XmlDocument(); d.LoadXml("<parent><subnode>value</subnode></parent>");
Constructing the document is pretty straightforward, but reading the node takes a bit more effort.
Console.WriteLine( d.ChildNodes.Cast<XmlNode>().First(n => n.LocalName == "parent") .ChildNodes.Cast<XmlNode>().First(n => n.LocalName == "subnode") .InnerText );
Of course you can cut corners if you know XPath, but purely on object level, this is pretty much what you’re facing. No admin would write scripts if it were this complex to handle arbitrary XML. To make the most basic tasks easier, PowerShell objects include some shortcut functionality:
$d = [xml]"<parent><subnode>value</subnode></parent>"
$d.parent.subnode
The variable $d contains a normal CLR object, an XmlDocument to be exact (take a look at $d.GetType() if you want to check). However, technically $d is actually an XmlDocument wrapped into a PSObject. PSObject class represents a generic PowerShell object and supports features such as dynamic properties.
In this case, $d would seem to have a property called “parent”, even though XmlDocument doesn’t define such a thing. But when PowerShell gets its hand on a Xml object, it automatically provides aliases to access the child elements as properties. While this would be rather unwieldy in a programming language, it suits a scripting language quite fine.
So yes, you should care about PSObjects: They’re all about making your life easier. If you want more theory, take a look at the MSDN documentation.
Classes or objects?
In C# and VB, an object’s properties are defined through their classes. In more dynamic languages such as JavaScript or PowerShell, features can be added to objects directly, meaning that two objects of the same class could have wildly different properties and methods available.
To prove the point, we’ll use the add-member cmdlet to add a custom field to a normal object. We’ll get back to add-member’s usage later on.
PS C:\> dir c:\autoexec.bat |
>> add-member -passthru NoteProperty "Foo" 123 |
>> format-table -a Name, Length, Foo
>>
Name Length Foo
---- ------ ---
autoexec.bat 24 123
The addition thus made is very simple and only affects the object we just created in the pipeline. Any future objects produced by dir wouldn’t be affected.
While customizing things on an object level is certainly handy, you don’t usually want to make objects of the same class differ from each other. But the separation of class vs. object allows you to extend objects whose classes you cannot access, thus providing lots of possibilities. As you might guess, you can add not only plain text members, but also methods and formulas that can be used to derive values from the object.
Custom types – why?
Creating your own types in PowerShell isn’t really an everyday task. If you create commandlets to manage an application, you might write new classes to represent the data within the application’s domain, but if you’re just using PowerShell, you won’t be likely to even need a single type-defining construct, ever.
However, there is a scenario where custom types make sense: returning complex information. And it is this situation where the loose coupling between classes and objects plays to your advantage, as you can just create objects without a real class, just adding the necessary properties to them. So in classic .NET terms, you’re creating a bunch of System.Objects and then tagging the required features onto them.
Now, suppose we want to create a PowerShell script that iterates through the applications set to start immediately after Windows boots. We can simply use the following:
(Get-Item 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Run').Property
However, it will only get us the list of friendly names for the software to run. The registry also contains the executable paths to be run, and we want them returned as well. Were you writing this in C#, you’d probably start with something like “public class StartupApplicationInfo” and then roll in properties for application name and path, finally returning a List<T> of such objects.
In PowerShell you essentially want to do the same, but you don’t need to specify things all that much. Let’s look at using add-member now, and then dig into this a bit more.
Using add-member
Getting the executable paths for the software being run at startup is easy, so we’ll focus on the object construction. Here’s the code you need:
$runkey = Get-Item 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Run' $values = Get-ItemProperty $runkey.PSPath foreach ($app in $runkey.Property) { $result = New-Object PSObject Add-Member -input $result NoteProperty 'Application' $app Add-Member -input $result NoteProperty 'Path' $values.$app Write-Output $result }
What we get as a result is a cute table including the names and executables:
Application Path ----------- ---- SynTPStart C:\Program Files\Synaptics\SynTP\SynTPStart.exe SoundMAXPnP C:\Program Files\Analog Devices\Core\smax4pnp.exe …
Each row represents a real object; we could manipulate them one-by-one and access the variables through the dot-notation. But now, let’s consider how the result construction part inside the foreach loop above works.
Add-member is a commandlet that takes an input object, either from the pipeline or a parameter called input. We use the latter syntax here. The input object gets the new member added; in this case, we’re defining a new NoteProperty (note properties are pretty much like normal variables on the object). Our property is called Application and has the initial value of $app, the variable holding the name of the application such as SynTPStart. Then we create another NoteProperty for the path and dig its value from the registry as well. When all is said and done, we just write the output object into the pipeline, delivering it for the next consumer or the display.
A syntactic shortcut: Select-Object
The example above should outline the steps required for adding simple properties. The syntax for Add-Member isn’t exactly compact, so at times you might want to consider an alternative. A commandlet called Select-Object comes to the rescue. Although Select-Object is intended to just select a subset of properties in an object (say dir | select Name, Length
), it also creates properties it cannot find from the original object. This allows alternate, perhaps cleaner syntax to be used:
$result = New-Object PSObject | select Application, Path $result.Application = $app $result.Path = $values.$app
Since the properties were already created by the select-object commandlet, the values can then be set using the normal assignment operator. This could be considered a better approach than using the clumsyish Add-Member, but using Select-Object with nonexistent properties isn’t that great either. Choice between the two should probably depend on circumstances. Particularly, if you’re using add-member anyway (for example, to add more complex members such as methods to the object), you might want to use the same syntax for all additions.
While both alternatives have some clunkiness in their syntaxes, either approach will work and allow you to return structured data from your PowerShell functions. Given that PowerShell is all about structuring of data and getting rid of parsing, this is an essential aspect left with perhaps a bit too little attention on Microsoft’s part. Hope this helps!
June 14, 2009
·
Jouni Heikniemi ·
4 Comments
Tags: PowerShell · Posted in: .NET
4 Responses
Jimmy - July 17, 2009
Great writeup, came in very helpful with the psobject I was working with. Thank you.
Heikniemi Hardcoded » Creating custom types in PowerShell, revisited for v2 - January 20, 2010
[…] June, I blogged on creating custom types within PowerShell. In PowerShell v2, released with Windows 7 and Windows Server 2008 R2, things are a bit […]
love spell - June 22, 2017
Hello,I read your blogs named "Heikniemi Hardcoded » PowerShell: PSObject, custom types and the add-member cmdlet" daily.Your humoristic style is awesome, keep up the good work! And you can look our website about love spell.
Meredith Pagenkopf - January 12, 2021
Only a smiling visitant here to share the love (:, btw outstanding style .
Leave a Reply