Yahav Boneh
Yahav Boneh

Reputation: 110

PowerShell multiple statements one-liner messes up stdout order

I tried to make a one liner that executes a command and than prints out a message, for example:

dir; echo 1

which outputs:

PS C:\Users\Administrator\Documents> dir; echo 1


    Directory: C:\Users\Administrator\Documents


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        8/18/2021   3:27 PM                a
d-----        8/18/2021   3:27 PM                vdb.1_1.dir
-a----        8/30/2021   2:48 PM             12 a.txt
-a----        8/30/2021   2:54 PM           2044 dir.txt
-a----        8/30/2021  10:31 AM              8 hey.txt
1


PS C:\Users\Administrator\Documents>

When running only dir, there are two blank lines at the end:

PS C:\Users\Administrator\Documents> dir


    Directory: C:\Users\Administrator\Documents


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        8/18/2021   3:27 PM                a
d-----        8/18/2021   3:27 PM                vdb.1_1.dir
-a----        8/30/2021   2:48 PM             12 a.txt
-a----        8/30/2021   2:54 PM           2044 dir.txt
-a----        8/30/2021  10:31 AM              8 hey.txt


PS C:\Users\Administrator\Documents>

But the combination of two commands seems to ruin the order (The '1' is printed before the two blank lines).

When I ran other versions the problem did not occur, even when the first command printed blank lines:

PS C:\Users\Administrator\Documents> echo 0; echo 1
0
1
PS C:\Users\Administrator\Documents>
PS C:\Users\Administrator\Documents> echo hello`n`n; echo 1
hello


1
PS C:\Users\Administrator\Documents>

What causes it? Does it happen with other commands too (besides ls, Get-ChildItem or other aliases)? How can I bypass that (the output is supposed to go to a script that assumes the message is at the end)?

** I'm running Windows server 2019, PowerShell version 5.1.17763.316

Upvotes: 1

Views: 197

Answers (1)

codewario
codewario

Reputation: 21468

It's the display-formatter output. What you see on screen is the default visual representation of an object's value, not exactly what that object's value is. Simpler, more primitive types might simply display True, 1, or the value of a string when the variable displays to the console. More complex types will show a formatted output, often (but not always) formatted as a table or a grouped list of properties per object.

It's recommended to operate on objects and member values themselves instead of the formatted output that gets written to console, but there are some cases (e.g. regression testing your own class) where the latter is desirable. If you want to do this, you first need to pipe the cmdlet to Out-String, and then you can inspect the output.

$gciFormatOutput = Get-ChildItem | Out-String

# Here you can inspect the formatted output of Get-ChildItem how you need

However, parsing this to get, say, the full path to the files in a directory is not recommended. Most of the time you'd want to do something like this:

# -File omits directories from the final output in this example
$filePathsInFolder = ( Get-ChildItem $someDir -File ).FullName

( Get-ChildItem $someDir -File ) runs first because of order of operations; expressions nested in parentheses () are evaluated first. We can then directly invoke the FullName property of any returned files from the previous expression and we get a list of all of the files in that directory. This is a much cleaner approach than parsing the formatted display output of Get-ChildItem, and showcases the power of working with objects over strings in PowerShell.


However, the way you're talking, it sounds like you are concerned with inconsistency when evaluating the output of external commands, for example, ping.exe, tracert.exe, etc. (these are poor examples since PS has built-in cmdlets for these functions). If you need to inspect the result of a command's output, you will get one or more strings returned (one string per line of output by default) and you can be certain that the command will return exactly the same lines of output. Consider this ping example:

$output = & ping google.com -n 1
"Ping Succeeded: $($output[2] -match '^Reply'")

We ping google.com, the third line if successful will begin with Reply if the ping succeeded. This is a rudimentary example and one that can be better done with PowerShell cmdlets but I hope you get the gist of processing command output.

Worthy to note: all external commands (anything that isn't part of PowerShell itself) returns strings for the output; you don't have to worry about these being transformed by PowerShell in some funky, unexpected way.

Upvotes: 3

Related Questions