How to generate an alert and make it look like it came from someone else


 

This capability has been around forever, but I have never seen it documented.  This is a really cool way to generate alerts as if they came from other agents, but target a different agent.

Suppose a scenario:  You have a client/server application (such as a backup program) where a central server logs all the events about success or failed jobs from clients.

This is scenario – we could simply generate alerts targeting the central server, and reading the event log, and bubble up the broken client name from the logs, into the alert.  The challenge becomes, what if some agents are test, or dev, and some are prod?  What if we have already put in place “tiering” of servers by groupings, and we use this to filter which alerts from which servers get ticketed?

There is actually a way to target one instance of a class with a workflow, but to generate alerts as if they came from a different instance of a different class, EVEN if that instance is a different agent altogether!

Let me demonstrate:

The most common write action for generating alerts in rules, is System.Health.GenerateAlert, which is the one commonly used in every Alert Generating rule you typically come across.  It is documented here:  https://msdn.microsoft.com/en-us/library/ee809352.aspx

HOWEVER – there is another write action you can use:  System.Health.GenerateAlertForType. 

This is documented here:  https://msdn.microsoft.com/en-us/library/jj130310.aspx  While we document the modules and a sample XML example, we don’t really give much guidance anywhere on use cases.

This is a really cool write action, which allows us to generate alerts “on behalf” of a different object type, or even a different object type from a different computer!  Let me show the difference:

A typical System.Health.GenerateAlert looks like this:

<WriteAction ID="GenerateAlert" TypeID="Health!System.Health.GenerateAlert"> <Priority>1</Priority> <Severity>1</Severity> <AlertMessageId>$MPElement[Name="Demo.Rule.AlertMessage"]$</AlertMessageId> <AlertParameters> <AlertParameter1>$Data/EventDescription$</AlertParameter1> </AlertParameters> </WriteAction>

As you can see – very simple.  It sets the priority and severity of the alert, references the Alert Message ID (which is the alert name and description configuration) and contains any alert parameters we want to use in the display output (in this case, Event Description is very common).

 

Now, see the System.Health.GenerateAlertForType:

<WriteAction ID="GenerateAlertForTypeWA" TypeID="Health!System.Health.GenerateAlertForType"> <Priority>1</Priority> <Severity>1</Severity> <ManagedEntityTypeId>$MPElement[Name="Example.Client.Class"]$</ManagedEntityTypeId> <KeyProperties> <KeyProperty> <PropertyId>$MPElement[Name="Windows!Microsoft.Windows.Computer"]/PrincipalName$</PropertyId> <IsCaseSensitive>false</IsCaseSensitive> <Value>servername.fqdn.local</Value> </KeyProperty> <KeyProperty> <PropertyId>$MPElement[Name="Example.Client.Class"]/ClientName$</PropertyId> <IsCaseSensitive>false</IsCaseSensitive> <Value>servername.fqdn.local</Value> </KeyProperty> </KeyProperties> <AlertMessageId>$MPElement[Name="Demo.Rule.AlertMessage"]$</AlertMessageId> <AlertParameters> <AlertParameter1>$Data/EventDescription$</AlertParameter1> </AlertParameters> </WriteAction>

The key section here is <ManagedEntityTypeId> and then some <KeyProperties>

In the <ManagedEntityTypeId> we need to reference the CLASS that we want the alert to appear as it is coming FROM.

Then, in the <KeyProperties> we need two sections:

The first key property is mapping the Windows Computer principal name to the fqdn of the agent we want the alert to “appear to be from”.  This part is easy.

The second key property is mapping the SAME fqdn, to a matching property on the CLASS we referenced in <ManagedEntityTypeId>, or a parent base class that has the key property defined.

