OpsMgr: Displaying an ASCII Bar Chart on a Text Display Widget

This post features another example of how the PowerShell Text Display Widget Template (Download Here) can used to create a custom widget in the OpsMgr 2012 Operations Console, to display the list of the top 5 alerts with the longest downtime (outage) and closed within the last X hours, in a management group.

Apart from listing the top 5 alerts in text, an ASCII bar chart was also added to provide a visual comparison between the outage duration of the alerts, on a text display widget, as shown in the picture below:
For more information on how to use a PowerShell script to generate a bar chart made up of ASCII characters, please check out the article that I contributed to the Hey, Scripting Guy! Blog, entitled Weekend Scripter: PowerShell ASCII Bar Charts

Note that for this instance, the outage duration is the time for an alert raised to be resolved, i.e. Alert Resolved Date – Alert Create Date. 
    
image

To create an instance of the example above using a custom PowerShell text display widget, first create a dashboard layout with 1 cell, then Click to add widget on a cell, and select the Sample PowerShell Text Display Widget template located under the "All Templates/WeiOutThere Text Display" folder , go through the UI pages of the template and enter the required information.

image

Here is the full PowerShell script used in the PowerShell Script page of the widget:

#///Main Part of the Script to retrieve the top 5 alerts with the longest outage within X hours ///

# Initializing
$FullList=""
$newline = "`r`n"
$hash = @{}
$hash2 = @{}
$index = 0
$output=""

# X hours is set to 72 hours by default
$backdateHours = 72  

# Retrieves all alerts with resolution state as "255" (Closed) in the last X hours
$alert = get-scomalert | where-object {$_.ResolutionState -eq "255" -and $(New-TimeSpan $_.TimeRaised $(Get-Date).ToUniversalTime()).TotalHours -le $backdateHours}       

# Null Check
if($alert)
{
foreach ($alertdetail in $alert)
{      

$timedifference = $(New-TimeSpan $(Get-Date).ToUniversalTime() $(Get-Date)).totalhours

$NewCreateDate = $($alertdetail.timeraised).addhours($timedifference)
$NewResolvedDate = $($alertdetail.TimeResolved).addhours($timedifference)

#Consider the outage duration is the time for an alert raised to be resolved:
$outage = $NewResolvedDate - $NewCreateDate

#Outage duration in minutes
$totaloutageinminutes = [math]::abs($outage.totalminutes)

#Use first hashtable for sorting and the second hashtable for referencing
$hash[$index] = @{}
$hash[$index] = [math]::Round($totaloutageinminutes)

$hash2[$index] = @{}
$hash2[$index]["NewCreateDate"] = $NewCreateDate
$hash2[$index]["NewResolvedDate"] = $NewResolvedDate
$hash2[$index]["totaloutageinminutes"] = [math]::Round($totaloutageinminutes)
$hash2[$index]["alertname"] = $alertdetail.Name

$index++
}
}

#Sort and get top 5
$recordset = $hash.GetEnumerator() | sort -Property value -descending | select -First 5  

#Generate list of top 5 alerts
$FullList = "Top 5 Alerts with The Longest Outages (in minutes) and Closed within the Last " + $backdateHours + " hours: " + $newline + $newline
ForEach($record in $recordset)
{
$identifier = $record.name
$FullList = $FullList + $hash2[$identifier]["totaloutageinminutes"] + " minutes of Outage for Alert: " + $hash2[$identifier]["alertname"] + " From: " + $hash2[$identifier]["NewCreateDate"] + " to " + $hash2[$identifier]["NewResolvedDate"] + $newline
}

#/////////////////////////END Of Main Part ///////

