EtienneT
EtienneT

Reputation: 5428

Color words in powershell script format-table output

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.

Upvotes: 8

Views: 17111

Answers (3)

stevethethread
stevethethread

Reputation: 2524

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'

Upvotes: 4

Rynant
Rynant

Reputation: 24343

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

Upvotes: 16

Emperor XLII
Emperor XLII

Reputation: 13452

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!

Upvotes: 6

Related Questions