SCOM Performance Dashboard Widget Woes
SCOM is a great product, but sometimes I would have liked that they spent a bit more time on implementing the silverlight dashboards, especially the performance one.
The reason? The usage of the hardcoded GUIDs to populate the performance counters.
Let’s back up a bit, before going on, and talk a bit about the usage scenario. Creating these dashboards using the GUI is fine, though if you need to create a lot of the same type of dashboards, just with different targets. The GUI route can become a big timesink. Also one usually want to keep these types of dashboards in an unsealed management pack so one can do adjustments as necessary on the fly without having to go the way through Visual Studio to seal the MP anew each time a change is needed. VS still has some complexity tied to it and not everybody wants to invest time into learning this tool.
In trying to find a way to make dashboard creation a bit more easy I did some experiments with unsealed MPs. Though the problem that kept on coming up is the volatile nature of unsealed MPs. By that I mean if you have defined groups in the MP, each time it is updated the GUIDs of the groups might change, and this breaks the dashboards targeting these groups. Even importing the unsealed MP back in with the existing MP in place changes the group GUIDs. So thank kind of slams the door in my face.
The old performance views support dynamic targeting, so for these one can target a group in this manner:
$MPReference/MyMPName!MyMPName.MyGroupClassName/Id$
Note: This also works for many of the other dashboard widgets, though not for the performance one for some reason.
Example:
First lets look at how the group class looks. SCOM does not make things easy to identify for things created through the GUI, but as I have only created only one group it is easy to identify in my exported unsealed MP.
1 2 3 4 5 6 7 | <TypeDefinitions> <EntityTypes> <ClassTypes> <ClassType ID="UINameSpace4970f727b71740128acd0e765222a146.Group" Accessibility="Public" Abstract="false" Base="MicrosoftSystemCenterInstanceGroupLibrary7585010!Microsoft.SystemCenter.InstanceGroup" Hosted="false" Singleton="true" Extension="false" /> </ClassTypes> </EntityTypes> </TypeDefinitions> |
Next find the performance view on the MP. Also here in the example I only created one so it is easy to identify. If you find the <target> one will see that in the exported MP a GUID will reside here, though this is tied to the group that only exists as long as the unsealed MP persists in it’s current state and not changed. We can get around this limitation by introducing a dynamic variable as shown below:
1 | $MPElement[Name='UINameSpace4970f727b71740128acd0e765222a146.Group']$ |
1 2 3 4 5 6 7 8 9 10 | <Views> <View ID="View_cf60c097736343f6bb96076c1dc605dc" Accessibility="Public" Enabled="true" Target="System!System.Entity" TypeID="SystemCenter!Microsoft.SystemCenter.PerformanceViewType" Visible="true"> <Category>Operations</Category> <Criteria /> <Presentation> Removed </Presentation> <Target>$MPElement[Name='UINameSpace4970f727b71740128acd0e765222a146.Group']$</Target> </View> </Views> |
When you now delete the MP in SCOM and import the same XML back in, it will use whatever the group contains as a scoping mechanism for the performance counters displayed. It is too bad this trick only works with the old performance view and not the new performance widget.
Here one is stuck with static GUIDs, I have not found any other dynamic variable that works with this. For many of the other widgets one can use this adaption:
1 | $MPReference/My.Dashboards!UINameSpace4970f727b71740128acd0e765222a146.Group/Id$ |
This just does not work for the performance widget for some reason.
So what can we do?
The only viable solution I have found is to separate the groups into another MP and seal it. When the MP is sealed, we can target the groups in there from another MP. Unsealed, this does not work.
So one would need to create a groups template MP, and for each type of scenario (like separation into different customers), create the needed groups and then seal the MP.
When it comes to the dashboard template MP, one would need to export this and then manually change all the GUIDs to the new ones of the different groups MP. This is a bit tedious. I therefore created a powershell script that strips the exported dashboard xml MP of the GUIDs and replaces them with the group name. The thinking behind this is that you now can change the ID of all the assets in the MP (preferable a bit more logical than what SCOM generates in the first place). An example of this is to take the group from the previous example:
1 | <ClassType ID="UINameSpace4970f727b71740128acd0e765222a146.Group" ... |
SCOM created this name, and it is not easy to know what this asset is. Therefore changing it to something like this would make it easier to do search and replace.
1 | <ClassType ID="MyDashboards.Customer.HyperVHosts.Group" |
As I know this group will have Hyper-V hosts from a specific customer I choose to name it so I can more easily understand this by looking at the name. And now all I need to do is change out the .Customer. identificator to another one, and now have an identical MP ready though for another customer.
After having changed all the IDs to something more descriptive, we can to the same for the static GUIDs of the performance widgets. The script below connects to SCOM and retrieves the group names based on the GUIDs in the xml MP and changes them out with the name of the group instead. Now one can do the same search and replace on the group names and change it out with the new customer name.
The next step will be to import the sealed MP with the groups for the new customer. Once they have taken hold, one can run the script again against the dashboard MP. The script will now take all the group names and through SCOM get the new GUIDs. These GUIDs will replace the names, and in a few moments we have fixed all the performance dashboards for the new customer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | Import-Module OperationsManager $SCOMmgmtServer = "" $XMLPath = "C:\Temp\DashboardMP.xml" $xPath = "//*[@PropertyId='ManagedEntityIds']" [XML]$XMLfile = Get-Content -Path $XMLPath $Nodes = Select-Xml -Xml $XMLfile -XPath $xPath $Nodes | ForEach-Object { if($_) { # Check if GUID If( $_.node.ComplexValueCollection.ComplexValue.binding.SimpleValue.Value -match("^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$") ) { $nodeValue = $_.node.ComplexValueCollection.ComplexValue.binding.SimpleValue.Value [string]$DisplayName = Get-SCOMGroup -Id $nodeValue -ComputerName $SCOMmgmtServer | Select-Object -ExpandProperty DisplayName # Write DisplayName to XML Write-Output "Replacing GUID: [$nodeValue] with Display Name of group: $DisplayName" $_.node.ComplexValueCollection.ComplexValue.binding.SimpleValue.Value = $DisplayName } Else { $nodeValue = $_.node.ComplexValueCollection.ComplexValue.binding.SimpleValue.Value $Group = Get-SCOMGroup -DisplayName $nodeValue -ComputerName $SCOMmgmtServer Write-Output "Replacing Display Name of group: $($Group.DisplayName) with GUID: : [$nodeValue]" # Write DisplayName to XML $_.node.ComplexValueCollection.ComplexValue.binding.SimpleValue.Value = [string]$Group.Id.GUID } } } # Save XML $XMLfile.Save($XMLPath) |
Happy tinkering