#//////////////////////////Part Generate ASCII bar chart ////////////////////
if($recordset.count-gt 0)
{
$TotalBars = 5
$Bar1 = $recordset[0].Value
$Bar2 = $recordset[1].Value
$Bar3 = $recordset[2].Value
$Bar4 = $recordset[3].Value
$Bar5 = $recordset[4].Value
$AllResultsMax= $Bar1,$Bar2,$Bar3,$Bar4,$Bar5 | measure -Maximum
$SumBar = $Bar1+$Bar2+$Bar3+$Bar4+$Bar5
$HeightPadding = 2
if($Bar1 -ge 100){$Amplifier = 0.05 }
if($Bar1 -ge 1000){$Amplifier = 0.005 }
if($Bar1 -ge 10000){$Amplifier = 0.0005 }  

$AverageHeight = [math]::Round(($SumBar/$TotalBars) * $Amplifier)
$AverageHeight = $AverageHeight + $HeightPadding
$Bar1Height = [math]::Round(($Bar1/$AllResultsMax.Maximum)*$AverageHeight)
$Bar2Height = [math]::Round(($Bar2/$AllResultsMax.Maximum)*$AverageHeight)
$Bar3Height = [math]::Round(($Bar3/$AllResultsMax.Maximum)*$AverageHeight)
$Bar4Height = [math]::Round(($Bar4/$AllResultsMax.Maximum)*$AverageHeight)
$Bar5Height = [math]::Round(($Bar5/$AllResultsMax.Maximum)*$AverageHeight)
$AllBarHeightMax= $Bar1Height,$Bar2Height,$Bar3Height,$Bar4Height,$Bar5Height | measure -Maximum
$XAxisPadding = 5
$Bar1Name = " "
$Bar2Name = " "
$Bar3Name = " "
$Bar4Name = " "
$Bar5Name = " "
$whiteSpaceHeight1 = $AllBarHeightMax.Maximum - $Bar1Height
$whiteSpaceHeight2 = $AllBarHeightMax.Maximum - $Bar2Height
$whiteSpaceHeight3 = $AllBarHeightMax.Maximum - $Bar3Height
$whiteSpaceHeight4 = $AllBarHeightMax.Maximum - $Bar4Height
$whiteSpaceHeight5 = $AllBarHeightMax.Maximum - $Bar5Height
$TotalXAxisLength = $Bar1Name.length + $Bar2Name.length + $Bar3Name.length + $Bar4Name.length + $Bar5Name.length -10
$renderString = "" + $newline
$renderString = $renderString + " |" + $newline
for($i=$AverageHeight; $i -ge 0; $i--)
{
$renderString = $renderString + " | "
if($whiteSpaceHeight1 -gt 0)
{
$BarWidthStr =""
$BarWidthStr = $BarWidthStr.PadRight($Bar1Name.length+4," ")
$renderString = $renderString + $BarWidthStr
$whiteSpaceHeight1 = $whiteSpaceHeight1 - 1
}
if($i -eq $Bar1Height)
{
$CountStr = " (" + $Bar1 + ")"
$padding = ($Bar1Name.length + 4) - $CountStr.length
if($padding -gt 0){$CountStr = $CountStr.PadRight($padding+$CountStr.length+1," ")}
$renderString = $renderString + $CountStr
}
if($i -eq $Bar1Height-1)
{
$BarWidthStr =""
$BarWidthStr = $BarWidthStr.PadRight($Bar1Name.length-7,"_")
$renderString = $renderString + $BarWidthStr
}
if($i -eq $Bar1Height-2)
{
$BarWidthStr ="|"
$BarWidthStr = $BarWidthStr.PadRight($Bar1Name.length+3," ")
$renderString = $renderString + $BarWidthStr + "|"
}
if($i -eq $Bar1Height-3)
{$renderString = $renderString + "| " + $Bar1Name + " |"}
if($i -lt $Bar1Height-3)
{
$BarWidthStr ="|"
$BarWidthStr = $BarWidthStr.PadRight($Bar1Name.length+3," ")
$renderString = $renderString + $BarWidthStr + "|"
}
if($whiteSpaceHeight2 -gt 0)
{
$BarWidthStr =""
$BarWidthStr = $BarWidthStr.PadRight($Bar2Name.length+4," ")
$renderString = $renderString + $BarWidthStr
$whiteSpaceHeight2 = $whiteSpaceHeight2 - 1
}
if($i -eq $Bar2Height)
{
$CountStr = " (" + $Bar2 + ")"
$padding = $Bar2Name.length+4-$CountStr.length
If($padding -gt 0){$CountStr = $CountStr.PadRight($padding+$CountStr.length+1," ")}
$renderString = $renderString + $CountStr
}
if($i -eq $Bar2Height-1)
{
$BarWidthStr =""
$BarWidthStr = $BarWidthStr.PadRight($Bar2Name.length-7,"_")
$renderString = $renderString + $BarWidthStr
}
if($i -eq $Bar2Height-2)
{
$BarWidthStr ="|"
$BarWidthStr = $BarWidthStr.PadRight($Bar2Name.length+3," ")
$renderString = $renderString + $BarWidthStr + "|"
}
if($i -eq $Bar2Height-3)
{$renderString = $renderString + "| " + $Bar2Name + " |"}
if($i -lt $Bar2Height-3)
{
$BarWidthStr ="|"
$BarWidthStr = $BarWidthStr.PadRight($Bar2Name.length+3," ")
$renderString = $renderString + $BarWidthStr + "|"
}
if($whiteSpaceHeight3 -gt 0)
{
$BarWidthStr =""
$BarWidthStr = $BarWidthStr.PadRight($Bar3Name.length+4," ")
$renderString = $renderString + $BarWidthStr
$whiteSpaceHeight3 = $whiteSpaceHeight3 - 1
}
if($i -eq $Bar3Height)
{
$CountStr = " (" + $Bar3 + ")"
$padding = $Bar3Name.length+4-$CountStr.length
If($padding -gt 0){$CountStr = $CountStr.PadRight($padding+$CountStr.length+1," ")}
$renderString = $renderString + $CountStr
}
if($i -eq $Bar3Height-1)
{
$BarWidthStr =""
$BarWidthStr = $BarWidthStr.PadRight($Bar3Name.length-7,"_")
$renderString = $renderString + $BarWidthStr
}
if($i -eq $Bar3Height-2)
{
$BarWidthStr ="|"
$BarWidthStr = $BarWidthStr.PadRight($Bar3Name.length+3," ")
$renderString = $renderString + $BarWidthStr + "|"
}
if($i -eq $Bar3Height-3)
{$renderString = $renderString + "| " + $Bar3Name + " |"}
if($i -lt $Bar3Height-3)
{
$BarWidthStr ="|"
$BarWidthStr = $BarWidthStr.PadRight($Bar3Name.length+3," ")
$renderString = $renderString + $BarWidthStr + "|"
}
if($whiteSpaceHeight4 -gt 0)
{
$BarWidthStr =""
$BarWidthStr = $BarWidthStr.PadRight($Bar4Name.length+4," ")
$renderString = $renderString + $BarWidthStr
$whiteSpaceHeight4 = $whiteSpaceHeight4 - 1
}
if($i -eq $Bar4Height)
{
$CountStr = " (" + $Bar4 + ")"
$padding = $Bar4Name.length+4-$CountStr.length
If($padding -gt 0){$CountStr = $CountStr.PadRight($padding+$CountStr.length+1," ")}
$renderString = $renderString + $CountStr
}
if($i -eq $Bar4Height-1)
{
$BarWidthStr =""
$BarWidthStr = $BarWidthStr.PadRight($Bar4Name.length-7,"_")
$renderString = $renderString + $BarWidthStr
}
if($i -eq $Bar4Height-2)
{
$BarWidthStr ="|"
$BarWidthStr = $BarWidthStr.PadRight($Bar4Name.length+3," ")
$renderString = $renderString + $BarWidthStr + "|"
}
if($i -eq $Bar4Height-3)
{$renderString = $renderString + "| " + $Bar4Name + " |"}
if($i -lt $Bar4Height-3)
{
$BarWidthStr ="|"
$BarWidthStr = $BarWidthStr.PadRight($Bar4Name.length+3," ")
$renderString = $renderString + $BarWidthStr + "|"
}
if($whiteSpaceHeight5 -gt 0)
{
$BarWidthStr =""
$BarWidthStr = $BarWidthStr.PadRight($Bar5Name.length+4," ")
$renderString = $renderString + $BarWidthStr
$whiteSpaceHeight5 = $whiteSpaceHeight5 - 1
}
if($i -eq $Bar5Height)
{
$CountStr = " (" + $Bar5 + ")"
$padding = $Bar5Name.length+4-$CountStr.length
If($padding -gt 0){$CountStr = $CountStr.PadRight($padding+$CountStr.length+1," ")}
$renderString = $renderString + $CountStr
}
if($i -eq $Bar5Height-1)
{
$BarWidthStr =""
$BarWidthStr = $BarWidthStr.PadRight($Bar5Name.length-7,"_")
$renderString = $renderString + $BarWidthStr
}
if($i -eq $Bar5Height-2)
{
$BarWidthStr ="|"
$BarWidthStr = $BarWidthStr.PadRight($Bar5Name.length+3," ")
$renderString = $renderString + $BarWidthStr + "|"
}
if($i -eq $Bar5Height-3)
{$renderString = $renderString + "| " + $Bar5Name + " |"}
if($i -lt $Bar5Height-3)
{
$BarWidthStr ="|"
$BarWidthStr = $BarWidthStr.PadRight($Bar5Name.length+3," ")
$renderString = $renderString + $BarWidthStr + "|"
}
$renderString = $renderString + $newline
}
$XAxisString = ""
$renderString = $renderString + " " + $XAxisString.PadRight($TotalXAxisLength,"-") + ">" + $newline
$output = $FullList + $newline+ "Top 5 Outages in minutes:" + $newline + $renderString
}
Else
{
$output = "List is empty."
}

