Last Update: Sep 04, 2024 | Published: Jan 26, 2016
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 $_ } }
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.
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:
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.
I also added options to color the background or the entire line.
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
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"
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.