Menu Close

How to discover and monitor a clustered application


Monitoring clustered applications can be a little tricky.

Clusters contains virtual, abstract objects, that could be hosted by one or more nodes.  We need to ensure we always monitor the clustered resource, no matter where it is running.

We cannot simply target discovery and monitoring to the nodes, because by design the clustered resource will only exist on one node, then all other nodes would generate alerts.

In SCOM, for every clustered resource group that contains an IP Address and a Network Name, we will discover the network name as a Windows Computer object.  We will use this “virtual” Windows Computer as the host for our clustered application class.

Essentially – this will be an MP that contains two discoveries – one lightweight discovery for the “Seed” class, then one more granular script based discovery to find only your clustered application class instances.

We start with a simple seed class discovery.  The purpose of the seed class is to discover all nodes and virtual computers that “could” host the resource.  In this example, I will use a simple registry discovery, and ensure Remoteable = true.  Remoteable allows the nodes to discover for the virtual computers.  Try to use something specific to your application, such as the existence of a specific service registry for this discovery.  This seed discovery will target “Windows Server” class.  The reason for this, is that we want to pass a property of the Windows Server Class “IsVirtualNode” to our seed class.  This Windows Server class already has a property to know if the object is a cluster virtual object (true) or not (empty).

Next, we will use a script based discovery, and *target* the seed class instances.  One of the first things we will pass to the script as a parameter, is the IsVirtualNode property.  The script will only return discovery data for virtual objects.  Then it will filter further, finding only the instance of your application, through a name, a file, a service, or however you choose to discover it.


Here is the MP example:

The class definitions:

<TypeDefinitions> <EntityTypes> <ClassTypes> <ClassType ID="Demo.MyClusteredApp.Seed.Class" Accessibility="Public" Abstract="false" Base="Windows!Microsoft.Windows.LocalApplication" Hosted="true" Singleton="false" Extension="false"> <Property ID="IsVirtualNode" Type="string" AutoIncrement="false" Key="false" CaseSensitive="false" MaxLength="256" MinLength="0" Required="false" Scale="0" /> </ClassType> <ClassType ID="Demo.MyClusteredApp.Clustered.Class" Accessibility="Public" Abstract="false" Base="Windows!Microsoft.Windows.LocalApplication" Hosted="true" Singleton="false" Extension="false"> <Property ID="ClResourceGroupName" Type="string" AutoIncrement="false" Key="false" CaseSensitive="false" MaxLength="256" MinLength="0" Required="false" Scale="0" /> <Property ID="ClResourceName" Type="string" AutoIncrement="false" Key="false" CaseSensitive="false" MaxLength="256" MinLength="0" Required="false" Scale="0" /> </ClassType> <ClassType ID="Demo.MyClusteredApp.ComputersAndWatchers.Group" Accessibility="Public" Abstract="false" Base="SCIG!Microsoft.SystemCenter.InstanceGroup" Hosted="false" Singleton="true" Extension="false" /> </ClassTypes> </EntityTypes> </TypeDefinitions>


The seed discovery:

In this example, I am using a very simple seed discovery, such as the existence of the print spooler service.  Normally, you’d want something specific to your application:

