What causes the output of select-object to be truncated?

I have the following silly PowerShell script:

$username = 'rny'

$null = mkdir "c:\Users\$username\YYY"
$null = mkdir "c:\Users\$username\YYY\TODO"
$null = mkdir "c:\Users\$username\YYY\TODO\2021-12-22_Foo-bar-baz-etc"

$files = "C:\Users\$username\one-two-three-four.sql.wxyz",
         "C:\Users\$username\YYY\TODO\2021-11_29_abcdefghijklmnop.wxyz",
         "C:\Users\$username\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz"

foreach ($file in $files) {
   $null = new-item $file
}

  Get-ChildItem . -errorAction silentlyContinue -recurse -filter *.wxyz | select-object fullName 

foreach ($file in $files) {
    remove-item  -literalPath $file
}

rmdir "c:\Users\$username\YYY\TODO\2021-12-22_Foo-bar-baz-etc"
rmdir "c:\Users\$username\YYY\TODO"
rmdir "c:\Users\$username\YYY"

When I execute it, the output of the get-childItem ... | select-object pipeline is truncated:

FullName
--------
C:\Users\rny\one-two-three-four.sql.wxyz
C:\Users\rny\YYY\TODO\2021-11_29_abcdefghijklmnop.wxyz
C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\an...

Note especially the last line. This behaviour was noted elsewhere on SuperUser and the accepted answer is to pipe the output into format-table with -autoSize. So far, so good.

However, If I comment the second file in the assignment of the $files array like so

$files = "C:\Users\$username\one-two-three-four.sql.wxyz",
#        "C:\Users\$username\YYY\TODO\2021-11_29_abcdefghijklmnop.wxyz",
         "C:\Users\$username\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz"

the output is not truncated anymore:

FullName
--------
C:\Users\rny\one-two-three-four.sql.wxyz
C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz

This puzzles me because the name of the file that was truncated is now fully visible and I have no explanation for this.

So, what exactly causes the truncation of the file in one case and not in the other case?


This isn't so much to do with Select-Object per se - it's more to do with how PowerShell converts values into string representations, and specifically in this case how it does that when it displays uncaptured output from cmdlets on the console.

PowerShell (Windows and Core) has a bunch of preconfigured "views" that define how some built-in types are rendered - e.g. whether they use Format-List or Format-Table, what properties to display, and in the case of tables, how wide to display each column - see about_Format.ps1xml.

For other types, PowerShell tries to make a best-guess on the fly. To do that it waits for the first N items in arrive from the input to make a decision on the formatting rules to apply. I can't find any definitive documentation that says how many items PowerShell waits for, so that might be a good follow-up question :-).

And you can obviously override these defaults by passing formatting parameters for Format-Table and Format-List.

In your case the top-level script has received pipeline output containing an array of PSCustomObject objects (i.e. the output from Select-Object) and it's decided to show them in a table with a column for the FullName property.

Example 1

In your first example, it's looked at the first two PSCustomObject items and decided to make the FullName column 54 characters wide since that's the length of C:\Users\rny\YYY\TODO\2021-11_29_abcdefghijklmnop.wxyz, and the third item gets truncated to that same width (if you include the ...) because it wasn't included in the decision-making process for column widths.

FullName
--------
C:\Users\rny\one-two-three-four.sql.wxyz
C:\Users\rny\YYY\TODO\2021-11_29_abcdefghijklmnop.wxyz
C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\an...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^| 54 characters

Example 2

In your second example, PowerShell sees the longest FullName property in the first couple of PSCustomObjects is C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz and so uses a column width of 70.

FullName
--------
C:\Users\rny\one-two-three-four.sql.wxyz
C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^| 70 characters

Example 3

Finally, if you do what @notjustme suggests in the comments and add -ExpandProperty FullName onto Select-Object you get an array of string values instead of an array of PSCustomObjects which is why you might see PowerShell apply different formatting rules - you don't get a FullName header for example because the values are strings not objects with properties, and it's using Format-List instead of Format-Table.

C:\Users\rny\one-two-three-four.sql.wxyz
C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz

To add some background to mcclayton's helpful answer:

Specifically, you're seeing the effects of the infamous 300-msec. delay built into Format-Table formatting, which PowerShell implicitly applies to instances of .NET types that have 4 or fewer properties and do not have explicit formatting data associated with them.

See this answer for details (which is given in the context of a different symptom of the same problem, namely unexpected output ordering), but the short of it is: The delay is used to infer suitable column widths from the specific property values received within the delay period.

This means that objects with property values received after the 300-msec. delay may be truncated in their column display if their values happen to be wider than the widest among the values received during the delay period.

Specifically, your symptom implies that only the first two objects were received within the delay period, and that the longer among the two property values then locked in the column width; when the third object was received later, the column width was already locked in, and the longer value was truncated (indicated with trailing ... in Windows PowerShell (3 . chars.) and in PowerShell (Core) 7+ (single char))

The only way to avoid truncating is to know the max. column width ahead of time and pass it to an explicit Format-Table call -
notably, this prevents using the output as data.
See below.


Here's a simple way to provoke the problem:

Note: The Select-Object calls below aren't strictly needed, but are provided for symmetry with the question.

# Create blocks of two objects with strings of different length in their 
# .Prop value: 10 chars. vs. 100 chars.
$count = 10000 # How often to repeat each object in a row.
$objs = 
 (, [pscustomobject] @{ Prop = ('x' * 10) } * $count) + 
 (, [pscustomobject] @{ Prop = ('y' * 100) } * $count)

# Depending on the value of $count - which translates into how
# long it takes until the second block of objects starts emitting -
# truncation will occur or not.
$objs | Select-Object Prop

With blocks of 10,000 objects, I do see the truncation: it takes long enough for the first block - with the short property value - to lock in the width of the display column, causing the objects in the second block to be truncated:

Prop
----
xxxxxxxxxx
...
yyyyyyyyy…  # <- truncated, because width 10 was locked in during the delay
...

To prevent truncation, pass a calculated property to Format-Table specifying the max. width:

$objs | Select-Object Prop | Format-Table @{ n='Prop'; e='Prop'; width = 100 }