Powershell: format-table should at least warn when it doesn't display properties

Created on 26 Sep 2018  路  15Comments  路  Source: PowerShell/PowerShell

I think format-table should at least give a warning, if more than one object type (EDIT: set of properties) is piped through it, and it only shows the properties from the first object type. This often happens implicitly when running a script. For example, in this script, the phone property never appears in the output (unless piped through format-list).

Steps to reproduce

[pscustomobject]@{name='joe';address='home'};
[pscustomobject]@{phone='1'}

Expected behavior

name address
---- -------
joe  home

phone
-----
1

Actual behavior

name address
---- -------
joe  home

Environment data

> $PSVersionTable
Name                           Value
----                           -----
PSVersion                      6.1.0
PSEdition                      Core
GitCommitId                    6.1.0
OS                             Microsoft Windows 10.0.16299
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Area-Cmdlets Issue-Discussion

Most helpful comment

() is for evaluating a single expression. $() is for evaluating one or more expressions. @() will also evaluate one or more expressions but in the case that the expression evals to a scalar value, @() will ensure that scalar value is wrapped in an array.

All 15 comments

This has been brought up a few times in a couple different forms; most recent being #7839 where some of the more auxiliary issues around the handling of different types of objects being output together are discussed.

In short, yes, there should at least be a warning, and I wouldn't be averse to implementing an experimental feature where it forces any differently typed object in the pipeline to start a completely new table. It already has this sort of behaviour with -GroupBy in any case, so it would be a matter of doing something similar-ish (though I don't think it should by default change the displayed order of items as that is disingenuous and does not represent the objects properly).

@jszabo98

more than one object type is piped through it

In your example, there is, in fact, only one type of object going through the pipeline. In the cases where there actually are multiple types of objects e.g.

 $((get-process | select -first 1) ; get-date ; (get-process | select -first 1)) | ft

the secondary objects are rendered as lists.

@vexx32 Please consider the following scenario: one of the most commonly used commands is Get-Childitem/dir/ls. This command returns heterogeneous object types: FileInfo and DirectoryInfo depending on the contents of the directory. DirectoryInfo doesn't have a Length property but FileInfo does. Should we be warning on that? That would mean that pretty much every time you type dir you'd get a bunch of warnings. Not a good experience. Alternatively, should we restart the table when we switch from object type to the other? If it happens once that might be OK but if it's happening every other record then you'd have poor experience, especially with a recursive dir.

Ultimately the current behavior of formatting and output is the result of a lot of testing focused on providing a good user experience (most of the time). It's certainly not perfect but it could be made much worse if we aren't careful.

@BrucePay Given that both those objects inherit from FileSystemInfo, I think that you'd generally use that as your formatting base. Obviously that has a custom format file because it's a core cmdlet, but in general that pattern wouldn't be terrible (not warning on objects that inherit from a common type). Granted, I'm not sure how you'd code for that at the moment, but it seems doable.

I agree that the current situation is... probably the best of a bad lot, in some ways, but just because it could be made worse is no reason to avoid looking at ways to improve it as well. Some experimentation is definitely required, but I think there's definitely a better solution out there. 馃槃

I was hoping this would be a workaround, but it didn't work.

[pscustomobject]@{name='me';address='here';PSTypeName='type1'} [pscustomobject]@{phone='111';PSTypeName='type2'}

Format-Table doesn't seem to check type names, I'm afraid, only the runtime type. And even if it did, the current behaviour doesn't care about different types in the stream, generally speaking, if they're all piped in at the same time. You'd have to call Format-Table on each object separately.

Hmm, not this way either. Why does format-list work so well?

`
class type1 {$name; $address}
class type2 {$phone}

[type1]@{name='me'; address='here'}
[type2]@{phone=111}
`

Format-List doesn't need to care about which properties each object has. Each object gets its own property labels.

@jszabo98 Beyond setting the TypeNames property, you need to create an entry in the formatting database for your type. Now in fact, as long as there is a type with a formatting entry first in the list of objects, everything gets printed out "fine":

