Powershell Custom Object property order issue - powershell

When creating a custom object in powershell my properties are not ordering in the same precedence I type them in (top to bottom -> left to right).
I was told to use [ordered] on my table declaration as done below;
$AllMailData += New-Object PSObject -Property [ordered]#{
'Unique ID' = $sharedmail.PrimarySmtpAddress
'Display Name' = $sharedmail.DisplayName
}
However this gives me a syntax error. Can anyone suggest where I have gone wrong?

You will get rid of the error when you enclose everything you're passing to -Property in parenthesis ().
Also shorter way of writing this would be:
$AllMailData += [PSCustomObject]#{
'Unique ID' = $sharedmail.PrimarySmtpAddress
'Display Name' = $sharedmail.DisplayName
}
[PSCustomObject] guarantees preservation of property order, where [PSObject] does not. But it seems that using ordered hashtable in your example accomplishes that, too.

Related

powershell hashtable key versus object property

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?
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

Split hastable at key/value pair

How can I split a hashtable starting from a specific key/value pair?
I have hashtable like the following, just longer:
Name Value
---- -----
Name Alpha
Age 2
Position Trick
Start date 01-01-31
End date Unknown
Name Corax
Age 21
Position Sneak
Earnings 40'000
End Date Unknown
Name Horus
Age 22
Position Dead
Why Heresy
End date 03-30-30
I tried Group-Object but it failed.
I particularly wanted to separate it by Name and everything aside from Name, Age and Position are not consistent.
My actual issue is that I want to parse the hashtable for the Name and Age when Why = Heresy, and unfortunately, the original source of the data is a list of strings, which is the reason why I convert it to a hashtable.
Hashtables are not ordered, so you can't rely on a concept of "before" and "after". If you know the specific names of one complete set of keys, then you can loop through the hashtable and build two new ones, so if you wanted one hashtable to contain the Name, Age, and Position, and the other to contain everything else, you can do something like this:
$New1 = #{}
$New2 = #{}
$KeysGroup1 = #('Name','Age','Position') # This could just be one value
$MyHashTable.GetEnumerator().ForEach({
if ($_.Key -in $KeysGroup1) {
$New1[$_.Key] = $_.Value
} else {
$New2[$_.Key] = $_.Value
}
})
You can use an ordered dictionary if order is important for you. You can use a shortcut to create a literal ordered dictionary by preceding a literal hashtable with the [ordered] type accelerator:
$myOrdered = [ordered]#{ a = 1; z = 5; g = 2 }
From there you could do a similar approach to above that relies on order.
To create an ordered dictionary that isn't based on a literal:
$myOrdered = New-Object System.Collections.Specialized.OrderedDictionary
# or in PowerShell v5
$myOrdered = [System.Collections.Specialized.OrderedDictionary]::new()
$myOrdered.Add('key','value')
Edit based on comments
It sounds more like what you have is an array of hashtables and you now want to go about filtering these.
A [hashtable] can (and is often) used as a sort of proto-object, and it can be very useful for that because it often supports the same syntax, and it has built-in literal support.
But you're starting to run into their limits, and at this point I think you want to be dealing with an array of objects and not an array of hashtables.
Luckily, there are really easy ways to create objects in PowerShell right from a hashtable:
$obj = New-Object -TypeName PSObject -Property $myHash
$obj = [PSCustomObject]$myHash
$objArray = $myHashArray.ForEach({[PSCustomObject]$myHash})
Once you've got your array of objects, the real fun begins:
$heretics = $objArray.Where({$_.Why -eq 'Heresy'})
You'll notice I didn't even bother filtering out the other properties here. You shouldn't, until you really need to. Then you can use Select-Object or just access the properties you need. So for display purposes you might just do:
$heretics | Format-Table Name,Age
There's more stuff you can do with an object that you can't with hashtables, like add special types of properties:
$objArray | Add-Member -MemberType ScriptProperty -Name IsHeretic -Value { $this.Why -eq 'Heresy' } -Force
$heretics = $objArray.Where({$_.IsHeretic})
So, in the end; I figured something out, Thanks a lot to #briantist for guidance to the right direction.
My solution code is:
$startobj = (0..($hastable.count -1)) | where {$hashtable[$_].keys -like "*Name*"}
$endobj = (0..($hashtable.count -1)) | where {$hashtable[$_].keys -like "*End date*"}
$objindex = (0..($hashtable.count -1))
$numberofobjindex = 0..($hashtable.Where({$_.keys -like "*Name*"}).count - 1)
$hashtableparsed = foreach ($numba in $numberofobjindex) {
$Conv2data = $hashtable[($startobj[$numba]..$endobj[$numba])]
[PSCustomObject] #{
Name = $Conv2data.Name
Age = $Conv2data.Age
Why = $Conv2data.Why
}
}
$hashtableparsed
I realized that the hashtable data I was presented with had a repeating pattern with Name at the start of each cycle, and End date at the end.
So I basically did an indexing of the hashtable, and marked and indexed all instances where the cycle would start and end.
I then counted the cycles, and FOR EACH cycle captured the data of hashtable data in the lines particularly of that cycle and turned it into a PSCustomObject

