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:
Irrespective of their input, Format-*
cmdlets output instances of Microsoft.PowerShell.Commands.Internal.Format.*
types that represent formatting instructions.