More PowerShell Coloring Tips for Domain Controller Statuses

color-aspect-hero
I hope you’ve enjoyed our journey into displaying domain controller service statuses in a more colorful format. I hope you have been thinking about how you could extend the techniques I’ve shown you into more flexible and re-usable tools. You will want to be caught up on the previous articles or some of what I’m about to show you might be confusing.


I’m sure you can think of a number of commands where you would like to highlight a particular value. In this article, I want to end up with a PowerShell function that will get the job done. Once again, let’s start with a set of domain controllers and services.

$dcs = "chi-dc01","chi-dc02","chi-dc04"
$svcs = "adws","dns","kdc","netlogon"

Depending on the service status, I want to display it in a given console color based on a hashtable.

$keycolor = @{
Stopped = "Red"
Running = "Green"
StartPending = "Magenta"
ContinuePending = "Cyan"
StopPending = "Magenta"
PausePending = "Cyan"
Paused = "Yellow"
}

I’m also going to need a regular expression pattern to match on the output.

[regex]$r = $keycolor.keys -join "|"

When building a tool, remember to break things down into discrete steps so that you can modularize. I’ll save the data separately.

$data = Get-Service -name $svcs -computername $dcs

This makes it easier to revise my code that will handle the formatting, and I won’t have to rerun the command all the time. I know from the previous articles that I need to turn the data into an array of strings. I had been using the Split() method, but it is just as easy to use the Split operator.

$in = ($data |Select Status,Displayname,Machinename | Out-String) -split "`n"

Now I can pipe this to the code I had been using with ForEach-Object.

$in | foreach {
$z = $r.Match($_)
if ($z.success) {
[string]$m = $z.Value
Write-Host $_.Substring(0,$z.index) -NoNewline
Write-Host $_.Substring($z.Index,$z.Length) -ForegroundColor $keycolor.item($m) -NoNewline
Write-Host $_.Substring($z.index+$z.Length)
}
else {
#just write the line
Write-Host $_
}
}

Highlighted output (Image Credit: Jeff Hicks)
Highlighted output (Image Credit: Jeff Hicks)

So what I need to do is modularize the ForEach code. Cmdlets and advanced functions work that way anyway under the hood. Here’s a prototype.

Function Out-Highlight {
[cmdletbinding()]
Param(
[Parameter(Position=0,Mandatory,ValueFromPipeline)]
[string]$Line
)
Begin {
$keycolor = @{
Stopped = "Red"
Running = "Green"
StartPending = "Magenta"
ContinuePending = "Cyan"
StopPending = "Magenta"
PausePending = "Cyan"
Paused = "Yellow"
}
[regex]$r = $keycolor.keys -join "|"
}
Process {
$z = $r.Match($Line)
if ($z.success) {
[string]$m = $z.Value
Write-Host $Line.Substring(0,$z.index) -NoNewline
Write-Host $line.Substring($z.Index,$z.Length) -ForegroundColor $keycolor.item($m) -NoNewline
Write-Host $line.Substring($z.index+$z.Length)
}
else {
#just write the line
Write-Host $line
}
}
End {
#not used
}
} #end function

And it more or less works.

Testing the prototype function (Image Credit: Jeff Hicks)
Testing the prototype function (Image Credit: Jeff Hicks)

Well, there are some errors, but the function is also limited. The function assumes I will always use the results from Get-Service and that I always want to use these colors. Don’t code yourself into a corner. The goal is to create something flexible enough to use in a variety of situations.
To me, it makes more sense to parameterize the key colors and regular expression pattern. Don’t worry, I’m not going to draw this out. Here’s my completely revised function.

Function Out-Highlight {
[cmdletbinding()]
Param(
[Parameter(
Position = 0,
Mandatory,
ValueFromPipeline
)]
[object]$InputObject,
[Parameter(Mandatory,HelpMessage="Enter a hashtable of associated property names and console colors")]
[ValidateNotNullorEmpty()]
[hashtable]$Highlight,
[Parameter(HelpMessage="Enter a regular expression pattern")]
[ValidateNotNullorEmpty()]
[Alias("rx")]
[regex]$Expression = ".*",
[Switch]$Background,
[switch]$Line
)
Begin {
Write-Verbose "Starting: $($MyInvocation.Mycommand)"
#initialize an array to hold incoming data
$data = @()
} #begin
Process {
$data +=$InputObject
} #process
End {
#convert the data to an array of strings
($data | out-string) -split "`n" | foreach {
$rxtest = $Expression.Match($_)
if ($rxtest.success) {
#get the matched value and trim off extra spaces
[string]$mtch = $rxtest.Value.Trim()
#get corresponding color from key hash
[string]$item = ($highlight.keys).where({$mtch -match "$_$"})
if ($Line) {
$paramHash = @{
Object = $_
}
if ($Background) {
$paramHash.BackgroundColor = $Highlight.item($item)
}
else {
$paramHash.foregroundColor = $Highlight.item($item)
}
#Write the highlighted portion of the line
Write-Host @paramHash
}
else {
Write-Host $_.Substring(0,$rxtest.index) -NoNewline
#define a hashtable of parameters to splat to Write-Host
$paramHash = @{
Object = $_.Substring($rxtest.Index,$rxtest.Length)
NoNewLine = $True
}
if ($Background) {
$paramHash.BackgroundColor = $Highlight.item($item)
}
else {
$paramHash.foregroundColor = $Highlight.item($item)
}
#Write the highlighted portion of the line
Write-Host @paramHash
#write the rest of the line
Write-Host $_.Substring($rxtest.index+$rxtest.Length)
}
} #if rxtest is successful
else {
#just write the line
Write-Host $_
}
} #foreach
Write-Verbose "Ending: $($MyInvocation.Mycommand)"
} #end
} #end function


This version lacks any sort of help documentation, but you can see the syntax:

Out-Highlight syntax (Image Credit: Jeff Hicks)
Out-Highlight syntax (Image Credit: Jeff Hicks)

The default behavior is to process a collection of input objects looking for regular expression pattern matches and then highlighting the match with a corresponding console color in the hightlight hashtable.
Testing the function (Image Credit: Jeff Hicks)
Testing the function (Image Credit: Jeff Hicks)

I also added options to color the background or the entire line.
Highlighting an entire line (Image Credit: Jeff Hicks)
Highlighting an entire line (Image Credit: Jeff Hicks)

This should now work with any type of command, although it may take some work to build the correct regular expression. Here’s an example that highlights files based on their extension.

dir c:\work -file | Out-Highlight -Highlight @{ps1 = "red";png = "cyan";bat = "magenta";xml = "yellow"} -Expression "(?<=\d)(([\s])\S+)+\.(png|ps1|bat|xml)" -Line

Highlighting files (Image Credit: Jeff Hicks)
Highlighting files (Image Credit: Jeff Hicks)

Or virtual machines:

get-vm | Out-Highlight -Highlight @{Running = "Green"; Saved = "Magenta";Paused = "Cyan";Off="Yellow";Critical = "Red"} -Expression "\b(Running|Saved|Off|Paused|\w+Critical)\b"

Highlighting virtual machines (Image Credit: Jeff Hicks)
Highlighting virtual machines (Image Credit: Jeff Hicks)

As I mentioned in previous articles, these techniques are writing directly to the console not the pipeline. This means you can’t do anything with the results other than look at it. Normally you should always be thinking about working with objects in the pipeline, but sometimes highlights like this can make your work a bit easier and PowerShell can help with that as well.

I hope a few of you will kick this around, and let me know what you think.