8

Is it possible to color only certain words (not complete lines) for a powershell output using format-table. For example, this script scans a folder recursively for a string and then output the result with format-table.

dir -r -i *.* | Select-String $args[0] |
format-table -Property @{label="Line #"; Expression={$_.LineNumber}; width=6},
Path, Line -wrap

It would be nice to be able to format the word we are searching for with a specific color, so that you can see exactly where it was found on the line.

3 Answers 3

16

You could pipe the table into Out-String, then write the string in parts using Write-Host with the -NoNewLine switch.

Something like this:

filter ColorWord {
    param(
        [string] $word,
        [string] $color
    )
    $line = $_
    $index = $line.IndexOf($word, [System.StringComparison]::InvariantCultureIgnoreCase)
    while($index -ge 0){
        Write-Host $line.Substring(0,$index) -NoNewline
        Write-Host $line.Substring($index, $word.Length) -NoNewline -ForegroundColor $color
        $used = $word.Length + $index
        $remain = $line.Length - $used
        $line = $line.Substring($used, $remain)
        $index = $line.IndexOf($word, [System.StringComparison]::InvariantCultureIgnoreCase)
    }
    Write-Host $line
}

Get-Process| Format-Table| Out-String| ColorWord -word 1 -color magenta
Sign up to request clarification or add additional context in comments.

2 Comments

Works great! Modified this line: $index = $line.IndexOf($word) to $index = $line.IndexOf($word, [System.StringComparison]::InvariantCultureIgnoreCase)
Good point on the ignore case. I've updated the answer with the modification.
6

I like Rynant's approach. Here is an alternate implementation, using -split instead of IndexOf:

filter ColorWord( [string]$word, [ConsoleColor]$color ) {
    $later = $false
    $_ -split [regex]::Escape( $word ) | foreach {
      if( $later ) { Write-Host "$word" -NoNewline -ForegroundColor $color }
      else { $later = $true }
      Write-Host $_ -NoNewline
    }
    Write-Host
}

Split includes empty strings if the line starts or ends with the given word, hence the extra "if not first" logic.


Edit: Following Rynant's comment, here's another implementation that supports both simple and regex patterns:

filter ColorPattern( [string]$Pattern, [ConsoleColor]$Color, [switch]$SimpleMatch ) {
  if( $SimpleMatch ) { $Pattern = [regex]::Escape( $Pattern ) }
  
  $split = $_ -split $Pattern
  $found = [regex]::Matches( $_, $Pattern, 'IgnoreCase' )
  for( $i = 0; $i -lt $split.Count; ++$i ) {
    Write-Host $split[$i] -NoNewline
    Write-Host $found[$i] -NoNewline -ForegroundColor $Color
  }
  
  Write-Host
}

The output from the following examples shows the difference:

PS> '\d00\d!' | ColorPattern '\d' 'Magenta' -Simple
\d00\d!

PS> '\d00\d!' | ColorPattern '\d' 'Magenta'
\d00\d!

2 Comments

Just beware that you need to escape regex characters. E.g. 'Ke$ha - Tik Tok.mp3'| ColorWords 'ke\$ha' red. Alternatively, $_ -split [regex]::Escape($word) could be used in the filter. Unless you want to match with regex, which could be nice.
@Rynant Thanks for pointing that out! Updated with new version.
4

I love answer @Ryant gave. I have a modified version here that can be used for colouring multiple words in an output by passing in arrays or words and colours. The trick is that you have to split the input text into lines based on the newline separator.

filter ColorWord2 {
param(
    [string[]] $word,
    [string[]] $color
)
$all = $_
$lines = ($_ -split '\r\n')

$lines | % {
    $line = $_      
    $x = -1

    $word | % {
        $x++
        $item = $_      

        $index = $line.IndexOf($item, [System.StringComparison]::InvariantCultureIgnoreCase)                            
            while($index -ge 0){
                Write-Host $line.Substring(0,$index) -NoNewline                 
                Write-Host $line.Substring($index, $item.Length) -NoNewline -ForegroundColor $color[$x]
                $used =$item.Length + $index
                $remain = $line.Length - $used
                $line =$line.Substring($used, $remain)
                $index = $line.IndexOf($item, [System.StringComparison]::InvariantCultureIgnoreCase)
            }
        }

    Write-Host $line
} }

and would be executed as follows

Get-Service | Format-Table| Out-String| ColorWord2 -word 'Running','Stopped' -color 'Green','Red'

2 Comments

I love this answer, it's exactly what I was looking for.
Cheers. I actually use a variation on this now. Rather than passing in two arrays, I just pass in a hashtable, with the colour being the key, and the value being an array of words that will have the colour applied to.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.