Add a where-object on a table construct?

Lee Dailey has provided the crucial pointer in a comment:

  • To transform data for further programmatic processing, use Select-Object or other data-transformation methods, such as via ForEach-Object.

  • Only ever use the Format-* cmdlets to format data for display, as their name suggests.

    • PowerShell’s great evolutionary leap was to send objects rather than text through the pipeline, and while Format-* cmdlets such as Format-Table emit objects as well, these objects no longer represent data, but formatting instructions for PowerShell’s output-formatting system – they serve no other purpose.

Therefore, simply replacing Format-Table with Select-Object solves your problem:

Get-WmiObject win32_logicaldisk -ComputerName sfuslt167 -Filter "drivetype=3" |
  Select-Object -Property deviceID,
    @{label="freespace(GB)";expression={$_.freespace / 1GB -as [int]}},
    @{label="Size(GB)";expression={$_.size / 1GB -as [int]}}, 
    @{label="SpaceLeft";expression={$_.freespace / $_.size * 100}} |
      Where-Object {$_.SpaceLeft -lt 10}

What the two cmdlets do have in common, however, is the ability to accept hashtable-based calculated properties (@{ label="..."; expression = { ... } }), as in your question.


As for what actually happened in your attempt:

Here’s a simplified example, using Format-Table:

PS> [pscustomobject] @{ freespace = 100; size = 1000 } |
      Format-Table @{label="SpaceLeft"; expression={$_.freespace / $_.size * 100}}

SpaceLeft
---------
       10

This looks just fine – and indeed that’s the purpose – producing a nice display representation.

In fact, substituting Select-Object for Format-Table results in the same display:

PS> [pscustomobject] @{ freespace = 100; size = 1000 } |
      Select-Object @{ label="SpaceLeft"; expression={$_.freespace / $_.size * 100} }

SpaceLeft
---------
       10

The reason is that when command output goes to the display, PowerShell implicitly, behind the scenes calls an appropriate Format-* cmdlet, which in this case is Format-Table.
In other words, the command above is equivalent to the following command:

PS> [pscustomobject] @{ freespace = 100; size = 1000 } |
      Select-Object @{label="SpaceLeft"; expression={$_.freespace / $_.size * 100}} |
        Format-Table 

SpaceLeft
---------
       10

For the logic behind which Format-* cmdlet is chosen when, see this answer.

However, instead of the (implicitly) applied Format-Table, you could have chosen a different formatting cmdlet, such as Format-List for list-style display that shows each property on its own line:

PS> [pscustomobject] @{ freespace = 100; size = 1000 } |
      Select-Object @{label="SpaceLeft"; expression={$_.freespace / $_.size * 100}} |
        Format-List

SpaceLeft : 10

However, when it comes to further processing, Select-Object and Format-Table are not created equal – only Select-Object is suitable:

Let’s look at what properties the output object(s) possess, using Get-Member -Type Properties, first with Select-Object:

PS> ([pscustomobject] @{ freespace = 100; size = 1000 } |
      Select-Object @{label="SpaceLeft"; expression={$_.freespace / $_.size * 100}} |
        Get-Member -Type Properties).Name
SpaceLeft

As expected, the output has one property, named SpaceLeft, and that’s what your Where-Object call can operate on.

Using Format-Table instead of Select-Object tells a different story:

PS> ([pscustomobject] @{ freespace = 100; size = 1000 } |
      Format-Table @{label="SpaceLeft"; expression={$_.freespace / $_.size * 100}} |
        Get-Member -Type Properties).Name
autosizeInfo
ClassId2e4f51ef21dd47e99d3c952918aff9cd
groupingEntry
pageFooterEntry
pageHeaderEntry
shapeInfo
ClassId2e4f51ef21dd47e99d3c952918aff9cd
groupingEntry
shapeInfo
ClassId2e4f51ef21dd47e99d3c952918aff9cd
formatEntryInfo
outOfBand
writeStream
ClassId2e4f51ef21dd47e99d3c952918aff9cd
groupingEntry
ClassId2e4f51ef21dd47e99d3c952918aff9cd
groupingEntry

It doesn’t really matter what these in part obscurely named properties specifically represent – all that matters is:

  • Their sole purpose is to be interpreted by PowerShell’s output-formatting system.

  • The selected / calculated properties passed to Format-Table are not present as such in the output.

    • That’s why your Where-Object call didn’t work as intended: $_.SpaceLeft referenced a non-existing property, so the expression evaluates to $null, and $null -lt 10 is always $true.

Irrespective of their input, Format-* cmdlets output instances of Microsoft.PowerShell.Commands.Internal.Format.* types that represent formatting instructions.

Leave a Comment