Search code examples
powershellmeasureget-childitempowershell-5.1

Measure multiple properties?


I'd like to query a directory with Get-ChildItem and create a table with columns like Path, Size(in Gb), MinimumCreationTime, MaximumCreationTime. In foreach cycle I wrote 3 Measure commands. Is it possible Measure multiple properties with one command?

$pathes = @'
C:\open
C:\games
'@.Split([System.Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries)

foreach ($path in $pathes){
    Get-ChildItem $path -Recurse | Measure Length -Sum 
    Get-ChildItem $path -Recurse | Measure CreationTime -Minimum
    Get-ChildItem $path -Recurse | Measure CreationTime -Maximum
    }

Solution

  • It is possible with a single call to the Measure-Object command, using a calculated property that converts the CreationTime property to numeric type. Now -Sum can work with that (albeit we'll discard the sum for CreationTime).

    After we've calculated the stats, we convert back to [DateTime] to get meaningful display values.

    Since PS 7+, a calculated property can be used as Measure-Object argument. For older PS versions, we can use Select-Object to create a calculated property.

    PS 7+ solution

    foreach ($path in $pathes){
    
        $stats = Get-ChildItem $path -File -Recurse | 
                 Measure-Object 'Length', { $_.CreationTime.Ticks } -Sum -Minimum -Maximum
    
        # Create the output for one table row
        [PSCustomObject]@{
            Path                = $path
            'Size(GB)'          = [math]::Round( $stats[0].Sum / 1GB, 2 )   # 2 = number of digits
            MinimumCreationTime = [DateTime] [Int64] $stats[1].Minimum
            MaximumCreationTime = [DateTime] [Int64] $stats[1].Maximum
        }
    }
    

    Explanation:

    • We are specifying two properties in the call to Measure-Object:
      • 1st property is just Length
      • 2nd property is a calculated property, meaning it gets its value from running a small script block. The script block converts the CreationTime to Int64 and uses it as value that will be measured.
    • When specifying multiple properties for Measure-Object, it outputs an array that contains an object for each property, which contains the stats.
      • $stats[0] contains the Sum, Minimum and Maximum for the Length property, of which we only take the Sum.
      • $stats[1] contains the Sum, Minimum and Maximum for the CreationTime property, of which we only take the Minimum and Maximum. Note that Measure-Object produces output of type [double], so we first have to convert back to [Int64] before finally converting back to [DateTime].

    PS 5 solution

    foreach ($path in $pathes){
    
        $stats = Get-ChildItem $path -File -Force | 
            Select-Object Length, @{ name = 'CreationTimeTicks'; expression = { $_.CreationTime.Ticks } } | 
            Measure-Object Length, CreationTimeTicks -Sum -Minimum -Maximum
    
        # Create the output for one table row - identical to PS 7+ solution
        [PSCustomObject]@{
            Path                = $path
            'Size(GB)'          = [math]::Round( $stats[0].Sum / 1GB, 2 )   # 2 = number of digits
            MinimumCreationTime = [DateTime] [Int64] $stats[1].Minimum
            MaximumCreationTime = [DateTime] [Int64] $stats[1].Maximum
        }
    }
    

    Explanation:

    This is similar to the PS 7+ solution, except that we use Select-Object to create a calculated property named CreationTimeTicks, so we can pass it by name to the Measure-Object call.

    Conclusion

    While this code appears to work, the code presented by this answer is conceptually much clearer, so I would go with it.