Sometimes you just want a property

On the PowerShell newsgroup I often (really often) see users new to PowerShell getting stuck transitioning from the point where they have a set of objects to creating an array of the values of one of the properties on those objects.  For example, if you are using the free Quest AD cmdlets and you want to retrieve a list of group names to which a member belongs, you can do this:

(Get-QADUser “Poshoholic”).memberOf | Get-QADGroup | ForEach-Object {$_.Name}

This seems simple enough but many people get stuck going from a collection such as that returned by Get-QADGroup to an array of names such as that returned by the ForEach-Object cmdlet shown in this exanple.  The missing piece of knowledge is usually understanding how to use ForEach-Object to extract the member you want.  Also, even when you know how to use ForEach-Object, I find it a little inconvenient to use ForEach-Object with a script block just to pull out a property name, or to put brackets around part of a command so that I can access members on the result.  And I find myself doing this quite regularly in my PowerShell work.

To both save myself from having to use ForEach-Object or brackets when all I want is to invoke a member on an individual object or a collection of objects, and to share this convenience with others, I have created an Invoke-Member function that makes the one-liner I listed above a little more elegant and PowerShelly.  Here’s an updated version of the one-liner I used above, revised to use the Invoke-Member function:

Get-QADUser “Poshoholic” | Invoke-Member memberOf | Get-QADGroup | Invoke-Member name

This one-liner retrieves an AD user named Poshoholic and from that user it retrieves the memberOf member value.  That value contains an array of DNs for groups to which the user belongs.  For each of those DNs, the one-liner then retrieves the AD group object and once it has that it extracts the name of that group.

The Invoke-Member function can be used to invoke any type of member, including both properties and methods (e.g. Invoke-Member “Open()”).  It can also be used to invoke nested members (e.g. Invoke-Member parameters.parameter).  And it can take input from the pipeline or alternatively through the InputObject parameter.  When using the InputObject parameter, the object is passed in as one object, even if it is a collection.  When using the pipeline collections are passed into Invoke-Member one item at a time.

[Update] Since this was originally posted, Xaegr pointed out in the comments that the PowerShell team already had published a function on their blog that does this, and that he has modified that function to support multiple members.  You can find that blog entry here, and Xaegr’s modifications can be found in the comments in this blog entry.  The problem with those two implementations of the … function  is that they don’t support calling methods and they don’t support nesting members, both of which are supported by the Invoke-Member function.  I liked Xaegr’s idea to support retrieving multiple members at once though, which I think could be useful for simplified output of member values without member names attached to that output.  I also think it would be useful when working with certain datasets that have different members with the same type that you want to do something with in one pass through that data set, or when you want to execute multiple methods in sequence during one pass through a dataset.  I added support to the Invoke-Member function so that it would support multiple members as well.

Here is the updated source code for the Invoke-Member function as well as a script to create im and … aliases for the Invoke-Member function:

Function Invoke-Member {
    param (
        [string[]]
$name = $(throw $($(Get-PSResourceString -BaseName ‘ParameterBinderStrings’ -ResourceId ‘ParameterArgumentValidationErrorNullNotAllowed’) -f $null,‘Name’)),
       
$inputObject = $null
   
)
    BEGIN {
    }
    PROCESS {
       
if ($inputObject -and $_) {
           
throw $(Get-PSResourceString -BaseName ‘ParameterBinderStrings’ -ResourceId ‘AmbiguousParameterSet’)
           
break
        } elseif ($inputObject) {
            foreach ($member in $name) {
               
Invoke-Expression “`$inputObject.$member”
           
}
        }
elseif ($_) {
           
foreach ($member in $name) {
               
Invoke-Expression “`$_.$member”
           
}
        } else {
           
throw $(Get-PSResourceString -BaseName ‘ParameterBinderStrings’ -ResourceId ‘InputObjectNotBound’)
        }
    }
    END {
    }
}
if (-not (Get-Alias -Name im -ErrorAction SilentlyContinue)) {
    New-Alias -Name im -Value Invoke-Member -Description ‘Invokes a member of each of a set of input objects.’
}
if (-not (Get-Alias -Name-ErrorAction SilentlyContinue)) {
    New-Alias -Name … -Value Invoke-Member -Description ‘Invokes a member of each of a set of input objects.’
}

