powershellget-childitempowershell-5.1new-item

Get-ChildItem responds differently when New-Item output is sent to null


Why do these two code snippets behave differently? Is this a bug? (PSVersion = 5.1.22621.2506, PSEdition = Desktop)

## Code Snippet #1
rmdir -recurse org
mkdir org\dir1
Get-ChildItem org -Directory  | foreach {   $_.GetType() }

vs

## Code Snippet #2
rmdir -recurse org
mkdir org\dir1 | Out-Null
Get-ChildItem org -Directory  | foreach {   $_.GetType() }

The only difference between these two is that in snippet 2, the output of mkdir is piped to Out-Null. This second snippet (and its variants like $null = mkdir org/dir1) work as I would expect:

Get-ChildItem org -Directory  | foreach {   $_.GetType() }

IsPublic IsSerial Name                                     BaseType                                                                  
-------- -------- ----                                     --------                                                                  
True     True     DirectoryInfo                            System.IO.FileSystemInfo 

But when mkdir output is not captured (as in snippet 1), Get-ChildItem does something unexpected:

Get-ChildItem org -Directory  | foreach {   $_.GetType() }


    Directory: C:\Users\Phil\ps\as1\org


Mode                 LastWriteTime         Length Name                                                             
----                 -------------         ------ ----
d-----         4/10/2024  12:56 AM                dir1                                  

Module                     : CommonLanguageRuntimeLibrary
Assembly                   : mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
TypeHandle                 : System.RuntimeTypeHandle
DeclaringMethod            : 
BaseType                   : System.IO.FileSystemInfo
UnderlyingSystemType       : System.IO.DirectoryInfo
FullName                   : System.IO.DirectoryInfo
AssemblyQualifiedName      : System.IO.DirectoryInfo, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
[: : :]

The same results are seen if you use New-Item instead of mkdir. I don't understand why (or even how) a directory structure can be different depending upon whether the output was captured during its creation.


Solution

  • The behavior - which is only a display problem - may be surprising, but it is by design:

    If the first output object in a pipeline triggers table-based display formatting based on explicitly defined formatting definitions (see below for why that matters), subsequent output objects of different type are coerced to list formatting.[1]

    Specifically, mkdir's output object - a System.IO.DirectoryInfo instance - triggered Format-Table formatting, because a table view (with predefined columns) is defined as the default view in the formatting definitions associated with that type.

    Therefore, the subsequent System.Type instances output by the .GetType() calls in your Get-ChildItem were implicitly forced to use Format-List formatting.

    At the expense of producing data output from your script, you can apply explicit formatting to each command; in the simplest case, pipe to Out-Host, which applies each command's default formatting in isolation, and sends it directly to the host, preventing it from being captured; alternatively, use Format-* cmdlet calls explicitly; while they do produce output that can be captured, it isn't data anymore - it is objects representing formatting instructions (which is why Format-* cmdlets should only ever be used for to-display output).


    Presumably, the underlying design rationale for this behavior is:


    [1] Strictly speaking, if a given object has associated formatting data that defines a custom view as its default, the latter is used; an example is Get-Date's output.

    [2] For a given object, you can use Get-FormatData to test whether its type has associated formatting definitions; for instance (non-empty output implies the presence of definitions):
    $obj = Get-Item .; Get-FormatData -TypeName $obj.GetType().FullName