Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the default method invoked on an object by Write-Output in Powershell?

Tags:

powershell

I've found a weird behavior using powershell.

The output of a quoted object alone differs from the output of the ToString() method invoked on the same object.

what is the method or property invoked in

Write-Output "$a"

This is the directory I'm running the example

> PS C:\temp\testps> DIR


    Directory: C:\temp\testps


Mode                LastWriteTime     Length Name                                       
----                -------------     ------ ----                                       
-a---        03/12/2010     11.21          8 1.txt                                      
-a---        03/12/2010     11.21          8 2.txt                                      
-a---        03/12/2010     11.21          8 3.txt                                      

Here I assign the file collection to the $q object

PS C:\temp\testps> $q  = Get-ChildItem . "*.txt"

Now I want to print the file names , but I receive an unexpected output

PS C:\temp\testps> foreach ($a in $q) {Write-Output $a}


    Directory: C:\temp\testps


Mode                LastWriteTime     Length Name                                       
----                -------------     ------ ----                                       
-a---        03/12/2010     11.21          8 1.txt                                      
-a---        03/12/2010     11.21          8 2.txt                                      
-a---        03/12/2010     11.21          8 3.txt                                      

Putting quotes around the object name shows the right behavior

PS C:\temp\testps> foreach ($a in $q) {Write-Output "$a"}
1.txt
2.txt
3.txt

This is the output for just the first object. It looks like the whole DIR command output till the first filename. Unparsed.

PS C:\temp\testps> foreach ($a in $q) {Write-Output $a;break}


    Directory: C:\temp\testps


Mode                LastWriteTime     Length Name                                       
----                -------------     ------ ----                                       
-a---        03/12/2010     11.21          8 1.txt                                      

The ToString() method behaves like the quoted object.

PS C:\temp\testps> foreach ($a in $q) {Write-Output $a.ToString()}
1.txt
2.txt
3.txt
like image 334
sergiom Avatar asked Dec 28 '22 04:12

sergiom


2 Answers

In the first case Write-Output $a, the object (System.IO.FileInfo) is formatted by the PowerShell formatting engine based on the format data stored in $pshome\FileSystem.format.ps1xml. That is, what is really executing is:

Write-Output $a | Out-Default

Out-Default is implicitly executed at the end of the pipeline and it is the cmdlet that does the formatting based on the formatting data.

In the "$a" case, the ToString() representation is used.

like image 183
Keith Hill Avatar answered Dec 30 '22 18:12

Keith Hill


Write-Output is really just an explicit way to send the object you give it to the next command in the pipleine. Since you don't capture the output, PowerShell uses the default formatting for the object. This is because Write-Output is not actually sending a string to stdout, it is returning an object to the pipeline. The object in the first case is an array of FileSystemInfo objects.

When you return an array in PowerShell without capturing the output, PowerShell attempts to iterate over the array and use its default formatting spec to display each object as text on stdout.

So, in order of the examples your provided, starting with dir:

  1. dir returns System.Array[System.IO.FileSystemInfo]. Since the array is not captured by any code, PowerShell uses its default format for FileSystemInfo to display the contents of the array.

  2. the same thing happens as above, but it's you that performs the iteration. You're actually writing your own array back to the pipeline, reconstructing the same object that was returned by get-childitem in the first place. Since, once again, no code is capturing the output array object, PowerShell displays it just like in the first case. It's as if you wrote:

    PS C:\temp\testps> $q  = Get-ChildItem . "*.txt"
    PS C:\temp\testps> $children = foreach ($a in $q) {Write-Output $a}
    PS C:\temp\testps> $children
    

    Hopefully this makes it clear that your example is functionally equivalent to your first example.

  3. Here, you're converting each object in the array to a string by placing it in quotes (which tells PowerShell to call $a.tostring()) before returning the result array. So this time, you're returning an array of strings to the pipeline. Since you don't capture that array, PowerShell does its default text output to the console, which in the case of a string, is just the text of the string.

  4. This is similar to cases 1 and 2 in that PowerShell is using its default output format for a FileSystemInfo object.

  5. This does the same as 3 because you're returning an array of strings again. The only difference is semantic... you're calling ToString() explicitly rather than implicitly like when you place the object in quotes.

For more information on default formatting, check out help about_format

like image 36
Robert S Ciaccio Avatar answered Dec 30 '22 17:12

Robert S Ciaccio