Note that this function uses the Get-PSResourceString function that I published recently in my Cmdlet Extension Library (you can read more about what this library contains here).  If you want to use the Invoke-Member function by itself, you will have to change the calls to Get-PSResourceString with static strings that you want to throw on error instead.  To simplify sharing the Invoke-Member function with the community I have added it to my Cmdlet Extension Library and uploaded the updated version to the PowerShell Community site.

Thanks to Joel Bennett for his article about writing functions that can also be executed in the pipeline.  I didn’t end up using Joel’s template as is because I wanted Invoke-Member to follow the model of other cmdlets that have the InputObject parameter, but it was my starting point before I went through numerous revisions and ended up with the final version shown above in the Invoke-Member function code.

Comments/suggestions are welcome and appreciated, as always.

Kirk out.

Technorati Tags: , , , , ,

Advertisements

5 thoughts on “Sometimes you just want a property

  1. Would this not work the same way?:
    Get-QADUser “Poshoholic” | select memberOf | Get-QADGroup | select name

    either way, yours looks like a robust function.

    Like

  2. Hi Ross,

    Select-Object doesn’t work quite the same way, no.

    In the specific example you mentioned in your comment, using Select-Object will create a new object of type PSCustomObject which will contain one member. The first call to Select-Object will create a custom object with a memberOf property that contains the list of groups to which the user belongs. If you then pass that object through the pipeline to Get-QADGroup, it returns nothing because Get-QADGroup doesn’t know that it should use the property called memberOf on the custom object that was passed in.

    Assuming that did work, then the second Select-Object would give you similar results but not quite the same as what you get with Invoke-Member. With Select-Object you’ll get an object (or an array of objects) with a member called Name. If you then want to do something with those you’ll either need a cmdlet that accepts pipeline input by named property or you’ll have to use something like ForEach-Object to get the property you want and then use it.

    In contrast, if you use Invoke-Member you get a nice neat array of strings. Sometimes it is easier to pass this through the pipeline to get what you want, and other times it is more convenient when you are trying to put together data, whether it be in a hash table, or for output to a file or email, or whatever. I have needed to do this when working with PowerShell that it surprised me that an Invoke-Member cmdlet didn’t exist in the first place. Since adding the function to my profile, I find it indispensable.

    Kirk out.

    Like

  3. How about this one? 🙂 I’m use it for about a year 🙂

    [PowerShell] ${function:…}={process {$Object=$_; $args[0]|%{$Object.($_)} } }
    [PowerShell] Get-Process powershell | … id
    10564
    20184
    [PowerShell] Get-Process powershell | … id, cpu
    10564
    240,7251431
    20184
    6,8172437

    The idea are from here http://blogs.msdn.com/powershell/archive/2006/10/21/Power-and-Pith.aspx According to Jeffrey many of the PoSh team using it 🙂 I’m just extended it slightly to allow select more than one parameter.

    Like

  4. Hi Xaegr,

    I hadn’t read that PowerShell blog article before I posted this function. Thanks for pointing the article out to me. I’ll update this entry to link to that article as well.

    There are a few problems with the function in that PowerShell blog article and with the modified version you provided, namely:
    1. They don’t support methods. As a result, you can’t do this:
    Get-Process PowerShell | … “GetType()”
    2. They don’t support nested properties within one function call. As a result, you can’t do this:
    Get-Process PowerShell | … PSObject.TypeNames

    Note that for the second item, you can do it with the … function but you would have to call it twice like this: Get-Process | … PSObject | … TypeNames. That’s not as elegant though.

    Both of these scenarios are supported by the Invoke-Member function as defined above. This is great because it allows me to do things like this:

    Get-ChildItem C:\ -Force | Invoke-Member -Name “GetType().FullName” | Select-Object -Unique

    Or, in a more pithy manner:

    dir C:\ -fo | im “GetType().FullName” | select -u

    Since the other function has been around for a while and people likely have developed the habit of using …, I’ll add an alias named … for the Invoke-Member function.

    Kirk out.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s