I was faced with an interesting situation recently. A customer had a request to discover and monitor an application which installs a service, among other things. Whoopty-doo right? Well, the unique part of this request was that the service name varied across different computers. The service name was not static, but contained some static data, and included the computer name in the service name as well, which would be different for each computer/agent.
So the service might look like: AppNameComputerNameVersion Where <ComputerName> was random depending on the computer name of the agent. What is worse – is that sometimes the <ComputerName> value did not match the actual NetBIOS name of the machine!
The challenge we are faced with, is that the built in Service Unit Monitor does not support wildcards. This is a specialized monitor type which requires the passing of a service name to the monitor workflow. The native module/API to monitor services in OpsMgr is very efficient, but there is no mechanism to support a wildcard. So the challenge is – how do I monitor this application service?
The typical solution would be to write a custom monitor, which runs a script, and have the script query WMI for the service name and state, as WMI does support wildcards. The problem with doing so is that this method requires someone to develop the script, test the script, support changes to the script down the road, and running scripts on a very frequent timer (like once a minute) will consume a lot of OS resources during script startup, runtime, and teardown.
So, another solution, is to discover the service name, and then pass that service name to the service monitor workflow using an XPATH query variable, which resolves as the actual service name (since we discovered it). Brian Wren wrote up a very similar solution here. Brian's solution to this challenge involved using the Windows Service Template to handle this…. The Windows Service Template creates a lot of monitoring from a simple wizard. First – it takes your service name as input. Then it creates a class for that service, and a discovery to discover instances of this new class. Then it creates the monitor for the service, and passes the service name to the monitoring workflow. His example works very well to accomplish the goal of monitoring just services with random naming using a wildcard.
The downside to this approach above, is that we will have to create a new class for each service with a unique name. Any tweaks to the monitor will have to be done for EACH monitor uniquely. Additionally – what if we are writing a complete management pack for our application, and want to treat all the services identically, just monitor them as if the service monitor did support wildcards?
Here is another approach:
What we can do, is to create a new class for our application, and write a discovery for our class. Then – we can add a new property to our class, for the “Service Name”. Then – write another discovery, to simply discover the service name as an attribute of the class. This way – once we discover the attribute of Service Name, we can pass this to the monitoring workflow using the standard service unit monitor. This is a much cleaner approach, but requires getting your hands a little dirtier in MP authoring to accomplish it.
Ok, that’s enough background – lets get started! So, for this example, I will walk through the entire thing, in 4 easy steps:
Step 1: First, we will create a class for our Application.
Step 2: Next, we will write a discovery to discover instances of the class.
Step 3: Then, we will add a property to the class for our service name. We will have to create the additional WMI discovery to discover the service name property and populate the class with that.
Step 4: Lastly – we will create the service monitor and pass this discovered property to the service unit monitor.
Step 1: Create a new Class
We will start with the authoring console. Open the Authoring console and create a new, empty management pack.
Give the MP an Identity (ID) which cannot contain spaces. This is the MP ID that all workflows and classes will leverage. I will call mine Example.Application
Give the MP a Display Name (and a description optionally). I chose “Example Application”. This will be the display name of the management pack.
Select the “Service Model” pane. Select Classes. In the actions pane on the right – choose New > Custom Class.
Give the new class an ID. Every object in the MP will begin with the ID of the management pack, in my case Example.Application.xxxx. For my example application, I don’t have a good service with random text in the name, so I will just use the Opalis Services for my example here. So my Class ID will be Example.Application.OpalisActionServer.
For the base class – we pretty much always start with Microsoft.Windows.LocalApplication as that class was designed to be a good base class locally installed applications on Microsoft servers.
Click OK to save the new class.
Step 2: Create a new discovery to discover instances of our class
Now – go to the Health Model pane, Discoveries, and in the Action pane choose New > Custom Discovery
We need to prove an ID for the Discovery which will populate instances of the Example.Application.OpalisActionServer class. I will use “Example.Application.OpalisActionServerDiscovery”
Fill in a good display name for the discovery. I like to always add the word “Discovery” at the end of any discovery workflow display name – it makes more sense when looking at it in the console down the road. I do the same for Group, Rule, Monitor, etc…. A good naming standard is critical to successful MP authoring and long term management.
For the Target – this is the class that we want to run the discovery on. Since we need to discover on ALL servers, if they have this application (Opalis Action Server) we will choose a good seed discovery target class, such as Microsoft.Windows.Server.Computer (Windows Server).
On the Discovered Classes Tab – add you new class. This is just saying that this discovery will discover instances of the chosen class:
On the Configuration Tab – select “Browse for a type”. We need to choose a predefined module for this discovery – such as the Registry, WMI, or Script. I will use the “Microsoft.Windows.FilteredRegistryDiscoveryProvider” type. This is a pre-created module for inspecting the registry on a target, and then adding an expression to add it to our new class if it finds a “match” in the registry. Name the Module ID “DS”, which is simply giving our DataSource a name:
Next – we need to configure the registry discovery. Set the interval to something short for testing (like 60 seconds), but for production use this should never be more frequent than once every 4 hours (14400 seconds)
Next – add a registry attribute on the Registry Probe configuration tab. We can use a key, or a value. We can inspect it for existence, or for a specific expression against the contents of the value. For my example – I am looking for the existence of the “HKLM\SOFTWARE\Opalis\Opalis Integration Server\Action Server” key. I choose Key, and then give my attribute a name like “OpalisASExists”. Fill in the registry info – taking care to notice that “HKLM\” is already assumed. I set my attribute type to “Check if exists” in this case.
On the Expression page – we will Insert, and choose our new attribute, setting it to “Equals” and “true”. This simply means that the existence of our reg key will be true or false, and we want to discover instances that have that specific key. That means they are Opalis Action Servers.
Lastly – on the discovery mapper tab – select your new custom class ID. We need to fill in the key and non key properties using a veriable from the flyout on the right. I will match up the Key property of Microsoft.Windows.Computer\PrincipalName to the appropriate variable on the right – which will be $Target/Property[Type="Windows!Microsoft.Windows.Computer"]/PrincipalName$. For the non key property, this is optional, but if we don’t fill this out – our discovered property for this column “DisplayName” will default to the ID of the class, which is ugly and provides no benefit. For this reason, I like to set this to $Target/Property[Type="Windows!Microsoft.Windows.Computer"]/NetworkName$ using the flyout on the right.
Let’s Click OK, and save our work to a file. This is also a good time to review what we have done thus far.
We have created a new MP. We have created a class for our Opalis Action Server. We have then created a discovery to discover and populate the class instances, and any properties of the class we want. At this point – if we imported the management pack in a test environment – we should be able to discover any Opalis Action servers. We can verify this using Discovered inventory.
Step 3: Add a new property to our existing class, and create a discovery for that new WMI property
Next up – adding a property to the class – to discover the service name. If the service name varies from computer to computer, we need to discover the name. This way – we can pass the discovered service name to the Unit Monitor workflow which monitors the service status.
Back to the authoring console!
Select the Service Model pane, then Classes, then bring up the properties of our newly created class.
Choose the Properties tab. Right Click – and choose “Add Property”. Lets call this one “OpalisASServiceName”. Then – give it a nice display name to be seen in the console. Such as “Action Service Name”. Leave the rest at defaults and click OK.
Now – we need to add an additional discovery to populate this class property. If we could easily get the service name from the registry, we could just add to our existing registry discovery that we already have, to discover and populate instances of the class. Instead, in this example – we will author an additional discovery, and use WMI to populate the information.
Go to the Health Model pane, and Discoveries. Add a new > Custom Discovery. I will call mine “Example.Application.OpalisActionServiceNameDiscovery”
Give the discovery a good display name. I used “Opalis Action Server Service Name Discovery”
Choose an appropriate target. For this discovery – we ONLY want it to run on previously discovered Opalis Action Servers, so we can target the discovery to the same class we are adding the property information to.
For the discovered classes – we will add our existing custom class. Then we can right click that class – and add the property which we will discover in THIS specific discovery – which is the OpalisASServiceName property.
For the Configuration tab – we need to Browse for a Type. We know we need to get this information from WMI – so in the “Look for:” box – type in WMI. We want to use the “Microsoft.Windows.Discovery.WMISinglePropertyProvider2”. How do you know that? Well – we are discovering a property – so that makes sense. The other ones sound overloy complex. So – we can guess – or we can look up the different providers and make sure we are picking a good one from the MSDN Module Reference where we can read about the specifics of the Microsoft.Windows.Discovery.WMISinglePropertyProvider2
Picking the right module type is probably the hardest part…. the best recommendation I can get (and what I often do) is look through other MP’s and see how they do it, what modules they used, and how they passed data to them, as a reference. You will find there is only a handful that you will use on a regular basis.
So – pick this module, and give your Module ID (Data Source) a name. I typically just put in “DS” for DataSource.
Click OK, and we will see the default, unconfigured module type config. We could fill this all in on this screen, but at this point it is just easier to go to XML. Click the “Edit” button below, which will bring up the XML snippet in notepad. If you havent ever used the Auth Console before – you will need to choose Notepad as your default editor.
Here is the default sample of XML for this provider:
<NameSpace>NameSpace</NameSpace> <Query>Query</Query> <Frequency>0</Frequency> <ClassID>ClassID</ClassID> <PropertyName>PropertyName</PropertyName> <InstanceSettings></InstanceSettings>
We need to pass the correct information to the provider, for what we want to discover, which is the Opalis Action Server Service Name.
Here is an example of how to get this information from WMI. Open WBEMTEST on the server which has the service you are looking to discover.
Connect to root\cimv2
Develop a query – using wildcards – which will allow you to discover a service that might have random text in the service name. My service name for Opalis is OpalisActionService. For this example I will pretend that part of the name contains random characters:
Hit the “Query” button in WBEMTEST and use something like: Select Name from Win32_Service where Name like 'OpalisAc%Service'
Test your query and ensure it only returns the discovery property data you are looking for, and won’t return multiple results.
Now that we have a working query – we are ready to input the XML, replacing the default snippet in between the <Configuration…..> and </Configuration>
<NameSpace>root\cimv2</NameSpace> <Query>Select Name from Win32_Service where Name like 'OpalisAc%Service'</Query> <Frequency>60</Frequency> <ClassID>$MPElement[Name="Example.Application.OpalisActionServer"]$</ClassID> <PropertyName>OpalisASServiceName</PropertyName> <InstanceSettings> <Settings> <Setting> <Name>$MPElement[Name="Example.Application.OpalisActionServer"]/OpalisASServiceName$</Name> <Value>$Data/Property[@Name='Name']$</Value> </Setting> <Setting> <Name>$MPElement[Name="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Name> <Value>$Target/Host/Property[Type="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Value> </Setting> </Settings> </InstanceSettings>
Namespace is the WMI namespace to connect to.
Query is the WMI WHQL query to execute which returns the property you are looking for.
ClassID is a reference to which class the discovery is returning data for. It will be in the format of $MPElement[Name="ClassID"]$
PropertyName is just a name for the discovered property, in this case it doesn’t matter what you use.
InstanceSettings are the class properties we need to populate, and the source of the information we will get it from. In this case – you can see we are populating the “OpalisASServiceName” class property with $Data/Property[@Name='Name']$ which is “Name” from the WMI select statement.
So – paste this into your discovery configuration in notepad – it should look something like this below, then hit ok:
Hit OK to save you discovery configuration. Now we should have two complete discoveries, and 1 class.
At this point, it might be a good idea now to increment our MP Version (File > Management Pack Properties > Version) and then save it to a file. Then – we can import it into a test environment/LAB where we can ensure it is working as designed.
I will import it, wait about 15 minutes to send to my agents, run the discoveries, and report back data. Then – I will open Discovered Inventory, and change the target type to my custom class “Opalis Action Server”
I now have a new property, and it discovered the full service name of the service!
Whew! The hard work is over.
Step 4: Creating a service monitor for our wildcard based service.
This part is really simple. We will be creating a standard service unit monitor, just like any other, however, instead of typing in a service name for the monitored service, we will be using an XPATH variable, which will resolve the service name from the discovered class property.
Back in the authoring console. Select the Health Model pane > Monitors > New > Windows Services > Basic Service Monitor.
Give the monitor an ID, I used Example.Application.ActionServiceMonitor
Give a display name for the monitor, you will see this in the console. I used “Opalis Action Server Service Monitor”
On a side note – I like to always end each workflow ID and Display Name with the name of the workflow type. I find this makes life a lot easier when reading XML, or searching in the console. So rules end with “Rule”, monitors end with “Monitor”, discoveries end with “Discovery”, groups end with “Group” etc….
For the target – chose your custom class we just created.
For Parent Monitor, it is CRITICAL you NOT accept the default – and choose a parent. We default to the root and this is a HUGE mistake…. nothing should ever be saved there. Most of your custom monitors will fall under Availability State or Performance State. Since this is a core service availability, I will choose System.Health.Availability.State
Set the category to match your Parent Monitor.
For the service name – NORMALLY we would input the service name verbatim. This module does NOT support wildcards like * or %. So – in this case, we will instead input an XPATH variable, which is essentially the discovered property for the service name, that we used wildcards to discover.
My example will be:
All you would need to change is the Class ID, and the Property Name in most cases for your custom MP’s, from this example. What happens here, is during runtime of the service monitor, it will resolve this to the name of the discovered property for the service name, and use that in the monitoring workflow, as if it was added explicitly.
Click Finish and we are done!
Now – this is another good time to increment our MP version, and save our work to a file. Then – import it into a test environment, and test for the desired functionality.
If all goes well, our discovered instances of the Opalis Action Server class should now show as monitored and healthy:
And when you stop the Action Server service, we should see a state change for the service monitor:
That’s all there is to it! If you have more than one service, then you would use more than one class property using this technique. Always try and combine multiple items in the same discovery whenever possible.
If you run into trouble at any step of the way – you can compare your XML to my example, which I am attaching below.