Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sort Hashtable with Arrays as values

Description: I'm building a PowerShell-script that searches for files, then gives them unique names, copies them and then verifies them via hash-calculation - I chose to split the script in functions for each step, so it's easier to maintain the whole thing. To get all values from one function to the other, I chose to use [hashtable]$FooBar - inside $FooBar, there are multiple arrays, such as FullName or OutputPath (which may change per file as they will be copied to subfolders named yyyy-mm-dd). All arrays are correlating with each other (meaning that index 1 contains all values of the first file, index 2 the values for the second file,...) and this works fine as of now.

A short simplified visualisation:

$FooBar = @{}
$FooBar.FullName = @()
$FooBar.Size = @()
$FooBar.Ext = @()
Get-ChildItem | ForEach-Object {
    $FooBar.FullName += $_.FullName
    $FooBar.Size += $_.Length
    $FooBar.Ext += $_.Extension
}

However, I now need to sort them all by one value-set of one of the arrays, e.g. the size. Or, visualised again:

# From:
$FooBar
Name                           Value
----                           -----
fullname                       {D:\AAA.XYZ, D:\BBB.ZYX, D:\CCC.YZX}
size                           {222, 111, 555}
extension                      {.XYZ, .ZYX, .YZX}

# To:
$FooBar = $FooBar | Sort-Object -Property Size -Descending
$FooBar
Name                           Value
----                           -----
fullname                       {D:\CCC.YZX, D:\AAA.XYZ, D:\BBB.ZYX}
size                           {555, 222, 111}
extension                      {.YZX, .XYZ, .ZYX}

I tried $FooBar.GetEnumerator() | Sort-Object -Property Size, but this does not change anything. Google turned up suggestions on how to sort an array of hashtables, but in my case, it's the other way round, and I can't get my head around this because I don't even understand why this is a problem in the first place.

So my question is: is there any way to sort all arrays inside the hashtable by the value-set of one of the arrays? I can't get my head around this.

Disclaimer: I'm a PowerShell-autodidact with no reasonable background in scripting/programming, so it might well be that my "include everything in one hashtable"-solution isn't going to work at all or might be extremely inefficient - if so, please tell me.

like image 987
flolilo Avatar asked Dec 19 '22 06:12

flolilo


1 Answers

The easiest way to go about what I believe you are trying to do is Select-Object

$fooBar = Get-ChildItem | Select-Object FullName, Size, Extension

This will create an array of new objects that only have the desired properties. The reason this works and your method doesn't is because Sort-Object works on properties and the property you are specifying is behind a few layers.

If you need more flexibility than just exact properties, you can create your own like this

$fooBar = Get-ChildItem | Select-Object @{Name = 'SizeMB'; Expression = {$_.Size / 1MB}}

Or manually create new properties with the [PSCustomObject] type accelerator:

$fooBar = Get-ChildItem | ForEach-Object {
    [PSCustomObject]@{
        FullName = $_.FullName
        Extension = $_.Extension
        Size = $_.Size
    }
}

Update

If you need to add additional properties to the object after it's initially created you have a few options.

Add-Member

The most common method by far is by using the Add-Member cmdlet.

$object | Add-Member -MemberType NoteProperty -Name NewProperty -Value 'MyValue'

$object

Something important to keep in mind is that by default this cmdlet does not return anything. So if you place the above statement at the end of a function and do not separately return the object, your function won't return anything. Make sure you either use the -PassThru parameter (this is also useful for chaining Add-Member commands) or call the variable afterwards (like the example above)

Select-Object

You can select all previous properties when using calculated properties to add members. Keep in mind, because of how Select-Object works, all methods from the source object will not be carried over.

$fooBar | Select-Object *, @{Name = 'NewProperty'; Expression = {'MyValue'}}

psobject.Properties

This one is my personal favorite, but it's restricted to later versions of PowerShell and I haven't actually seen it used by anyone else yet.

$fooBar.psobject.Properties.Add([psnoteproperty]::new('NewProperty', 'MyValue'))
$fooBar

Each member type has it's own constructor. You can also add methods to $fooBar.psobject.Methods or either type to $fooBar.psobject.Members. I like this method because it feels more explicit, and something about adding members with members feels right.

Summary

The method you choose is mostly preference. I would recommend Add-Member if possible because it's the most used, therefore has better readability and more people who can answer questions about it.

I would also like to mention that it's usually best to avoid adding additional members if at all possible. A function's return value should ideally have a reliable form. If someone is using your function and they have to guess when a property or method will exist on your object it becomes very difficult to debug. Obviously this isn't a hard and fast rule, but if you need to add a member you should at least consider if it would be better to refactor instead.

like image 92
Patrick Meinecke Avatar answered Jan 17 '23 11:01

Patrick Meinecke