The second key property is the tough one.  The criteria for this to work (from my testing) is that we MUST have a class with a key property first, and that key property MUST be the fqdn of the agent/server for each instance (or whatever value we are “matching” on.

In most of my classes I create, I don’t create key properties.  Key properties aren't required unless I have a class that will discover multiple instances on the same healthservice (agent).  For stuff I do – this is rarely the case.  However, it is EASY to create a key property for your custom classes, and many Microsoft classes already have key properties.  The big “gotchya” here is that in order to generate an alert for another instance of a class (not the targeted instance), the class we specify MUST have a key property defined for this to work.

So – I simply added a key property of “ClientName” to my custom class, and then to discover it, all I have to do is add some simple code to the discovery which maps the hosting Windows Computer principal name to the property.

Ok…. I know…. I probably lost a lot of you up to this point….. but it is easier to just do it, than it is to understand it.  That’s why I will post my XML examples at a link below.  Smile

 

Here is an example of me adding a custom key property to my custom class:

<ClassType ID="Example.AlertFromAnotherInstance.Client.Class" Accessibility="Public" Abstract="false" Base="Windows!Microsoft.Windows.LocalApplication" Hosted="true" Singleton="false" Extension="false"> <Property ID="ClientName" Type="string" AutoIncrement="false" Key="true" CaseSensitive="false" MaxLength="256" MinLength="0" Required="false" Scale="0" /> </ClassType>

And here is part of the discovery that I will use to map “ClientName” to the hosting Windows Computer principal name:

 

<Discovery ID="Example.AlertFromAnotherInstance.Client.Class.Discovery" Enabled="true" Target="Windows!Microsoft.Windows.Server.OperatingSystem" ConfirmDelivery="false" Remotable="true" Priority="Normal"> <Category>Discovery</Category> <DiscoveryTypes> <DiscoveryClass TypeID="Example.AlertFromAnotherInstance.Client.Class" /> </DiscoveryTypes> <DataSource ID="DS" TypeID="Windows!Microsoft.Windows.FilteredRegistryDiscoveryProvider"> <ComputerName>$Target/Host/Property[Type="Windows!Microsoft.Windows.Computer"]/PrincipalName$</ComputerName> <RegistryAttributeDefinitions> <RegistryAttributeDefinition> <AttributeName>ClientExists</AttributeName> <Path>SOFTWARE\Demo\Client</Path> <PathType>0</PathType> <AttributeType>0</AttributeType> </RegistryAttributeDefinition> </RegistryAttributeDefinitions> <Frequency>86400</Frequency> <ClassId>$MPElement[Name="Example.AlertFromAnotherInstance.Client.Class"]$</ClassId> <InstanceSettings> <Settings> <Setting> <Name>$MPElement[Name="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Name> <Value>$Target/Host/Property[Type="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Value> </Setting> <Setting> <Name>$MPElement[Name="System!System.Entity"]/DisplayName$</Name> <Value>$Target/Host/Property[Type="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Value> </Setting> <Setting> <Name>$MPElement[Name="Example.AlertFromAnotherInstance.Client.Class"]/ClientName$</Name> <Value>$Target/Host/Property[Type="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Value> </Setting> </Settings> </InstanceSettings>

 

So – now all I need to do it write a rule, and use our new write action.

You can write the event rule like my example will do using the console, or any other tool, then simply modify the write action section in XML.

Here is my simple rule:

 

<Rule ID="Example.AlertFromAnotherInstance.Server.Event.Rule" Enabled="true" Target="Example.AlertFromAnotherInstance.CentralServer.Class" ConfirmDelivery="false" Remotable="true" Priority="Normal" DiscardLevel="100"> <Category>EventCollection</Category> <DataSources> <DataSource ID="Microsoft.Windows.EventCollector" TypeID="Windows!Microsoft.Windows.EventCollector"> <ComputerName>$Target/Host/Property[Type="Windows!Microsoft.Windows.Computer"]/NetworkName$</ComputerName> <LogName>Application</LogName> <AllowProxying>false</AllowProxying> <Expression> <And> <Expression> <SimpleExpression> <ValueExpression> <XPathQuery Type="UnsignedInteger">EventDisplayNumber</XPathQuery> </ValueExpression> <Operator>Equal</Operator> <ValueExpression> <Value Type="UnsignedInteger">999</Value> </ValueExpression> </SimpleExpression> </Expression> <Expression> <SimpleExpression> <ValueExpression> <XPathQuery Type="String">PublisherName</XPathQuery> </ValueExpression> <Operator>Equal</Operator> <ValueExpression> <Value Type="String">TEST</Value> </ValueExpression> </SimpleExpression> </Expression> </And> </Expression> </DataSource> </DataSources> <WriteActions> <WriteAction ID="GenerateAlertForTypeWA" TypeID="Health!System.Health.GenerateAlertForType"> <Priority>1</Priority> <Severity>2</Severity> <ManagedEntityTypeId>$MPElement[Name="Example.AlertFromAnotherInstance.Client.Class"]$</ManagedEntityTypeId> <KeyProperties> <KeyProperty> <PropertyId>$MPElement[Name="Windows!Microsoft.Windows.Computer"]/PrincipalName$</PropertyId> <IsCaseSensitive>false</IsCaseSensitive> <Value>$Data/Params/Param[1]$</Value> </KeyProperty> <KeyProperty> <PropertyId>$MPElement[Name="Example.AlertFromAnotherInstance.Client.Class"]/ClientName$</PropertyId> <IsCaseSensitive>false</IsCaseSensitive> <Value>$Data/Params/Param[1]$</Value> </KeyProperty> </KeyProperties> <AlertMessageId>$MPElement[Name="Example.AlertFromAnotherInstance.Server.Event.Rule.AlertMessage"]$</AlertMessageId> <AlertParameters> <AlertParameter1>$Data/Params/Param[1]$</AlertParameter1> <AlertParameter2>$Data/EventDescription$</AlertParameter2> </AlertParameters> </WriteAction> </WriteActions> </Rule>

 

The rule is simple.  It simply looks in the Application Event log for an event ID 999, with a event source of “TEST”.  If found, run the write action.  If you scroll down, you can see the write action part, which I will explain:

 

In my rule, I am targeting the workflow to run on the “Server” class.  However, in my write action, I want the alert generated by instances of the “Client” class.  So on my <ManagedEntityTypeId> line, I am using Example.AlertFromAnotherInstance.Client.Class which is my client class ID.

Next, I map the key property for Windows Computer (Principal Name) to the machine I want to appear to generate the alert.  In this case, the name of the affected machine is in Param 1 of my test event, so I am mapping whatever name is in Param1 of the event to generate the alert.

Next, I map the key property of my custom class to the SAME FQDN value.

That’s it!

 

In this example – I create an event on my “Server”, and param 1 of the event will have the name of the client I want the alert to come from:

image

 

Note:  in the above image – the event was logged on a Server named “STORAGE.opsmgr.net” but param1 contained a name of “RD01.opsmgr.net”.

As long as RD01.opsmgr.net hosts an instance of my “Client” class, an alert will be generated as if it came from this server:

 

image

 

 

If you want to test my example XML out in your own environment, simply create some reg keys to be the “Server” and the “Client” instances to be discovered:

HKEY_LOCAL_MACHINE\SOFTWARE\Demo\Server

HKEY_LOCAL_MACHINE\SOFTWARE\Demo\Client

 

The example management pack is available for download at:  https://gallery.technet.microsoft.com/Management-pack-sample-How-8b6741e3


Comments (5)

  1. Ian Smith says:

    Oh man, you lost me at key property..

  2. Love this one Kevin! It would be nice if Microsoft would use this to generate heartbeat alerts on behalf of the Windows Computer or Windows Operating System class instance, instead of using the Health Service Watcher class. Then we don’t have to create Health Service Watcher groups anymore to make heartbeat alerts visible to specific User Scopes based on groups with Windows Computer classes.

  3. Arthur REMY says:

    Great article ! Actually I was looking for this kind of mechanism to answer client needs. Cheers.

  4. sploy says:

    And I’m getting this event alert now:

    An alert couldn’t be inserted to the database. This could have happened because of one of the following reasons:

    – Alert is stale. The alert is generated by an MP recently deleted.
    – Database connectivity problems or database running out of space.
    – Alert received is not valid.

    The following details should help to further diagnose:

    Details: RuleId:5809b8d9-e578-bc39-713b-23e1a7050f87. HealthServiceId:9e252845-969e-d53d-c060-1c6617e5ec07. The INSERT statement conflicted with the FOREIGN KEY constraint “FK_Alert_BaseManagedEntity”. The conflict occurred in database “OpsMgr”, table “dbo.BaseManagedEntity”, column ‘BaseManagedEntityId’.
    The statement has been terminated..

  5. sploy says:

    Hi Kevin. This is very helpful stuff. But, I can’t seem to make it work on my requirement.

    Here’s the story:

    We are planning to purchase Solarwinds NPM. Our only concern is that the alerts in SCOM side only shows the generic alert name – “Orion Active Alerts” and its source is the Server where Solarwinds is installed. What we wanted is (1) Show the actual alert name as seen in the Solarwinds console, and (2) repoint the source to the actual network node from where the alert is coming.

    The solution in your post above is part of the 4 approaches I combined to come up with a Custom Management Pack.

    1. Create powershell discovery based on the network Nodes found by Solarwinds. (OK)
    2. Create an Event and Script Datasource to look for a particular event log. Once found, parse the contents to get NodeName, AlertName and Description for later use in Alert Message. (OK – I created an event log to show when the script was succesful)
    3. Use the solution you posted above, create a GenerateAlertForType to repoint the source of the alert to the actual node. (Can’t confirm if successful)
    4. Use the alert parameters for Dynamic Alert Name and Description. (No alert is showing up in SCOM console)

    I really need your expert opinion on what seems to be the problem with what I’m trying to do.

Skip to main content