2

I'm confused at how PowerShell treats Hashtable keys versus object properties.

Consider:

$list = @{
    'one' = @{
        name  = 'one'
        order = 80
        };

    'two' = @{
        name  = 'two'
        order = 40
        };

    'twotwo' = @{
        name  = 'twotwo'
        order = 40
        };

    'three' = @{
        name  = 'three'
        order = 20
        }
    }

$list.Values|group-object { $_.order }
$list.Values|group-object -property order

The first Group-Object gives me what I expect (three groups), the second one does not (one big group). Clearly Hashtable keys are not object properties, but syntactically they are referenced in the same manner (var.name).

What is that second group-object actually doing?
What does it think the 'order' property is?

1
  • First thing worth noting here is that $_.order in the first example is syntactic sugar that gets translated into (and executed as) $_['order'] Commented Feb 28, 2018 at 20:51

1 Answer 1

6

This is understandable confusion, but as you said, hashtable keys are not object properties.

It can be tempting to treat them the same (and sometimes that works), but this is a situation where it definitely won't.

And part of the reason is that you're using a different cmdlet, not the direct language semantics.

Hashtables can use dot notation for their keys but the keys are not properties, and when you use the -Property parameter of Group-Object, you are looking for properties specifically, not just "anything you can access with a dot".

The alternative form of that parameter that takes a scriptblock, as you saw, is code that will be executed and so it's whatever value that block returns that will be grouped on.

To more directly answer your questions:

What is that second group-object actually doing?

It's looking for a property (specifically) on the current object (which is a hashtable).

If you want to see what the properties on one of those objects looks like, try this:

$list.Values[0].PSObject.Properties | ft

What does it think the 'order' property is?

It doesn't think it's anything; it looks for a property with that name and if it finds one it uses that value; otherwise it uses $null.

You'll get the same result with:

$list.Values | group -Property FakeProp

or

$list.Values | group -Property { $null }

Addressing your question in the comment:

Is there any way to know "when you should" and "when you shouldn't", Should I just defer to using script blocks, When is -Property usage preferred over { ... }?

-Property (without a scriptblock) is preferred whenever the value you want to group is available as a direct property of the object being inspected. If it's a property of a property, or some calculated value, or a hashtable key/value, or anything else, use a scriptblock. I'll call those "complex values".

If the complex value is useful, you may want to add it as an actual property of the object itself to encapsulate it; then you can reference it directly. This example isn't really appropriate for your hashtable situation but consider objects that represent people. They have 2 properties: Name and DateOfBirth.

$people is an array of these objects, and you want to group people by age (please ignore my inaccurate age-determining code).

$people | Group-Object -Property { ([DateTime]::Now - $_.DateOfBirth).Days / 365 -as [int] }

That's ok if you never need to know the age again; of course that's unlikely and also this looks a bit messy. It should be more immediately clear that you want to "group by age".

Instead, you can add your own (calculated) Age property to the existing objects with Add-Member:

$people |
    Add-Member -MemberType ScriptProperty -Name Age -Value {
        ([DateTime]::Now - $this.DateOfBirth).Days / 365 -as [int]
    } -Force

From here on out, each object in the $people array has an Age property that is calculated based on the value of the DateOfBirth property.

Now you can make your code clearer:

$people | Group-Object -Property Age

Again this doesn't really address your hashtable issue; the truth is they don't work that well for grouping. If you're going to do a lot of grouping with them, and you don't really need hashtables, make them into objects:

$objs = $list.Values |
    ForEach-Object -Process {
        New-Object -TypeName PSObject -Property $_ # takes a hashtable
    }

or

$objs = $list.Values |
    ForEach-Object -Process {
        [PSCustomObject]$_ # converts a hashtable to PSObject
    }

Then:

$objs | Group-Object -Property Order
Sign up to request clarification or add additional context in comments.

2 Comments

Is there any way to know "when you should" and "when you shouldn't", Should I just defer to using script blocks, When is -Property usage preferred over { ... }?
@Dweeberly my answer to your question quickly got out of control so I added it as an edit ;)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.