Hashtable vs array of custom objects regarding iteration

I frequently write lists of things and perform enumeration against them to perform some get/set.
I hate enumerating hashtables, since whenever I have to do it, I have to bend my mind backwards to work with hashtable objects.
$hashtablelistofitems = #{}
$hashtablelistofitems.add("i'm a key", "i'm a value")
foreach ($item in $hashtablelistofitems.keys) {
$item
$hashtablelistofitems.item($item)
}
Instead, I usually revert to using a single dimensional array of a custom object with two noteproperties.
$array = #()
$listofitems = "" | select key,value
$listofitems.key = "i'm a key"
$listofitems.value = "i'm a value"
$array += $listofitems
foreach ($item in $listofitems) {
$item.key
$item.value
}
Why should I be using a hashtable over this method? Simply because it only guarantees a single value per key?
You should use a hashtable if you want to store a key value list and not create an array containing a custom object with two properties (key / values) for mainly two reasons:
You might want to pass your hashtable to a function that expect a hashtable.
Hashtable is a built-in PowerShell type which users are aware of. Your second approach is harder to read / maintain for other users.
Note:
You can iterate over a hashtable almost the same way as your approach by calling the GetEnumerator() function:
foreach ($item in $listofitems.GetEnumerator()) {
$item.key
$item.value
}
Also, the hashtable comes with handy methods that you might want to use:
#{} | Get-Member | Where-Object MemberType -eq Method | Select Name
Output:
Name
----
Add
Clear
Clone
Contains
ContainsKey
ContainsValue
CopyTo
Equals
GetEnumerator
GetHashCode
GetObjectData
GetType
OnDeserialization
Remove
ToString
This is nowhere near as useful as Martin's compendium, but it's useful enough.
It's an MSDN article about how to convert back and forth between Hashtables and PSCustomObjects. The article

How to get "-property foo.insidefoo" to work in Powershell?

Anyone know the proper way to do this statement?
$dlls | format-list -Property name, Versioninfo.Product
When I use the dot variable with -Property, it just generates a new property instead of giving me Product.
What I'm trying to do is get the source of whole lists of DLLs to see where they came from (e.g. Microsoft, Google, Company, etc ). If there is an easier way to do this, please tell.
You can't use that format. You can provide Format-List a [hashtable] that defines a new property name and an expression to generate the value though:
$newCol = #{
Name = 'Product'
Expression = {
$_.Versioninfo.Product
}
}
$dlls | format-list -Property name,$newCol

PowerShell: How to change table headers in loop?

I want to collect data remotely and adapt the table headers later with the help of an xml-file. This should happen in a loop, looking like that:
foreach($tableheader in $table) {
$table.$tableheader = $xmlFile.$tableheader
}
Amongst others I tried the following:
$x = 0
$sitesonfig = Get-ConfigSite -AdminAddress localhost
foreach($Prop in ($siteconfig |get-member -MemberType Property | select -Property name))
{
$x += 1;
$siteconfig = $siteconfig | Select-Object * | format-table #{l="Smile$x";e={$_.$Prop}}
}
Yes, I know this looks silly, but I've got really no idea, how to change the headers one by one without listing each time all the other headers, too.
One possibility is to use a loop to create the header map that you pass into Format-Table.
Here is your second example modified to demonstrate this concept. You should be able to adapt this to grab the header info from your XML file.
$x = 0
$siteconfig = Get-ConfigSite -AdminAddress localhost
$headerMap = #()
foreach($Prop in ($siteconfig |get-member -MemberType Property | select -ExpandProperty name))
{
$x += 1;
$headerMap += #{
l="Smile$x";
e={ $_.$Prop }.GetNewClosure()
}
}
$siteconfig | Format-Table $headerMap
Important Points
Select -Property name needed to be changed to Select -ExpandProperty name. The reason for this is that Select-Object in PowerShell will return an object filtered down to the selected member but you need a string for grabbing the property value by name. The -ExpandProperty parameter will expand this to be the string value instead.
The expression block needs GetNewClosure() called on it to capture the value of $Prop at the time of script block creation versus at the time of calling. This will probably be a little confusing if you are new to the concept of closures and PowerShell's scoping rules. Without this, due to PowerShell's scoping rules, $Prop will evaluate to the value of $Prop at the time it is used by Format-Table. By calling GetNewClosure(), the value of $Prop is captured when GetNewClosure() is called which is what we want in this case.