1

I was pointed at a question that suggests using Write-Output over Write-Host if I want commands to operate sequentially (as Write-Host doesn't put the output on the pipeline while other commands do, which means that Write-Host output can happen before or after other commands that are on the pipeline leading to very messy output): command execution ordering inside a PowerShell scriptblock

Following this advice, I made a simple function using Write-Output to mimic Write-Host's colour syntax. For ordering, this works well, so that output from commands is now sequential, but the colour output is now awful with Write-Output so that if I use any BackgroundColor at all, the results are sprayed over the screen in very ugly ways. Write-Host was tight and reliable with colour output and didn't bleed into other parts of the console so using Write-Output with colour makes for some really ugly/clunky console output.

Do I need to reset $host.ui in some way before leaving the function, or can anyone suggest a way to modify this function so that the colours remain tight to the areas that they are required for and don't bleed to other console areas?

function Write-Color ($text, $ForegroundColor, $BackgroundColor) {
    $defaultFore = $host.ui.RawUI.ForegroundColor
    $defaultBack = $host.ui.RawUI.BackgroundColor
    if ($ForegroundColor -ne $null) { $host.ui.RawUI.ForegroundColor = $ForegroundColor }
    if ($BackgroundColor -ne $null) { $host.ui.RawUI.BackgroundColor = $BackgroundColor }
    Write-Output $text
    $host.ui.RawUI.ForegroundColor = $defaultFore
    $host.ui.RawUI.BackgroundColor = $defaultBack
}

e.g.

Write-Color "The dog sat on the couch" -ForegroundColor Red -BackgroundColor White
YorSubs
  • 3,194
  • 7
  • 37
  • 60
  • 2
    You were pointed to the wrong post (it is only tangentially related to your problem - see [my recent answer](https://stackoverflow.com/a/59220711/45375) there). For colored output you do need `Write-Host` or else you'd have to embed VT (Virtual Terminal) escape sequences in output strings sent to the success stream / passed to `Write-Output`, but then they become _data_. To solve the sequencing problem between `Write-Host` and `Write-Output` / success-stream output, see https://stackoverflow.com/a/43691123/45375, but note that it requires sending _data_ output _to the screen_. – mklement0 Dec 06 '19 at 22:40
  • Thanks very much. If you post this as an answer I will mark it as the answer. I don't like that Microsoft created this mess. There should have been some clear differentiation: Write-Output / Write-Host, it's not clear to me that Write-Host is going to act in such a weird way. This is a scripting language and we should expect things to be sequential, line 1 fully completes, then line 2 etc. I would have no problem with some special function that does something else, but for most people, most of the time, they just want command 1 to execute before command 2. – YorSubs Dec 07 '19 at 05:54
  • I think in the past, I came across this "never-ever-use-Write-Host-for-no-reason-ever" but I stopped using PowerShell much for some years and have come back to it so have to go through this pain again (thanks Microsoft!). ok, the language is great, it's so much easier to slice and dice information than other languages so I can forgive things like this but what is your advice? Should I a) use Write-Output (with its horrible inability to do background colours), or b) use Write-Host like "Write-Host "123" -F Red -B White | Out-Host" and that will guarantee sequentiality? – YorSubs Dec 07 '19 at 05:58
  • Going to your second link in the above, I tried using " | Out-Host" as suggested. So taking your example from the first link `Write-Host '------- 1'; Get-Item / | Select-Object FullName; Write-Host '------- 2'` (This is non-sequential!). So I added `| Out-Host` to everything (wrongly) thinking this would fix it. `Write-Host '------- 1' | Out-Host; Get-Item / | Select-Object FullName; Write-Host '------- 2' | Out-Host` This is (also!) non-sequential! I'm stumped... I just want to reliably output colour in a *sequential* manner. Is it possible? I'd really appreciate an answer to that... – YorSubs Dec 07 '19 at 06:26
  • You need to apply `Out-Host` to the _non_-`Write-Host` statements; `Write-Host` already writes to the host. Therefore: `Write-Host '------- 1'; Get-Item / | Select-Object FullName | Out-Host; Write-Host '------- 2'` – mklement0 Dec 07 '19 at 08:20
  • Got it, thanks. I though that *non-* `Write-Host` statements refered to `Write-Output` statements. You are referring to the pipeline Cmdlets like `Select-Object` in the above and by doing that it's now working for me. There are times when I want to write scripts that are not "purist pipeline PowerShell" (in fact Snover himself completely contradicts his own arguments in the "if you are using Write-Host you are doing it wrong" article). There are times that a tool needs to display information to the user in a sequential manner. The "pipeline" is not the be all and end all. Thanks! – YorSubs Dec 07 '19 at 11:08
  • I've been hunting around for various color compatible text output that are "pipeline compliant" as opposed to making pipeline commands "Out-Host" compliant and was wondering your thoughts on this mklement0. The function in the OP (using $host.ui.RawUi") is not good I think so looking at various options, I found this on PowerShell Gallery and was wondering your view of this as an alternative? Do you think that this is a better approach overall to ever using Write-Host (it doesn't bleed color in the way that the $host.ui does)? https://www.powershellgallery.com/packages?q=write-coloredoutput – YorSubs Dec 07 '19 at 14:39

1 Answers1

1

Write-Host is the right tool for producing (possibly colored) for-display output - as opposed to outputting data via PowerShell's success output stream, via cmdlet calls and expressions, (optionally via explicit Write-Output calls, but that's rarely needed).

This answer explains that if you mix Write-Host and success-stream output, in PowerShell v5+ what prints to the console can appear out of order.

This is a side effect of implicitly applied tabular formatting situationally being asynchronous, in an effort to collect some data before printing output so as to determine suitable column width. It happens only for output types that (a) don't have predefined format data, and (b) have 4 or fewer properties (because types with more properties default to list formatting).

The problematic behavior is discussed in GitHub issue #4594; while there's still hope for a solution, there has been no activity in a long time.

There is no good solution to this problem as of PowerShell 7.0:

There are two - suboptimal - workarounds:

  • (a) Pipe individual commands that trigger the asynchronous behavior to ... | Out-Host.

    • E.g., in the following command the command with the Select-Object call must be sent to Out-Host so as to appear correctly between the two Write-Host calls on the screen:

      Write-Host '------- 1'
      Get-Item . | Select-Object FullName | Out-Host
      Write-Host '------- 2'
      
    • Downside: Using Out-Host means you lose the ability to capture or redirect the command's output, because it is sent directly to the host (display). Additionally, it is cumbersome to (a) know what commands trigger the problem and (b) to remember to apply the workaround to each.

  • (b) Replace Write-Host calls with sending strings with embedded VT (Virtual Terminal) escape sequences (for coloring) to the success output stream.

    • Note: Requires Windows PowerShell v5.1 on Windows 10 or PowerShell [Core] v6+

    • Downside: The (colored) strings become part of the code's data output and are therefore included when you capture / redirect output.

          # Windows PowerShell 5.1: [char] 0x1b produces an ESC char.
          $green = [char] 0x1b + '[32m'; $reset = [char] 0x1b + '[m'
          # Print "green" in green.
          "It ain't easy being ${green}green${reset}."
      
          # PowerShell 6+: `e can be used inside "..." for ESC.
          $yellow = "`e[33m"; $reset = "`e[m"
          # Print "yellow" in yellow.
          "They call me mellow ${yellow}yellow${reset}."
      
      • The fact that these strings contain ESC chars. could actually be used to filter out for-display strings from the data stream (assuming your actual data doesn't contain ESC chars.), along the lines of ... | Where-Object { -not ($_ -is [string] -and $_ -match '\e') }

Embedding VT escape sequences allows you to selectively color parts of your strings.

Achieving the same effect with Write-Host would require multiple calls with -NoNewline.

Third-party cmdlet (module) Write-ColoredOutput emulates Write-Host's syntax and uses the [console] type's attributes to turn coloring on and off, while sending the string to the success output stream.
This works well for writing an entire string in a given color, but you cannot piece together differently colored parts on a single line, because each string individually written to the success output stream invariably prints on its own line.

If you wanted a convenience wrapper around embedding VT sequences directly in strings, you could adapt the Write-HostColored function from this answer, by replacing the Write-Host calls that happen behind the scenes with VT sequences.

mklement0
  • 382,024
  • 64
  • 607
  • 775