Question:
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:
1 2 3 4 5 6 7 8 9 10 |
$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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 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.
Answer:
The easiest way to go about what I believe you are trying to do is Select-Object
1 2 |
$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
1 2 |
$fooBar = Get-ChildItem | Select-Object @{Name = 'SizeMB'; Expression = {$_.Size / 1MB}} |
Or manually create new properties with the [PSCustomObject]
type accelerator:
1 2 3 4 5 6 7 8 |
$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.
1 2 3 4 |
$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.
1 2 |
$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.
1 2 3 |
$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.