out-string behaviour and strings in general

I am still not clear as to how strings work in powershell.
The commands below are for educational purposes, hence they look contrived.

If I do
echo “a b” | ls ls shows the contents of folder “a b”.
But If I do
echo “a b” | out-string | ls It throws an error
while adding -stream to out-string make it work. The documentation says this
"By default, Out-String accumulates the strings and returns them as a single string, but you can use the stream parameter to direct Out-String to return one string at a time. "
It seems the opposite. Without -Stream it should be one string and yet ls is interpreting it as separate strings.

First, stop using Echo. It doesn’t (maybe) do what you think. Try Write instead (write-output).

And, Out-String is designed to take objects and output a string representation. If you have a string, there’s no need to convert it to a string. It might be that the contrived example is obscuring your point of confusion?

Keep in mind this isn’t Cmd.exe, it’s an object oriented shell. There’s a lot going on under the hood that you may not realize, and that can cause unexpected behavior.

For example, “a b” is a single string, not two strings. A command that returns multiple objects would be converted to one string without -stream, and each object would be a string with -stream. This relates to the PowerShell pipeline, and how it streams objects one at a time.

Out-String is designed for a specific purpose, and that isn’t really to feed other commands. Out- commands should usually be the last command on the line. Perhaps a different example to approach your question?

(And I should rather say that Echo is perhaps misleading; it’s an unusual command to see in PowerShell. You should be using Write-Host or Write-Output, which are clearer about what they’re doing. Echo kind of implies one, but it does the other; not knowing your knowledge level, I don’t know which you intended.)

just to be clear, echo is an alias to write-output, right?(at least get-alias says so)
The reason I was being a complete fool with out-string was to understand if cmdlets could take plain strings as arguments, instead of the usual objects.
If “a b” is a single string, why does out-string piped to get-childitem throw an error, while without out-string- it works? The obvious guess is that out-string does something weird to the string to make it not acceptable by get-childitem. The error is below

ls : Illegal characters in path.
At line:1 char:28

  • echo “a b” | Out-String | ls
  •                        ~~
    
    • CategoryInfo : InvalidArgument: (C:\Users\garegin\a b
      :String) [Get-ChildItem], ArgumentException
    • FullyQualifiedErrorId : ItemExistsArgumentError,Microsoft.PowerShell.Commands.GetChildItemCommand

ls : Cannot find path 'C:\Users\garegin\a b
’ because it does not exist.
At line:1 char:28

  • echo “a b” | Out-String | ls
  •                        ~~
    
    • CategoryInfo : ObjectNotFound: (C:\Users\garegin\a b
      :String) [Get-ChildItem], ItemNotFoundException
    • FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

Take a look at this:

PS D:> (“a b”).Length
3
PS D:> (“a b” | Out-string -stream).Length
3
PS D:> (“a b” | Out-string).Length
5
PS D:> (“a b” | Out-string).ToCharArray() | %{ [int]$_ }
97
32
98
13
10

You See ?
Out-string Add Carriage return to string because of its nature - [help]“Sends objects to the host as a series of strings.

Don’t mix host oriented cmdlets with others and you do not get such problems

btw, it you do not have ‘a b’ folder you can see difference in error messages:

PS C:> ‘a b’ | ls
ls : Cannot find path ‘C:\a b’ because it does not exist.
At line:1 char:9

  • ‘a b’ | ls
  •     ~~
    
    • CategoryInfo : ObjectNotFound: (C:\a b:String) [Get-ChildItem], ItemNotFoundException
    • FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

PS C:> ‘a b’ | out-string | ls
ls : Illegal characters in path.
At line:1 char:22

  • ‘a b’ | out-string | ls
  •                  ~~
    
    • CategoryInfo : InvalidArgument: (C:\a b
      :String)
      [Get-ChildItem], ArgumentException
    • FullyQualifiedErrorId : ItemExistsArgumentError,Microsoft.PowerShell.Commands.GetChildItemCommand

ls : Cannot find path 'C:\a b
’ because
it does not exist.
At line:1 char:22

  • ‘a b’ | out-string | ls
  •                  ~~
    
    • CategoryInfo : ObjectNotFound: (C:\a b
      :String) [Get-ChildItem], ItemNotFoundException
    • FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

To be clear, there’s no such thing as a “plain string.” Strings are also objects.

And I wasn’t calling you foolish; I didn’t understand where the question was going, and I didn’t understand you background knowledge or experience. I apologize if it seemed rude - I just didn’t know where to start.

I don’t have access to a shell right now, but have you tried a Trace-Command that includes pipeline binding? It’d reveal some more details about what’s happening in both instances, and comparing the two might be useful to understand what the shells doing.

"I don’t have access to a shell right now, but have you tried a Trace-Command that includes pipeline binding? It’d reveal some more details about what’s happening in both instances, and comparing the two might be useful to understand what the shells doing. "

Thanks. I also discovered the .count property to see how many objects are in the pipe.