Udostępnij za pośrednictwem


Microcode: PowerShell Scripting Tricks: Select-Object (Note Properties) vs Add-Member (Script Properties)

As I've said a number of times before, PowerShell's quantum leap forward is something called the Object Pipeline.  It allows you to take the results of one command and easily use them with the next, which means that each command you create becomes a link in a chain rather than an isolated island.

In several posts, I've talked about how to create objects that are property bags as a means to an end (such as when I was Cleaning Up Get-RecordedTV with Select-Object), but I haven't really focused on how to make property bags that much on its own.

There are two basic ways you can create property bags in PowerShell.  The first is by using the Add-Member cmdlet, and the second is by using Select-Object.  As with anything in programming, deciding which way to get a task done involves some trade offs.  Most of the trade off between Select-Object and Add-Member has to do with what type of properties the commands add to objects.  Select-Object will add note properties, which are fine for most purposes and quick to create, but they cannot do anything when the properties are set and cannot create properties that are always up to date.

The following table shows when I tend to use Select-Object versus Add-Member

When your priority is... You should use...
Adding a few quick properties to an existing object Select-Object
Adding properties that may be take time to evaluate or need to be up-to-date Add-Member
Creating a property bag to summarize information from cmdlets Select-Object
Wrapping an existing object with a friendlier surface area Add-Member
Adding a single property to an object Add-Member

The bottom line of choosing which way to make your property bag comes down to speed and convenience.  Because you have to call Add-Member once for every property you wish to add to an object, Add-Member is often slower to use for this purpose than Select-Object.  However, to be fair, Select-Object is kind of ugly and a little inconvenient to write. To prove the speed gain and illustrate the strange syntax of Select-Object, here's two equivalent scripts that both create an object with two properties, Foo and Bar:

Select-Object Add-Member
     1 | Select-Object @{        Name='Foo'        Expression={"Bar"}    }, @{        Name='Bar'        Expression={"Baz"}    }
       New-Object Object |          Add-Member NoteProperty Foo Bar -passThru |           Add-Member NoteProperty Bar Baz -passThru      

Obviously, Select-Object is a lot less clean looking than Add-Member.  To determine which is faster, just pack each item into a long loop and run Measure-Command.

 Measure-Command {
    foreach ($n in (1..1000))  {
        1 | Select-Object @{
            Name='Foo'
            Expression={"Bar"}
        }, @{
            Name='Bar'
            Expression={"Baz"}
        }
    }
}

Measure-Command {
    foreach ($n in (1..1000))  {
        New-Object Object |
            Add-Member NoteProperty Foo Bar -passThru | 
            Add-Member NoteProperty Bar Baz -passThru
    }
}

On my computer, Select-Object is much faster. It's actually twice as fast over 1000 objects. The more properties you add with Add-Member, the slower it will be.  This concern should really only come into play when dealing with thousands of objects, so if you were adding properties to the output of Get-Process, the difference should be negligible, but if you're adding properties to every single file underneath Windows\System32, you'll probably want to use Select-Object.

Add-Member does have one huge advantage over Select-Object, and that's the type of properties that it can tack on.  Select-Object adds Note Properties, which means that it will evaluate each item as you add it to the object (and never again).  For instance, if I wanted to add a property to a file object that would tell me who currently had the file open, then I would need that property to always be up to date.  In this case, if you had a command that would determine if the file was open (let's call it Test-FileOpen) you could add something like Add-Member ScriptProperty IsOpened { Test-FileOpen $this.Fullname }.

The other thing you can do with a script property that you can't do with a note property is set values.  To do this with a ScriptPropery, you simply give Add-Member two script blocks.  The first is a getter and the second is a setter. In the simple example below, I update the value of Bar through the setter of Foo

 $i = New-Object Object |
    Add-Member NoteProperty Bar "baz" -passThru |     
    Add-Member ScriptProperty Foo {$this.Bar} { $this.Bar = $args[0]} -passThru 
$i
$i.Foo = "bing"
$i

ScriptProperties are especially useful if you would like to take a surface area that's awkward and make it nicer to use in PowerShell.  In this case, you simply create a new object, attach the original object to it as a note property, and then use Script Properties to talk to the original object.  Sadly, in order to demonstrate the usefulness of that particular capability, I have to walk through a long and complicated API.   Rather than make this post more long and complicated, I'll leave this post as food for thought on where you can use note properties and script properties in your code.

Have fun experimenting, and remember: Select-Object's Speedy, Add-Member's Adaptable.

Hope this helps,

James Brundage [MSFT]

Comments