I had an interesting request from a customer. They wanted to collect events that would still continue to be collected, even when the monitored server was in maintenance mode.
Quick Download: https://github.com/thekevinholman/Demo.UnhostedClass
This presents an interesting challenge. When we place the “Windows Computer” object into maintenance mode, this starts a chain of events to occur. First, due to a special relationship, this also places the instances of “Health Service” and “Health Service Watcher” into maintenance mode. This will generate a config update for that agent, which will be sent down to the agent. The agent will process this config, and upon reading it – see that it is being told to unload ALL of its workflows.
Now, this happens because almost every single class instance running on an agent, is *hosted* by the Windows Computer object. Therefore, all workflows targeting all instances that are hosted by Windows Computer also go into maintenance mode, by design.
So what we need to do, is create some kind of instance, that is run on the agent, but is NOT hosted by Windows Computer. Was this even possible? Yes!
We can create a new custom class, and choose a base class of Microsoft.Windows.ApplicationComponent. This is a good base class because it basically inherits no properties or relationships.
We create this class as Abstract=False, Hosted=False, and Singleton=False. Abstract is False because we want a class that will contain instances, and we will not be using this class only as a base class. Hosted is false, because if this was a hosted class, we would have to provide another class to have a relationship, and the maintenance mode would unload the workflows, such as if we had chosen Microsoft.Windows.LocalApplication as a base class. Lastly, Singleton is False because we want multiple instances of this class we are creating, not a singleton instance, such as would be used for a group (group is a class with only one instance – the group itself, which has containment over the objects in the group)
Note: Read more about classes here: http://technet.microsoft.com/en-us/library/ee957039.aspx
Since we are creating a class which is not Singleton, we much define a key property. The key property is simply a unique identifier for each instance of the class. A good thing to populate a key property of a class is the FQDN of the agent OS, because FQDN’s should always be unique in any environment. I will create a key property named “KeyProperty” for simplicity… since I am only creating it because I have to. The SCOM God’s said so. Ok, here is what my class definition looks like: (hint – copy and paste this into your Notepad++ to see the entire code)
<ClassType ID="Demo.UnhostedClass.Class" Accessibility="Internal" Abstract="false" Base="Windows!Microsoft.Windows.ApplicationComponent" Hosted="false" Singleton="false">
<Property ID="KeyProperty" Type="string" Key="true" CaseSensitive="false" Length="256" MinLength="0" />
Next up, I need to discover the instances of my class, and create a special relationship. Discovering the instances is pretty easy. HOWEVER, if I simply discover instances of an unhosted class, these instances will be managed on (or exist on) the management servers (or RMS if you are on SCOM 2007 R2). This is simply because this is the default behavior for any class that is not hosted. (Remember, we could not use an existing hosted class or it would go into maintenance mode).
I need to create a special relationship in a discovery script. This is a special relationship called HealthServiceShouldManageEntity which is defined in the Microsoft.SystemCenter.Library.mp. This is a simple relationship which states that the HealthService “should manage” the discovered entity. This makes the instances of our unhosted class, get managed by the agent healthservice on each machine where we discover an instance! That is exactly what we want.
I need this discovery to do a few things. First, I need to define the discovery in XML (see below, this is very basic)
Next, I need to choose who should run the discovery. I choose my generic target of choice “Windows Server Operating System” or Microsoft.Windows.Server.OperatingSystem.
Then, in the script, I need to define the criteria for discovering an instance of the class. I could use the registry to look for a specific key or value, or WMI to filter based on a specific property…. or I could just create an instance of the class for EVERY Windows Server OS it runs on. I chose the latter, because I want to target all servers for these custom workflows.
Then, I need to discover the special relationship for HealthServiceShouldManageEntity. That part is really simple as you will see from the script below.
I also added to the script, a logging of an event each time the script runs. This will help us troubleshoot to make sure the script is running successfully, and on the agents we’d expect. See more examples at https://kevinholman.com/2009/07/22/scom-101-using-custom-scripts-to-write-events-to-the-opsmgr-event-log-with-momscriptapi-logscriptevent/
I chose VBScript, and not PowerShell, simply because I need to include Windows 2003 and 2008 servers that most likely don’t have PowerShell installed.
<Discovery ID="Demo.UnhostedClass.Class.Discovery" Enabled="true" Target="Windows!Microsoft.Windows.Server.OperatingSystem" ConfirmDelivery="false" Remotable="true" Priority="Normal">
<DiscoveryClass TypeID="Demo.UnhostedClass.Class" />
<DataSource ID="DS" TypeID="Windows!Microsoft.Windows.TimedScript.DiscoveryProvider">
<Arguments>$MPElement$ $Target/Id$ $Target/Host/Property[Type="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Arguments>
' Script: Demo.UnhostedClass.Class.Discovery.vbs
' Date: 1/19/14
' Author: email@example.com
' Purpose: Discovers unhosted Demo.UnhostedClass class
' Creates a HealthServiceShouldManageEntity relationship between the local agent and the
' newly created Demo.UnhostedClass object to cause the object to
' be managed on the local agent instead of the Management Servers.
'Setup variables sent in through script arguments
SourceId = WScript.Arguments(0) 'GUID of discovery calling the script. Provided by the MPElement variable.
ManagedEntityId = WScript.Arguments(1) 'GUID of target object. Provided by the Target/Id variable.
sComputerName = WScript.Arguments(2) 'Name of the computer holding the Store Server or Store Client class.
'Start by setting up API object and creating a discovery data object.
'Discovery data object requires the MPElement and Target/ID variables. The first argument in the method is always 0.
Set oAPI = CreateObject("MOM.ScriptAPI")
Set oDiscoveryData = oAPI.CreateDiscoveryData(0, SourceId, ManagedEntityId)
'===== Log that the script is running for troubleshooting
Call oAPI.LogScriptEvent("Demo.UnhostedClass.Class.Discovery.vbs", 123, 0, "Running discovery script")
'===== Create the instance
'Create an instance of the class and add it to the discovery data.
'The KeyProperty property is required because it is the key property of the class.
Set oUnhostedInstance = oDiscoveryData.CreateClassInstance("$MPElement[Name='Demo.UnhostedClass.Class']$")
oUnhostedInstance.AddProperty "$MPElement[Name='Demo.UnhostedClass.Class']/KeyProperty$", sComputerName
'===== Force object to be managed on agent
'By default, unhosted objects are managed on the Management Servers.
'To manage the object on an agent instead, create an instance of the HealthServiceShouldManageEntity relationship.
'Source of the relationship is the health service of the agent. Target of the relationship is the object itself.
'Create an instance of the health service class.
Set oHealthServiceInstance = oDiscoveryData.CreateClassInstance( "$MPElement[Name='SC!Microsoft.SystemCenter.HealthService']$" )
oHealthServiceInstance.AddProperty "$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$", sComputerName
'Create the relationship and add to the discovery data.
set oShouldManageInstance = oDiscoveryData.CreateRelationshipInstance("$MPElement[Name='SC!Microsoft.SystemCenter.HealthServiceShouldManageEntity']$")
oShouldManageInstance.Source = oHealthServiceInstance
oShouldManageInstance.Target = oUnhostedInstance
'Return the discovery data.
'Sample GUID for testing from the command line
The example is heavily commented above so it should make sense for each step, if you need to make some changes for your specific needs.
That’s IT. When this script runs, it will do three things:
- Log an event in the OpsMgr event logs that it is running.
- Create an instance of our custom, unhosted class “Demo.UnhostedClass.Class”
- Create a relationship on each agent so that the local HealthService will manage the instances of our custom class, and NOT the management servers.
In order to properly test this, I created some ample alert generating event rules, and performance collection rules, which are included in the MP I will attach to this post.
The behavior you will see, is that when you place Windows Computer into maintenance mode, our custom class instances will not go into maintenance mode, and their targeted workflows will continue to run on the agent. HOWEVER, we will not transfer this data to the management servers until maintenance mode has ended. We will generate any alerts at the time of maintenance mode ending, and performance, state, and event data will be inserted into both DB’s at that time. This is because the “HealthService” object is placed into maintenance mode, which unloads internal workflows which are necessary to process the alerts and performance data into the send queues. If you wish to have your custom workflows generate alerts and collect events or performance data IMMEDIATELY, simply ensure when you place your computers into maintenance mode, that you remove the HealthService object from maintenance mode. At any rate, this will ensure that we don’t miss any critical events that we need to collect regardless of maintenance mode windows. We should use our custom class to target these collection / alert rules.
***NOTE – this MP requires that all agents that will host this class, need to have Agent Proxy enabled.
Quick Download: https://github.com/thekevinholman/Demo.UnhostedClass