PSCore (1:270) >  Get-Date; [pscustomobject]@{name='joe';address='home'}; [pscustomobject]@{phone='1'}

Wednesday, September 26, 2018 2:41:16 PM

name    : joe
address : home

phone : 1

This actually looks like a bug to me. There is code that checks to see if there are out-of-band objects in the stream but only takes action if there is an active View. A View is established by having an object with a View defined in a format ps1xml file upstream in the pipeline. (including Views for PSCustomObject's with "fake" type names.). I would think that if there is no active view, one should be synthesized based on the first object encountered...

@vexx32

probably the best of a bad lot, in some ways, but just because it could be made worse is no reason to avoid looking at ways to improve it as well

No - please - by all means experiment! That's why we have the experimental flag. I just wanted to provide some context on why things work the way they do. We considered an lot of scenarios in building formatting and output (and PowerShell in general) and it's (hopefully) helpful to know some of the specific things we looked at.

@BrucePay I do appreciate it, and I will happily have a good stab at it at some point during Hacktober, I think.

And yeah, I agree; there are some cases where it is able to figure out that something isn't right and it seems like it defaults to spitting them out with Format-List, which is great, but the cases where it quietly hides things are a bit of a pain point. Is there something we could look at where, perhaps, situations like this are handled kind of like this?

# mock up
name   address
-----  -------
joe    home
Steve  work
--------------
Name    :  Bill
Address :  Florida
Phone   :  (772) 845-1189
--------------
James  work
Joan   home

What would be thoroughly useful, I think, is for this display to be collapsible to hide the interloper objects (perhaps even by default, with a marker to be clicked on or notifying the user to press a key to show the interlopers).

That is a very display-only sort of solution, though, but that's really where this tends to hurt the most -- at least if users are aware there're interloper objects of different types than they might expect they can filter them out as needed. Not being shown them at all can make it very difficult to work with!

Your example (excluding the dashes) is more or less what we do today if we have type names and format data. (I assume that the 'Bill' record is actually a record with three fields and not inferred by joining two records.) In the absence of type names and information about how to format objects, a set of heuristics are used with questionable results. If there are fewer than 5 properties on the object then table formatting is used with the set of properties from the first object used as column headers. The formatter will then attempt to retrieve those properties from subsequent typeless objects. Absent properties are not treated as errors (like Length with dir) and additional properties are ignored (since objects typically have a large number of properties). Beyond the threshold of 5 properties, list formatting is used for all objects so the 'hidden object' problem goes away:

PSCore (1:303) >  [pscustomobject]@{name='joe';address='home'; a=1;b=2;c=3};  [pscustomobject]@{phone='1'}

name    : joe
address : home
a       : 1
b       : 2
c       : 3

phone : 1

If you have typenames and format data, you get:

PSCore (1:306) >  [pscustomobject]@{PSTypeName = "Address"; name='joe';address='home'};  [pscustomobject]@{phone='1'}

Name       Address
----       -------
joe        home

phone : 1

If we changed the formatter to synthasize a View for the first object if it was typeless, then you would get the above without having to provide additional information. That would fix this specific problem.

At a glance, that seems to be a relatively pragmatic and minimal solution, really. I'm game. Not sure how that'd go, I'm not at all familiar with how those objects are constructed and handled, but I'm sure I can figure that part out relatively easily. 馃槃

$((get-process | select -first 1) ; get-date ; (get-process | select -first 1)) | ft

You know, I thought $( ) was just for inside double quotes. But then I had trouble running (get-process; get-date) to work, with the semicolon. I see people put $( ), when ( ) would work elsewhere.

The $ isn't required in the vast majority of common use cases, but it doesn't do any harm.

Basically it tells PS that "this is a complete subexpression" so that even the line breaks (semicolons) don't terminate the expression itself and are self-contained. That's probably the only case outside of inserting expressions in strings that I can think of, really.

() is for evaluating a single expression. $() is for evaluating one or more expressions. @() will also evaluate one or more expressions but in the case that the expression evals to a scalar value, @() will ensure that scalar value is wrapped in an array.

Was this page helpful?
0 / 5 - 0 ratings