Home > Software design >  Using Measure-Object on array of hashtables in PowerShell
Using Measure-Object on array of hashtables in PowerShell

Time:10-26

Statements below are executed in Windows PowerShell 5.1

I have a script that executes several runs, during which it creates files and measures the time it takes. The results are stored in an array of PSCustomObject.

Here's a sample of the data:

> $ResultsCreate | Select-Object -First 10 | ft

Run Folder                                 File                                                       Size Create
--- ------                                 ----                                                       ---- ------
  1 \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv\file_1_1.bin   304087,04 00:00:00.1377073
  1 \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv\file_1_2.bin   608174,08 00:00:00.0776793
  1 \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv\file_1_3.bin      524288 00:00:00.0780064
  1 \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv\file_1_4.bin   922746,88 00:00:00.0759425
  1 \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv\file_1_5.bin   503316,48 00:00:00.0842039
  1 \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv\file_1_6.bin   178257,92 00:00:00.0610860
  1 \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv\file_1_7.bin  1038090,24 00:00:00.0916000
  1 \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv\file_1_8.bin   660602,88 00:00:00.0782461
  1 \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv\file_1_9.bin   293601,28 00:00:00.0612056
  1 \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv \\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv\file_1_10.bin  744488,96 00:00:00.0758247

Here's how the PSCustomObject is defined:

> $ResultsCreate | Get-Member

   TypeName: Selected.System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Create      NoteProperty timespan Create=00:00:00.1377073
File        NoteProperty string File=\\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv\file_1_1.bin
Folder      NoteProperty string Folder=\\opidt660-vxzv\PB-VW_NKB$\Lmneom\vxzv
Run         NoteProperty int Run=1
Size        NoteProperty double Size=304087,04

What I'd like to do is get aggregates (average, min, max) for the files in each run. Seems easy enough, but for some reason I cannot get Measure-Object to do what I want.

Here's my attempt to group the array $ResultsCreate by Run and then calculate the average. I end up with an empty $stat hashtable (the keys are there, the values are empty).

> $($ResultsCreate |
    Group-Object -Property Run -AsHashTable).GetEnumerator() |
    ForEach-Object -Begin {$stat = @{}} -Process { $stat[$_.Key] = $(Measure-Object -Property $_.Value.Duration -Average) }

> $stat

Name                           Value
----                           -----
2
1

Even a simple example (ignoring Run completely) fails miserably.

> $ResultsCreate |  Measure-Object -Property $_.Create.TotalSeconds

Measure-Object : Cannot validate argument on parameter 'Property'. The argument is null or empty. Provide an argument that is not null or empty, and th
en try the command again.
At line:1 char:44
  $ResultsCreate |  Measure-Object -Property $_.Create.TotalSeconds
                                             ~~~~~~~~~~~~~~~~~~~~~~
      CategoryInfo          : InvalidData: (:) [Measure-Object], ParameterBindingValidationException
      FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.MeasureObjectCommand

I have no clue what's wrong, and it's driving me up the wall. Any help, hints and/or tips would be greatly appreciated!


UPDATE: Finally found a way to make this work. In short, it seems that Measure-Object cannot access the TotalSeconds property directly.

> $($ResultsCreate |
    select-object -Property *,@{ Name = 'TotalSeconds'; Expression = {  $_.Create.TotalSeconds }} | 
    Group-Object -Property Run -AsHashTable).GetEnumerator() |
    ForEach-Object -Begin {$stat = @{}} -Process { $stat[$_.Key] = $_.Value | Measure-Object -Property TotalSeconds -Average }

> $stat

Name                           Value                                                                                                                   
----                           -----                                                                                                                   
2                              Microsoft.PowerShell.Commands.GenericMeasureInfo                                                                        
1                              Microsoft.PowerShell.Commands.GenericMeasureInfo                                                                        

Apparently prior to PS 6, hashtables are a bit of an issue:

Beginning in PowerShell 6, Measure-Object supports measurement of hashtable input.

Source

CodePudding user response:

Here's how I managed to solve the issue:

$($ResultsCreate |
    Select-Object -Property *,@{ Name = 'TotalSeconds'; Expression = {  $_.Create.TotalSeconds }} |
    Group-Object -Property Run -AsHashTable).GetEnumerator() |
    ForEach-Object `
        -Begin {$Stats = @()} `
        -Process { $Stats  = $_.Value |  
                        Measure-Object -Property TotalSeconds -Maximum -Minimum -Average |
                        Select-Object -Property Count, Average, Maximum, Minimum | 
                        Add-Member -Name 'Run' -Type NoteProperty -Value $_.Key -PassThru }

The end result is this:

> $Stats | ft

Count     Average   Maximum   Minimum Run
-----     -------   -------   ------- ---
  100 0,082614501 0,1272803 0,0574419   2
  100 0,076335947 0,1377073 0,0597995   1

CodePudding user response:

Currently the Measure-Object doesn't support the TimeSpan structure.
See: #10712 Measure-Object should support TimeSpan.
But you might do this to get e.g. the average timespan of the Run groups:

$ResultsCreate |Group-Object Run |Select-Object Name,
    @{ Name = 'Average'; Expression =
        { [TimeSpan][int]($_.Group.Create.Ticks |Measure-Object -Average).Average }
    }
  • Related