Populating groups from external sources

Groups are one of the most used areas of functionality in Operations Manager. When you are creating overrides, setting up role based security, scoping views, or running reports you are most likely using groups. Generically speaking, groups allow you to express the fact that an arbitrary number of instances are related in some way. One of the most common scenarios is grouping of servers to express the fact that some servers are for example production servers and some servers are test/dev servers. You can then use the "Production Servers" group when creating a user role to grant monitoring access to these servers in Operations Manager. 

Before we look at how a group can be populated from an external source such as Active Directory, lets examine what is actually a group and what does it mean for an object such as a computer or a database to be a member of a group.  

All groups in Operations Manager are objects whose base type is System.Group. So when you create a new group in Operations Manager, the following MP elements are created for you:

Class - This class is derived from Microsoft.SystemCenter.InstanceGroup (this class is turn derived from System.Group). The class definition inside the MP looks like this:

<ClassTypes>

     <ClassType ID="UINameSpaced20b434de436466f835487b6d855bbe2.Group" Accessibility="Public" Abstract="false" Base="MicrosoftSystemCenterInstanceGroupLibrary!Microsoft.SystemCenter.InstanceGroup" Hosted="false" Singleton="true" />

      </ClassTypes> 

I highlighted the fact that the new class is marked as a singleton. In the computer science world, a singleton class means that there will always be only a single instance of the specified class. In the case of Operations Manager, this means that there is no need to create a discovery to discover an instance of the group. If a class is marked as a singleton, Operations Manager will automatically create a single instance of the class. 

I also highlighted the fact the class is marked as "Hosted=false". By default, when a class is marked as "Hosted=false" instances of this class will be managed by the RMS. Because the instance of the class which represents our group is managed by the RMS, it means that any discoveries/monitors/rules/tasks/diagnostics/recoveries will also run on the RMS. By the way, this explains the problems you see when you target a monitor to a group and it doesn’t work on any agents. 

Discovery - The UI also creates a discovery and this discovery is what actually will make computers or other objects be members of the group. Here is a sample discovery:

<Discovery ID="UINameSpaced20b434de436466f835487b6d855bbe2.Group.DiscoveryRule" Enabled="true" Target="UINameSpaced20b434de436466f835487b6d855bbe2.Group" ConfirmDelivery="false" Remotable="true" Priority="Normal">

        <Category>Discovery</Category>

        <DiscoveryTypes>

          <DiscoveryRelationship TypeID="MicrosoftSystemCenterInstanceGroupLibrary!Microsoft.SystemCenter.InstanceGroupContainsEntities" />

        </DiscoveryTypes>

       <DataSource ID="GroupPopulationDataSource" TypeID="SystemCenter!Microsoft.SystemCenter.GroupPopulator">

          <RuleId>$MPElement$</RuleId>

         <GroupInstanceId>$MPElement[Name="UINameSpaced20b434de436466f835487b6d855bbe2.Group"]$</GroupInstanceId>

          <MembershipRules>

            <MembershipRule>

              <MonitoringClass>$MPElement[Name="MicrosoftWindowsLibrary6163930!Microsoft.Windows.Computer"]$</MonitoringClass>

              <RelationshipClass>$MPElement[Name="MicrosoftSystemCenterInstanceGroupLibrary6163930!Microsoft.SystemCenter.InstanceGroupContainsEntities"]$</RelationshipClass>

              <Expression>

                <RegExExpression>

                  <ValueExpression>

                    <Property>$MPElement[Name="MicrosoftWindowsLibrary6163930!Microsoft.Windows.Computer"]/NetbiosComputerName$</Property>

                  </ValueExpression>

                  <Operator>ContainsSubstring</Operator>

                  <Pattern>prodsrv</Pattern>

            </RegExExpression>

              </Expression>

            </MembershipRule>

          </MembershipRules>

        </DataSource>

      </Discovery>

The above XML might look a little complicated, but it can be explained.

1 - The discovery target is "UINameSpaced20b434de436466f835487b6d855bbe2.Group". One might ask, but how is this going to work? The class represents the group and we have no discoveries to discover this class. The reason this work is that the class is marked as a singleton and this means that the RMS will automatically create an instance of the class. This is why the discovery is going to work.

2 - The first highlighted line tells us that all that this discovery really discovers is relationships of type Microsoft.SystemCenter.InstanceGroupContainsEntities. We can use the following PS command to find out what does this relationship really represent:

(Get-RelationshipClass -Name:'Microsoft.SystemCenter.InstanceGroupContainsEntities').Base.GetElement().Name

The output of the cmdlet is the following: System.Containment

So this basically tells us that all that the discovery above do is create containment relationships. This is interesting because this tells us that the group membership feature is built purely on creating containment relationships between an object that represents a group and the object which is member of the group.

The second highlighted line tells us that this discovery uses a datasource module called "Microsoft.SystemCenter.GroupPopulator". This datasource is at the core of the groups feature in OperationsManager. This datasource uses the configuration that its passed (highlighted in yellow) and generates a T-SQL query which is executed against the Operations Manager DB every 60 seconds. The results of this query are the objects that meet the criteria that the user specified while creating the group. For each one of the objects which the T-SQL query returns, the datasource module creates a containment relationship. The source of the this relationship is the group and the target is the object which met the criteria. One might ask, but how does the datasource know the type of the group to create a containment relationship with? Well that is specified using the GroupInstanceID parameter (highlighted in green)