#//////////////////////////End Part Generate ASCII bar chart ////////////////////  

#Create data object

$dataObject = $ScriptContext.CreateInstance("xsd://TextDisplayTemplate!Widget/Text")
$dataObject["Id"] = "ObjectID"

$OutputString = $output

#Assign a string to the ReturnString property of the data object to display on the Text Display Widget

$dataObject["ReturnString"] = $OutputString
$ScriptContext.ReturnCollection.Add($dataObject)

#/////////////////////////////////////////////////////////////////////////////////

On the “Refresh Interval” page, enter a numerical value for the refresh interval of the widget (in seconds), then click the Finish button to create the custom PowerShell Text Display widget.

Additional Notes:
Because the text display widget displays ASCII characters in a different font compared to a regular PowerShell or ISE session, the ASCII bar chart may look distorted due to the way the widget handles the spacing between different characters. Hence, to save time in calibrating the spacing and size of characters for the text display widget, assign the numerical values from Bar1 to Bar 5 in descending order (after sorting), so that the ASCII bar chart would look like the left most example shown in the following picture:

image

For more information, about the Sample PowerShell Text Display Widget Template, refer to:
https://blogs.msdn.com/b/wei_out_there_with_system_center/archive/2015/11/28/opsmgr-sample-powershell-text-display-widget-template.aspx

      

Thank you for your support !       
                 

Disclaimer:
All information on this blog is provided on an as-is basis with no warranties and for informational purposes only. Use at your own risk. The opinions and views expressed in this blog are those of the author and do not necessarily state or reflect those of my employer.