Powershell – Output Color for certain results

Find advanced function Out-HostColored below, to which you can pipe any command and color its output selectively based on regular expressions / literal substrings.

Applied to your case, with simulated input:

@'
Enabled          : True
Locked           : False
Display Name     : foo
Name             : bar
Last Name        : baz
Mail             : [email protected]
Last Logon       : 9/1/2017 7:33:30 PM
Date Created     : 11/21/2014 6:04:54 AM
PW Last Reset    : 9/2/2015
PW Never Expires : False
Description      : Technician
Office           : IT
Canonical Name   : CORPORATE.LOCAL/foo Users/bar
'@ |
  Out-HostColored '(?<=Enabled\s+: )True', '(?<=PW Never Expires\s+: )False' Green

Two regular expressions are passed that match only the tokens of interest and color them greenOriginally, only a single regex was supported. Tip of the hat to @SamHasler for the suggestion to accept an array..
Note the use of look-behind assertions ((?<=...)) to match the proper context without including it in the match.

The above yields:

sample output

Out-HostColored features:

  • You can pipe any command – whether you rely on its implicit formatting or whether you pipe it to a Format-* cmdlet explicitly first.

  • Matching is restricted to a single line at a time, but coloring multiple matches on a given line is supported.

  • You can specify both a foreground color (-ForegroundColor) and (-BackgroundColor) explicitly if desired; by default, matches are colored green.

  • To color the entire line if a match is found, pass -WholeLine

  • To pass literal substrings to match rather than regular expressions, use -SimpleMatch.


Out-HostColored source code (PSv2+):

Note:

  • The function below is now also available as an MIT-licensed Gist – only the latter will be maintained going forward.

  • The Gist additionally supports a -CaseSensitive switch and pattern-specific coloring, via a dictionary of per-pattern colorsTip of the hat to @not2qubit for the inspiration; e.g.:

    # Prints "easy" and "green" in green, "being" in black on yellow.
    'It ain''t easy being green.' | Out-HostColored @{ 
      ('easy', 'green') = 'green'
      '\bbe.+?\b' = 'black,yellow' 
    }
    
  • In PSv3+, you can install the function directly from the Gist as follows (while I personally assure you that doing so is safe, it’s generally a good idea to check the source first):

    irm https://gist.github.com/mklement0/243ea8297e7db0e1c03a67ce4b1e765d/raw/Out-HostColored.ps1 | iex
    
<#
.SYNOPSIS
Colors portions of the default host output that match given patterns.

.DESCRIPTION
Colors portions of the default-formatted host output based on either
regular expressions or a literal substrings, assuming the host is a console or
supports colored output using console colors.

Matching is restricted to a single line at a time, but coloring multiple
matches on a given line is supported.

Note: Since output is sent to the host rather than the pipeline, you cannot
      chain calls to this function.

.PARAMETER Pattern
One or more search patterns specifying what parts of the formatted 
representations of the input objects should be colored.

 * By default, these patterns are interpreted as regular expressions.

 * If -SimpleMatch is also specified, the patterns are interpreted as literal
   substrings.

.PARAMETER ForegroundColor
The foreground color to use for the matching portions.
Defaults to green.

.PARAMETER BackgroundColor
The optional background color to use for the matching portions.

.PARAMETER WholeLine
Specifies that the entire line containing a match should be colored,
not just the matching portion.

.PARAMETER SimpleMatch
Interprets the -Pattern argument(s) as a literal substrings to match rather
than as regular expressions.

.PARAMETER InputObject
The input object(s) whose formatted representations to color selectively.
Typically provided via the pipeline.

.NOTES
Requires PSv2 or above.
All pipeline input is of necessity collected first before output is produced.

.EXAMPLE
Get-Date | Out-HostColored '\b\p{L}+\b' red white

Outputs the current date with all words composed of letters (p{L}) only in red
on a white background.

.EXAMPLE
Get-ChildItem | select Name | Out-HostColored -WholeLine -SimpleMatch .exe

Highlight all executable file names in green.

.EXAMPLE
'apples', 'kiwi', 'pears' | Out-HostColored '^a', 's$' blue

Highlight all As at the beginning and Ss at the end of lines in blue.
#>
Function Out-HostColored {
  # Note: The [CmdletBinding()] and param() block are formatted to be PSv2-compatible.
  [CmdletBinding()]
  param(
    [Parameter(Position = 0, Mandatory = $True)] [string[]] $Pattern,
    [Parameter(Position = 1)] [ConsoleColor] $ForegroundColor="Green",
    [Parameter(Position = 2)] [ConsoleColor] $BackgroundColor,
    [switch] $WholeLine,
    [switch] $SimpleMatch,
    [Parameter(Mandatory = $True, ValueFromPipeline = $True)] $InputObject
  )

  # Wrap the pattern / literal in an explicit capture group.
  # Fail, if the given regex is syntactically invalid.
  try {
    $re = [regex] ('(?<sep>{0})' -f $(if ($SimpleMatch) { 
          ($Pattern | ForEach-Object { [regex]::Escape($_) }) -join '|'
        } 
        else { 
          ($Pattern | ForEach-Object { '(?:{0})' -f $_ }) -join '|'
        }))
  }
  catch { Throw }

  # Build a parameters hashtable specifying the colors, to be use via
  # splatting with Write-Host later.
  $htColors = @{
    ForegroundColor = $ForegroundColor
  }
  if ($BackgroundColor) {
    $htColors.Add('BackgroundColor', $BackgroundColor)
  }

  # Use pipeline input, if provided (the typical case).
  if ($MyInvocation.ExpectingInput) { $InputObject = $Input }

  # Apply default formatting to each input object, and look for matches to
  # color line by line.
  $InputObject | Out-String -Stream | ForEach-Object {
    $line = $_
    if ($WholeLine) {
      # Color the whole line in case of match.
      if ($line -match $re) {
        Write-Host @htColors $line
      }
      else {
        Write-Host $line
      }
    }
    else {
      # Split the line by the regex and include what the regex matched.
      $segments = $line -split $re, 0, 'ExplicitCapture'
      if ($segments.Count -eq 1) {
        # no matches -> output line as-is
        Write-Host $line
      }
      else {
        # at least 1 match, as a repeating sequence of <pre-match> - <match> pairs
        $i = 0
        foreach ($segment in $segments) {
          if ($i++ % 2) {
            # matching part
            Write-Host -NoNewline @htColors $segment
          }
          else {
            # non-matching part
            Write-Host -NoNewline $segment
          }
        }
        Write-Host '' # Terminate the current output line with a newline.
      }
    }
  }
}

Leave a Comment