<Discovery ID="Demo.MyClusteredApp.Seed.Class.Discovery" Enabled="true" Target="Windows!Microsoft.Windows.Server.Computer" ConfirmDelivery="false" Remotable="true" Priority="Normal"> <Category>Discovery</Category> <DiscoveryTypes> <DiscoveryClass TypeID="Demo.MyClusteredApp.Seed.Class"> <Property PropertyID="IsVirtualNode" /> </DiscoveryClass> </DiscoveryTypes> <DataSource ID="DS" TypeID="Windows!Microsoft.Windows.FilteredRegistryDiscoveryProvider"> <ComputerName>$Target/Property[Type="Windows!Microsoft.Windows.Computer"]/PrincipalName$</ComputerName> <RegistryAttributeDefinitions> <RegistryAttributeDefinition> <AttributeName>SeedRegKeyExists</AttributeName> <Path>SYSTEM\CurrentControlSet\Services\Spooler</Path> <PathType>0</PathType> <!-- 0=regKey 1=regValue --> <AttributeType>0</AttributeType> <!-- 0=CheckIfExists (boolean) 1=treat data as string 2=treat data as INT--> </RegistryAttributeDefinition> </RegistryAttributeDefinitions> <Frequency>14400</Frequency> <ClassId>$MPElement[Name="Demo.MyClusteredApp.Seed.Class"]$</ClassId> <InstanceSettings> <Settings> <Setting> <Name>$MPElement[Name="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Name> <Value>$Target/Property[Type="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Value> </Setting> <Setting> <Name>$MPElement[Name="System!System.Entity"]/DisplayName$</Name> <Value>$Target/Property[Type="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Value> </Setting> <Setting> <Name>$MPElement[Name="Demo.MyClusteredApp.Seed.Class"]/IsVirtualNode$</Name> <Value>$Target/Property[Type="Windows!Microsoft.Windows.Server.Computer"]/IsVirtualNode$</Value> </Setting> </Settings> </InstanceSettings> <Expression> <SimpleExpression> <ValueExpression> <XPathQuery Type="Boolean">Values/SeedRegKeyExists</XPathQuery> </ValueExpression> <Operator>Equal</Operator> <ValueExpression> <Value Type="Boolean">true</Value> </ValueExpression> </SimpleExpression> </Expression> </DataSource> </Discovery>


The script:

In this example, I first check IsVirtualNode = true, then if so, continue to find instances of my clustered application.  You will need to customize this part for each app you wish to discover.

#================================================================================= # Discover Clustered Application # This discovery runs against a seed class target to find a specific clustered app # # Author: Kevin Holman # v1.0 #================================================================================= param($SourceId,$ManagedEntityId,$ComputerName,$IsVirtualNode) # Manual Testing section - put stuff here for manually testing script - typically parameters: #================================================================================= # $SourceId = '{00000000-0000-0000-0000-000000000000}' # $ManagedEntityId = '{00000000-0000-0000-0000-000000000000}' # $ComputerName = "" # $IsVirtualNode = $true #================================================================================= # Constants section - modify stuff here: #================================================================================= # Assign script name variable for use in event logging. # ScriptName should be the same as the ID of the module that the script is contained in $ScriptName = "Demo.MyClusteredApp.Clustered.Class.Discovery.ps1" $EventID = "9051" #================================================================================= # Starting Script section - All scripts get this #================================================================================= # Gather the start time of the script $StartTime = Get-Date #Set variable to be used in logging events $whoami = whoami # Load MOMScript API $momapi = New-Object -comObject MOM.ScriptAPI #Log script event that we are starting task $momapi.LogScriptEvent($ScriptName,$EventID,0,"`n Script is starting. `n Running as ($whoami). `n ComputerName: ($ComputerName) `n IsVirtualNode: ($IsVirtualNode)") #================================================================================= # Discovery Script section - Discovery scripts get this #================================================================================= # Load SCOM Discovery module $DiscoveryData = $momapi.CreateDiscoveryData(0, $SourceId, $ManagedEntityId) #================================================================================= # Begin MAIN script section #================================================================================= #Only continue if this is a cluster virtual IF (!($IsVirtualNode)) { # Log an event to end discovery $momapi.LogScriptEvent($ScriptName,$EventID,0,"`n IsVirtualNode = false. Do not return discovery data.") } ELSE { # Log an event to continue discovery $momapi.LogScriptEvent($ScriptName,$EventID,0,"`n IsVirtualNode = true. `n We will continue discovery on this cluster object. `n ComputerName: ($ComputerName)") # ====== BEGIN your custom filter for clustered app ====== # Now we need to create a filter so that only the app we are looking for is discovered # This might be using a cluster powershell filter, WMI query, or looking for a file or process or however makes sense for your app # This example will discover any cluster resource with a NetworkName object type, that has a DNS name value that matches the target computer as an example only # Import cluster PS module Import-Module FailoverClusters #Get all the cluster resources on this cluster [array]$ClResources = Get-ClusterResource | where {$_.ResourceType.Name -eq "Network Name"} FOREACH ($ClResource in $ClResources) { #Get the Cluster Resource Name [string]$ClResourceName = $ClResource.Name #Get the NetBIOS name from the ComputerName passed as a param to the script [string]$ComputerNameSplit = ($ComputerName.Split("."))[0] #Get the DNS name from the cluster network name object $ClDNSNameObj = $ClResource | Get-ClusterParameter -Name DnsName [string]$ClDNSName = $ClDNSNameObj.Value #Get only the NetBIOS name for comparison [string]$ClDNSNameSplit = ($ClDNSName.Split("."))[0] IF ($ComputerNameSplit -eq $ClDNSNameSplit) { #Discover stuff [string]$ClResourceGroupName = $ClResource.OwnerGroup.Name $momapi.LogScriptEvent($ScriptName,$EventID,0,"`n Adding discovery data for: `n ComputerName () `n Cluster Resource Name: ($ClResourceName) `n Cluster Resource Group Name: ($ClResourceGroupName)") $instance = $DiscoveryData.CreateClassInstance("$MPElement[Name='Demo.MyClusteredApp.Clustered.Class']$") $instance.AddProperty("$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$", $ComputerName) $instance.AddProperty("$MPElement[Name='System!System.Entity']/DisplayName$", $ComputerName) $instance.AddProperty("$MPElement[Name='Demo.MyClusteredApp.Clustered.Class']/ClResourceName$", $ClResourceName) $instance.AddProperty("$MPElement[Name='Demo.MyClusteredApp.Clustered.Class']/ClResourceGroupName$", $ClResourceGroupName) $DiscoveryData.AddInstance($instance) } } # ====== END your custom filter for clustered app ====== } # Return Discovery Items Normally $DiscoveryData # Return Discovery Bag to the command line for testing (does not work from ISE) # $momapi.Return($DiscoveryData) #================================================================================= # End MAIN script section # End of script section #================================================================================= #Log an event for script ending and total execution time. $EndTime = Get-Date $ScriptTime = ($EndTime - $StartTime).TotalSeconds $momapi.LogScriptEvent($ScriptName,$EventID,0,"`n Script Completed. `n Script Runtime: ($ScriptTime) seconds.") #================================================================================= # End of script


Now, as you can see – this gets pretty complicated to write for each clustered application.

However, it doesn’t have to be complicated.  We can wrap all this up into a Fragment, and reuse it over and over!  I am adding example fragments to my fragment library:

These fragments will make it super easy to reuse this code sample, and just tweak it for your specific clustered apps.




When I use the combo fragment, I can see my work pay off instantly.  I only need to provide 3 pieces of information:

The CompanyID, the AppName, and the RegKeyPath for the SEED class.  That’s it!


Then I can literally import this into SCOM.  Of course, you will want to customize the sample script first, but all the MP heavy lifting is done.

I have a nice prebuilt folder and state views:



I have discovered instances of my SEED class based on the registry:



I have discovered instances of my clustered applications as well, including properties about the cluster resource and group they are from:



This makes something pretty hard, really easy to use.  3 simple pieces of input information, customize the script, and you are done.


To see more about MP fragments, check out the basics:


  1. Jeremy Warren

    I feel like I’m missing something here. Great seed-based discovery example, especially the ability to discover resources hosted within the cluster. However, it’s not really “monitoring” anything. We’d still need to target a service or application monitor at the discovered “Clustered.Class” object, right?

    Would it be possible to put together a fragment that goes the whole way, discovering and monitoring a cluster-hosted service?

    • Kevin Holman

      You are correct – this is just the class and discovery structure. From there – you can target monitoring workflows to the cluster class. This is kind of the point of fragments – lots of easy to add-on components. So now that you have this, just bring in a fragment using Visual Studio or Silect MP Author Pro, for service monitoring, and target your Cluster class you just created. It takes less than 60 seconds to do.

Leave a Reply

Your email address will not be published. Required fields are marked *