After posting my blog entry about naming your custom object types on Thursday, Hal Rottenberg left me a comment saying how it’s a shame that you have to manually create ps1xml files to store your type data and format data extensions. Hal’s right. Having to create ps1xml files to accompany each script you make that generates custom objects is too much work for the script author, and the script consumer has more files to download each time. But then I thought twice about what Hal said, and asked myself: Is creating the ps1xml files to get the output you want from custom objects really necessary?
Fortunately the answer to this is “No”. But why not? Before I answer that, I should give a short explanation about how PowerShell determines what properties it will display when an object is output and in what format that object will be displayed.
How PowerShell determines the default format for an object
Let’s use the WMI service object for the Windows Update service as an example. You can get this object in PowerShell using this command:
$object = gwmi Win32_Service -Filter ‘name=”wuauserv”‘
This command calls Get-WmiObject (using the gwmi alias) and requests the Win32_Service object that has the name “wuauserv”. The result object is stored in the $object variable.
Now that the object is stored, if you want to view it you simply need to enter “$object” (minus the quotes) in your PowerShell console and you’ll see the default representation of that object. It looks like this:
Here you can see the ExitCode, Name, ProcessId, StartMode, State and Status properties for the Win32_Service object. There are many more properties than these six though. You can type “$object | Format-List *” in PowerShell for yourself to see them all…there is quite a long list. So how did PowerShell decide that these six properties were the ones to display by default? In this case, the object itself contained the list of properties that would be displayed by default. In PowerShell, any object may have the set of properties that will be output by default when that object is displayed stored in a member on the object itself. It’s not kept in the most obvious of locations, but you can find it when you need to.
For any object in PowerShell, you can access its PSStandardMembers.DefaultDisplayPropertySet property to see the properties that are default for that object, if they are defined. They won’t be defined for all objects, but in our example, they happen to be. I show the results of the command to see the default property set here:
In this output window, the ReferencedPropertyNames property contains the list of properties that are displayed in PowerShell by default (ExitCode, Name, ProcessId, …).
What caused our object to display the properties in list format though? After the default properties are set on an object (assuming they are set), when you output an object to the console without any Format-verb cmdlets at the end of the pipeline, the PowerShell formatting engine looks in the format data it has loaded and finds the first format data specification whose object type matches the one of the types in the object hierarchy that can be found by accessing the PSObject.TypeNames parameter. It doesn’t matter if it is for a list format, a table format, a wide format, or a custom format — the first one found is used. The types in the object hierarchy are looked up starting with the lowest derived type (index 0 in that collection) and then moving on to the next lowest (index 1), and so on until the base type is reached.
For our Win32_Service object, there is no format data specification for any of the object types, in which case PowerShell applies a default formatting rule: if there are four properties or less in the list of properties to display (the default properties if they were assigned, all properties if they were not), display the object in table format; otherwise, display it in list format. For other types of objects there may be formatting data found, and when this happens the default properties are ignored and the default format is derived using the first matching format data specification. This simplifies this whole process, but it should give you an idea how it works.
How to define the default properties for any object
Now that you have a general idea how formatting works, do you see the shortcut to defining the default output for custom objects you create? Here’s a hint: it isn’t through the creation of a ps1xml file.
The easiest way to define the default output for a custom object is to add a PSStandardMembers member to the object and set the default properties in that member. For objects that don’t have their default properties defined in a type data file, this is very easy to do. Assume you have a script that generates and returns one or more custom objects with six properties: Name, Property1, Property2, Property3, Property4 and Property5. Here’s a script to create one such object:
$myObject = New-Object PSObject
$myObject | Add-Member NoteProperty Name ‘My Object’
$myObject | Add-Member NoteProperty Property1 1
$myObject | Add-Member NoteProperty Property2 2
$myObject | Add-Member NoteProperty Property3 3
$myObject | Add-Member NoteProperty Property4 4
$myObject | Add-Member NoteProperty Property5 5
To view this object, enter $MyObject in the console. Here is the default output for that object:
There are no default properties assigned for this object and there are no type data or format data for the object type, so PowerShell resorts to determining the output format based on the number of properties that the object has. This is fine for one object, like we have here, but if you have a script that returns a collection of these, having their default output in list format does not give users of your script a very good user experience because it is very hard to find information in a long list of objects output in list format in PowerShell. To show the objects in table format users can simply pipe them to the Format-Table cmdlet where they can specify the properties to show, but they shouldn’t have to do that. Instead, you can update your script so that it specifies the default properties when these objects are created. Here are the additional commands required to specify the default properties for our sample object:
$defaultProperties = @(‘Name’,’Property2′,’Property4′)
$defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet(‘DefaultDisplayPropertySet’,[string]$defaultProperties)
$PSStandardMembers = [System.Management.Automation.PSMemberInfo]@($defaultDisplayPropertySet)
$myObject | Add-Member MemberSet PSStandardMembers $PSStandardMembers
The first command sets the default properties. Then in the next command we create a new property set containing those default properties. With that property set, we can create the collection of member info objects we need. And then once we have that collection we can add that as our member set to our object.
After you have done this your custom object will display the default properties you specified when you output it without any Format-verb cmdlets. Here’s our $myObject default output after running the commands shown above:
That’s more like it! Now we have a custom object with a predefined default property set, ready for users to start using, and our custom object still contains all of the properties for the object so that users can get additional fields if they want them!
Taking it further
Armed with this knowledge, you should be able to specify the default properties on any custom objects you create without using ps1xml files. What if you wanted to take this further? What functionality would be useful to have for this in a generic script so that you could get even more use out of it?
Using the knowledge derived above I was able to put together a script called Select-Member.ps1 that provides rich support for selecting the default properties for an object if none exist, or overriding the default properties for an object if they were defined in a type data file. Select-Member can be used in a pipeline against a collection, or it can be used by itself passing the objects to process into the inputObject property.
Note that when you download this script, if you haven’t already you will also need to download Get-PSResourceString.ps1. This is a simple utility script used to look up localized error messages, and it is used by Select-Member.
Here are the syntaxes supported by Select-Member:
Select-Member [-include] <string> [-exclude <string>] [[-inputObject] <psobject>]
Select-Member -exclude <string> [[-inputObject] <psobject>]
Select-Member -reset [[-inputObject] <psobject>]
The first variation allows you to specify which properties you want to include and optionally which properties you want to exclude. If you exclude properties, those will be removed only after the list of properties to include have been processed. Both the include and the exclude parameters support wildcards as well.
The second variation allows you to specify which properties you want to exclude from the default without including any parameters.
The last variation allows you to reset the object so that it will use the default property set as defined by the type data files and throw away any default property set that was added with Select-Member.
Here’s a screenshot showing some cool things you can do with this script and WMI objects:
Here’s another screenshot showing how you can get better formatting on ADSI objects:
Note that in both these examples, the value isn’t simply in being able to specify the defaults ad-hoc like this; format-table can allow you to specify which properties you see. The real value lies in writing scripts or functions that output objects already formatted a certain way. You could have a script that would set the default properties for a bunch of WMI objects you use, or a script that creates its own objects and outputs them with default properties defined. There are other opportunities with this cmdlet as well, but this should get you started.
What’s missing from this?
The Select-Member script doesn’t yet support specifying the parameter sort order, nor does it support specifying the single default parameter that is used when using wide format. These could be easily added in the future, and I will look into that as time permits.
As with all scripts I write, I’d love to hear what you think. Whether you use the simple solution to specify default properties for custom objects or the more advanced Select-Member script, let me know how well it works out for you.
|Share this post:|
14 thoughts on “Essential PowerShell: Define default properties for custom objects”
I am a big fan of Powershell and I very regularly use custom objects in my scripts. They just give me so much flexibility, for instance to do sorting, filtering, grouping or formatting afterwards.
Using a Format-… command in a script or function to get the standard output set properly is killing, because it make it impossible to use additional filtering, sorting and so on.
I am definitively going to use the first method you describe. Maybe I’ll create my own function to make it easier to implement in my scripts.
The full Select-Member script might be a but too much for me, especially because of the second required script.
Just so you know, removing the second required script would be pretty easy. I use Get-PSResourceString because I try to show how easy it can be to access the canned error messages that come with PowerShell, localized in the language you run PowerShell in. But my scripts are not fully localized because I don’t have the time or the resources to do that. They’re just as localized as I can make them. If you want to use the Select-Member script without the Get-PSResourceString script, you could just replace the Get-PSResourceString calls with strings indicating what the error is in the language of your choice. For example, this:
throw $(Get-PSResourceString -BaseName ‘ParameterBinderStrings’ -ResourceId ‘AmbiguousParameterSet’
could be changed to this:
throw ‘Ambiguous parameter set.’
or if you want the actual English text used in PowerShell, this:
throw ‘Parameter set cannot be resolved using the specified named parameters.’
That might let you use the full Select-Member function more easily. But of course you could strip it down to just what you need as well, or roll your own function based on the first script to add the default property set.
This is really good info. I’m having a problem with it, though, and you might be able to help. I’m trying to access the default properties of an object in a custom pshost in order to populate a grid (or a treeview…it doesn’t really matter which), and I can’t seem to find the PSStandardMembers member in the members collection. I’ve tried using the .Members(“PSExtended”).Value to get the Extended property set (since the property set in question was loaded from a formatting ps1xml file), and then looking in that object for a member with a name of “DefaultDisplayPropertySet”, but can’t seem to find it. I’ve tried lots of other combinations as well, all to no avail. If nothing else works, I can always do an invoke of a pipeline that does $o.PSStandardMembers.DefaultDisplayPropertySet, but that seems like a hack. Any ideas?
I just tried this out on PowerShell v2 and it doesn’t work. Any ideas on what might have changed and how to fix it?
I did get it working correctly on PowerShell v1.
Great post, very useful.
Unfortunately the PowerShell Team broke this capability in PowerShell 2.0 RTM. They are aware of the issue and it is my understanding that they will fix it in a future release. I’ll be keeping my eyes open for that fix because this functionality is too handy to do without.
After posting my comment I did some searching and found a question on stackoverflow detailing that it was a bug.
I created a workaround:
If you get a chance, take a look and let me know what you think. All feedback is appreciated.
Thanks for the update!
Nice workaround, that works quite well! I’m going to have to play with it and try a few scenarios I have in mind to see if it supports them (one in particular I’m curious about — overriding the default property set for a type that already has it defined…does it just override the default property set or does it lose all other type information with the override). Anyhow, thanks for taking the time to build the workaround! I simply hadn’t gotten around to it yet.
Thanks for all, I have learnt so much from this article.
I’m glad you liked it!
I got here because i was looking for a way to show a a custom object with more than 4 properties as a table without using the format-table ( which turns objects into a stream of strings and looses context and disallow use of where filters ). Thanks for your post. But unfortunetely Select-Member suffers from the same symptom as Select-Object . Objects up to 4 properties shows as table, above 4 properties shows as list… Now what i was looking for
You can’t force tabular output with more than four columns via script. You’ll need a format.ps1xml file for that. If you create a table format definition for the type of object you are creating (which you should decide upon and assign if you’re creating custom objects), you can specify the default format is of type table, which columns are to be used in the table by default, and what widths and alignments those columns should use. For example, if you open DotNetTypes.format.ps1xml and search for System.Diagnostics.Process you will see the definition for the default output of the process objects (returned by Get-Process). That output shows 7 columns in a table.
[…] possible to define the default properties you are interested in. This link gives you more details, the tl;dr summary of which is as […]
[…] https://poshoholic.com/2008/07/05/essential-powershell-define-default-properties-for-custom-objects/ […]
[…] たっとえばGet-ChildItemすると返ってくるオブジェクトはたくさんプロパティがあるけど表示はさっぱりしてるわけですよ ああいうのを自前のpsobjectを返す関数なんかでもやりたいなって やり方ちっともわかんなかった(というかどんなワードでググればヒットするのかわかんなかった)んだけど今日判明しました ありがとうサンタさん […]