Ilya Afinogentov
Ilya Afinogentov

Reputation: 157

Powershell Different color in strings with echo in array

I trying to write a script, which show iis pools state with a different color. And I can't understand why script coloring in one color all strings when I use echo. Here script:

    $pools = invoke-command -session $session -scriptblock {Import-Module WebAdministration; Get-ChildItem IIS:\AppPools | Where {$_.Name -like "*abc*"}}
    $poolsShow = $pools | Select-Object -Property name, state
    $poolsShow | ForEach-Object {
        if($_.state -eq "Started") { 
            $Host.UI.RawUI.ForegroundColor = "Green";
            echo $_;
            [Console]::ResetColor();
        }
        if($_.state -eq "Stopped") { 
            $Host.UI.RawUI.ForegroundColor = "Red";
            echo $_;
            [Console]::ResetColor();
        }
    }

It is work if I go through the $pools, but if I select name and state via Select-Object - all strings are coloring in the color of the last service. I have tried via Write-Host - and it's worked, but I didn't find a way, how to format output in one table with a headers only at first line and with the same width in every string.

Upvotes: 3

Views: 2085

Answers (2)

mklement0
mklement0

Reputation: 439597

To complement Santiago's helpful answer:

As for what you tried:

  • echo in PowerShell is a built-in alias for Write-Output, which does not print directly to the console - instead, it prints to the success output stream.

  • If the success output stream isn't captured or redirected in a given command, it is eventually printed to the console, after undergoing for-display formatting by PowerShell's formatting system.

  • Because your output objects have 4 or fewer properties, PowerShell applies tabular formatting by default; that is, the Format-Table cmdlet is implicitly used, which has a perhaps surprising implication:

    • So as to allow determining suitable column widths for the table, a 300-millisecond delay is introduced during which the objects are internally cached and analyzed.
    • While this behavior is helpful in principle, it has surprising side effects, notably in that direct-to-host output and output from other streams then can appear out of order; a simple example: [pscustomobject] @{ foo = 1 }; Write-Host 'Why am I printing first?? - see this answer for background information.
  • Therefore, the formatted table's rows only started printing after that delay, so your attempt to control their color one by one with ForEach-Object was ineffective.

    • As an aside: In PowerShell (Core) 7.2+ there's an additional consideration: formatted output now applies its own coloring by default, as controlled by the .OutputRendering property of the $PSStyle preference variable.
  • Santiago's answer bypasses this problem by using a calculated property to color individual property values rather than trying to control the coloring of the already-formatted representation of the object.


If you want a prepackaged, general-purpose solution, you can use the Out-HostColored function from this Gist (authored by me), which in your case would make the solution as simple as piping your objects to
Out-HostColored.ps1 @{ Started = 'Green'; Stopped = 'Red' }:

# Download and define function `Out-HostColored` on demand (will prompt).
# To be safe, inspect the source code at the specified URL first.
if (-not (Get-Command -ErrorAction Ignore Out-HostColored1)) {
  $gistUrl = 'https://gist.github.com/mklement0/243ea8297e7db0e1c03a67ce4b1e765d/raw/Out-HostColored.ps1'
  if ((Read-Host "`n====`n  OK to download and define function ``Out-HostColored```n  from Gist ${gistUrl}?`n=====`n(y/n)?").Trim() -notin 'y', 'yes') { Write-Warning 'Aborted.'; exit 2 }
  Invoke-RestMethod $gistUrl | Invoke-Expression 3>$null
  if (-not ${function:Out-HostColored}) { exit 2 }
}

# Emit sample objects and color parts of their formatted representation
# based on regex patterns.
0..5 | ForEach-Object {
  [pscustomobject]@{
    Name  = "Test $_"
    State = ('Started', 'Stopped')[$_ % 2]
  }
} | 
  Out-HostColored.ps1 @{ Started = 'Green'; Stopped = 'Red' }

Output:

screenshot

Add -WholeLine if you want to color matching lines in full.

  • The hashtable maps search text patterns to colors.

  • Whenever a pattern is found in the formatted representations of the input objects, it is colored using the specified color.

    • Note that this means that finding what to color is purely based on string parsing, not on OOP features (such as checking specific properties).
  • Note that the hashtable keys are regexes by default, unless you also specify -SimpleMatch.

    • Thus you could easily make the above more robust, if needed, such as replacing Started = with '\bStarted\b' = in order to only match full words.

Upvotes: 2

Santiago Squarzon
Santiago Squarzon

Reputation: 60683

You can take a similar approach as the one proposed in this answer, the only difference would be that the ANSI Escape Sequences are prepended to the property values of the objects created by Select-Object. Helpful answer provided by @mklement0 in the same question provides more details on this.

function status {
    $ansi = switch($args[0]) {
        Stopped { "$([char] 27)[91m" }
        Started { "$([char] 27)[32m" }
    }
    $ansi + $args[0]
}

Invoke-Command -Session $session -ScriptBlock {
    Import-Module WebAdministration
    Get-ChildItem IIS:\AppPools | Where-Object { $_.Name -like "*abc*" }
} | Select-Object Name, @{ N='Status'; E={ status $_.State }}

A demo using custom objects:

0..5 | ForEach-Object {
    [pscustomobject]@{
        Name   = "Test $_"
        Status = ('Started', 'Stopped')[$_ % 2]
    }
} | Select-Object Name, @{ N='Status'; E={ status $_.Status }}

demo

Upvotes: 3

Related Questions