3

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.

1

2 Answers 2

3

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

Sign up to request clarification or add additional context in comments.

4 Comments

@zett42 PSStyle is not available in Windows PowerShell
As an aside, if you have PowerShell 7+ available, you can avoid hardcoded ANSI escape sequences by using the $PSStyle variable: Stopped { $PSStyle.Formatting.Error } Started { $PSStyle.Foreground.BrightGreen }
I'm surprised this works actually, since there is no ANSI reset code, AFAICT.
@not2qubit sure there is, the reset code is "$([char] 27)[0m" but there is no need here because these are property values not a simple string. see in my profile I have a regex example using the reset code for a rainbow string
2

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.

3 Comments

Thanks for the explanation about delay! It was unclear for me why coloring didn't work for every line, when I used foreach. But why this work for array if I don't use Select-Object -Property name, state?($pools in my example) Why we don't have delay here?
@IlyaAfinogentov, there are two likely explanations: without Select-Object, (a) perhaps you're getting a list representation (implicit Format-List), which is not subject to the delay or (b) the output objects are of a type that has predefined formatting data associated with it, which is also not subject to the delay, because the column widths are predefined.
Yes, it has list representation. Thanks a lot! It's clear now.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.