So if 50 objects met the user specified criteria, the datasource will return 50 relationships. Then these relationships are stored into the DB by the agent running on the RMS. Now we have a group with 50 members.

In essence, all that it means for an object such as a computer to be a member of a group is to have a containment relation whose source is the group and the target is the object. For the groups created in the UI, you can only use the group calculation datasource module which queries the operational DB and based on the results creates these containment relationships. In a lot of the cases, querying the operational database is sufficient, but sometimes you might need to query a different source of information, for example Active Directory. The goal of such query would be to say "Add all computers that are members of an OU or perhaps meets a more complex LDAP query to a group in Operations Manager". One way I have seen people try to make this work is using the OrganizationalUnit property of a computer. While this works for this particular scenario, there are scenarios where using the property will not help you. In such cases, the natural question to ask is "Can I populate a group using information from another source such as a CSV file, CMDB, Active Directory or some other source?". The answer is "Absolutely Yes!". What you need to do is replace the discovery which queries the Operations Manager DB with a discovery that reads a CSV file or does a more complex LDAP query against AD and then using this information return relationship information from you discovery. As long as the discovery is properly written, it will result in the group being populated with the right objects and you can use this group anywhere you use other groups created in the UI. Here is an example of how such discovery would look like:

 <Discovery ID="ADBasedGroupDemo.SampleDiscovery" Enabled="true" Target="SC!Microsoft.SystemCenter.RootManagementServer" ConfirmDelivery="true" Remotable="true" Priority="Normal">

        <Category>Discovery</Category>

        <DiscoveryTypes>

          <DiscoveryClass TypeID="GroupPopulationDemo.ADBasedGroup" />

        </DiscoveryTypes>

        <DataSource ID="DS" TypeID="Windows!Microsoft.Windows.TimedScript.DiscoveryProvider">

  <IntervalSeconds>300</IntervalSeconds>

          <SyncTime />

          <ScriptName>ADBasedGroupDiscovery.vbs</ScriptName>

          <Arguments>$MPElement$ $Target/Id$</Arguments>

          <ScriptBody><![CDATA[Dim SourceId

Dim ManagedEntityId

Dim oAPI

Dim oDiscoveryData

 

SourceId = WScript.Arguments(0)

ManagedEntityId = WScript.Arguments(1)

 

Set oAPI = CreateObject("MOM.ScriptAPI")

Set oDiscoveryData = oAPI.CreateDiscoveryData(0,SourceId,ManagedEntityId)

 

Set objConnection = CreateObject("ADODB.Connection")

 

objConnection.Open "Provider=ADsDSOObject;"

 

Set objCommand = CreateObject("ADODB.Command")

objCommand.ActiveConnection = objConnection

 

objCommand.CommandText = "<LDAP://OU=MS,dc=contoso,dc=com>;(objectCategory=computer);dNSHostName;subtree"

 

Set objRecordSet = objCommand.Execute

 

Set groupInstance = oDiscoveryData.CreateClassInstance("$MPElement[Name='GroupPopulationDemo.ADBasedGroup']$")

 

While Not objRecordSet.EOF

 

Set serverInstance = oDiscoveryData.CreateClassInstance("$MPElement[Name='Windows!Microsoft.Windows.Computer']$")

serverInstance.AddProperty "$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$",objRecordSet.Fields("dNSHostName")

 

Set relationshipInstance = oDiscoveryData.CreateRelationshipInstance("$MPElement[Name='GroupPopulationDemo.ADBasedGroupContainsWindowsComputers']$")

 

relationshipInstance.Source = groupInstance

relationshipInstance.Target = serverInstance

 

oDiscoveryData.AddInstance relationshipInstance

 

objRecordSet.MoveNext

Wend

 

objConnection.Close

 

Call oAPI.Return(oDiscoveryData)

                      ]]></ScriptBody>

          <TimeoutSeconds>120</TimeoutSeconds>

        </DataSource>

      </Discovery>

In this case, the discovery is using the Microsoft.Windows.TimedScript.DiscoveryProvider which I am sure many of you used before to do discovery. The only two differences are:

1 - The discovery only returns containment relationship as this is all we need to populate a group

2 - The discovery is targeted to the RMS. I could of targeted the discovery to the type that represents the group, but for this example I targeted to the RMS to explicitly show that the discovery will run on the RMS.

As you can see all this discovery does is execute an LDAP query and then based on the results it creates containment relationships. In this case the script enumerates all computer objects in the OU called MS and then adds each of these computer objects to my group.

Hopefully this article showed how you can populate groups not only based on information in the Operations Manager DB, but also from other places such as Active Directory or another source of information. Attached to the blog post is a complete MP which shows this example working end to end.

If you have questions or run into any issues making this sample work in your dev/test environment, please let me know.

ADBasedGroupDemo.xml