Objects and entity identifiers
Every single monitored object (class instance), or class, or monitor, or rule, or any other management pack element in SCOM has its own unique identifier. The id is of type of Guid
in PowerShell/C# or uniqueidentifier
in SQL. Looking at a Guid
property (where GUID is an abbreviation for Globally Unique Identifier), it is reasonable to think: “Oh, that must be an object key, randomly generated at the object insert operation, which will be used as a unique object reference in the future”. And it would be a good guess.
However, if like me, you have an exposure to multiple SCOM management groups, you may notice, that classes (for example) always have the same ids. For instance,
Get-SCOMClass -Name Microsoft.Windows.Computer | fl DisplayName, Name, ManagementPackName, Id
will always return the following results:
DisplayName : Windows Computer Name : Microsoft.Windows.Computer ManagementPackName : Microsoft.Windows.Library Id : ea99500d-8d52-fc52-b5a5-10dcd1e9d2bd
Well. May be classes are kind of “standard” elements, therefore they always have the same id. So other elements, especially class instances, should be random. I mentioned class instances explicitly, because they are pure dynamic data: objects/instances which represent your monitored elements, such as servers, their disks, applications, or network nodes, or appliances, or anything else you added to your management group. But this is not true as well. Say, I have a computer named ‘SCOM2019MS.SCOM2019.local’. I registered this computer in two (or more) SCOM management groups. Then I run a command:
Get-SCOMClassInstance -DisplayName "SCOM2019MS.SCOM2019.local" | Where-Object { $_.FullName -ilike "Microsoft.Windows.Computer*" } | fl DisplayName, FullName, id
which will show the following output in any management group:
DisplayName : SCOM2019MS.SCOM2019.local FullName : Microsoft.Windows.Computer:SCOM2019MS.SCOM2019.local Id : 935f3928-aa65-0753-ad6d-485d56491491
OK, now we have proved that ids are not random, but… they still must be unique? Yes, these ids must be unique within their scopes. I.e. class instance id may be the same as a class id, but must be unique among other class instances. How Microsoft managed to make them unique but not random?
How they made it up?
First step begins at the class definition stage. Any SCOM class, where a management pack developer is going to have more then one instance of this class, must have a key property(ies). This is how, Microsoft ensures, that developers always may distinct between instances of the same class. However, class instances ids are globally unique. Well, it should be simple: just add class id as a part of instance id. I.e. class + key property(ies) = uniquer instance id. And the last step: pack that all in a fixed number of digits to make a Guid
record. Best guess here: use a hash algorithm. Now, if we’re right, there must be something that implements this procedure. And it’s here. The OperationsManager
database has a stored function called fn_MPObjectId
, which makes up management pack element ids
-- (C) Microsoft Corporation
CREATE function [dbo].[fn_MPObjectId] (@MPName nvarchar(256), @MPKeyToken nvarchar(32), @ObjectName nvarchar(256))
RETURNS uniqueidentifier
AS
BEGIN
IF @MPName IS NULL RETURN cast( HashBytes('SHA1', 'ObjectId=' + @ObjectName) AS uniqueidentifier)
IF @MPName = @ObjectName RETURN dbo.fn_MPId (@MPName, @MPKeyToken)
IF @MPKeyToken IS NULL RETURN cast( HashBytes('SHA1', 'MPName=' + @MPName + N',ObjectId=' + @ObjectName) AS uniqueidentifier)
RETURN cast( HashBytes('SHA1', 'MPName=' + @MPName + N',KeyToken=' + @MPKeyToken + N',ObjectId=' + @ObjectName) AS uniqueidentifier)
END
or, if we re-write this function in C#
public static Guid GetManagementPackElementId(string mpName, string mpToken, string elementName) => GetGuidFromString(GetManagementPackElementReference(mpName, mpToken, elementName));
public static string GetManagementPackElementReference(string mpName,
string mpToken,
string elementName)
{
if (string.IsNullOrEmpty(mpName))
return $"ObjectId={elementName}";
if (mpName == elementName)
{
if (string.IsNullOrEmpty(mpToken))
return $"MPName={mpName}";
else
return $"MPName={mpName},KeyToken={mpToken}";
}
if (string.IsNullOrEmpty(mpToken))
return $"MPName={mpName},ObjectId={elementName}";
return $"MPName={mpName},KeyToken={mpToken},ObjectId={elementName}";
}
let’s try to run this function with the following parameters:
GetManagementPackElementId("Microsoft.Windows.Library", "31bf3856ad364e35", "Microsoft.Windows.Computer")
this will result in
[ea99500d-8d52-fc52-b5a5-10dcd1e9d2bd]
On the top of that, the database has another function called fn_ManagedTypeId_MicrosoftWindowsComputer
, which simply returns an id:
-- (C) Microsoft Corporation
CREATE FUNCTION [dbo].[fn_ManagedTypeId_MicrosoftWindowsComputer]
()
RETURNS uniqueidentifier
BEGIN
RETURN 'EA99500D-8D52-FC52-B5A5-10DCD1E9D2BD'
END
and this Id is exactly the same Id as we saw in the very beginning.
What’s about class instance Ids?
Now we know how management pack element Ids are generated, and can generate them in our code. But what is about class instances Ids? Unfortunately, there is no stored procedure or function in the SCOM database to make up such id. This is because all inserts are done by Data Access Layer in SCOM base code. Let’s look into it.The Microsoft.EnterpriseManagement.Core.dll assembly has a static class IdUtil
in the Microsoft.EnterpriseManagement.InternalSdkOnly
namespace. This class has everything we need to create a class instance Id:
// (C) Microsoft Corporation
public static Guid GetManagedEntityInstanceIdAsGuid(
Guid guidMEClassId,
ReadOnlyCollection<IdUtil.KeyProperty> keyProperties)
{
if (keyProperties == null || keyProperties.Count == 0)
return guidMEClassId;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("TypeId=");
stringBuilder.Append(guidMEClassId.ToString("B").ToUpperInvariant());
if (keyProperties != null)
{
List<IdUtil.KeyProperty> keyPropertyList = new List<IdUtil.KeyProperty>((IEnumerable<IdUtil.KeyProperty>) keyProperties);
keyPropertyList.Sort();
for (int index = 0; index < keyPropertyList.Count; ++index)
{
IdUtil.KeyProperty keyProperty = keyPropertyList[index];
stringBuilder.Append(",");
stringBuilder.Append(keyProperty.PropertyId.ToString("B").ToUpperInvariant());
stringBuilder.Append("=");
if (keyProperty.IsCaseSensitive)
stringBuilder.Append(keyProperty.Value);
else
stringBuilder.Append(keyProperty.Value.ToUpperInvariant());
}
}
return IdUtil.GetGuidFromString(stringBuilder.ToString());
}
this class has internal access modifier, therefore cannot be used in any external project. Therefore, we need to re-write it in order to be able to create class instance Ids. Let’s do this.
public static T GetObject<T>(this IMonitoringObjectsManagement monitoringObjects, Guid managementPackClassId, IDictionary<Guid, object> keyProperties, ObjectQueryOptions queryOptions) where T: EnterpriseManagementObject
{
StringBuilder hashString = new StringBuilder();
hashString.Append("TypeId=");
hashString.Append(managementPackClassId.ToString("B").ToUpperInvariant());
if (keyProperties != null)
foreach (var keyProperty in keyProperties)
{
hashString.Append(",");
hashString.Append(keyProperty.Key.ToString("B").ToUpperInvariant());
hashString.Append("=");
hashString.Append(keyProperty.Value.ToString());
}
return monitoringObjects.GetObject<T>(GetGuidFromString(hashString.ToString()), queryOptions);
}
This function, actually, does a bit more than just creating an Id — it also returns this instance as a EnterpriseManagementObject
class. managementPackClassId
parameter should be set to the least derived non abstract class Id. Let’s test this function for the example computer object in the beginning. For it, least derived class in Microsoft.Windows.Computer
, key property is PrincipalName
, and key property value is “SCOM2019MS.SCOM2019.local”. These inputs gives us the following string before hashing (principal name is in upper case, because it is not case sensitive by class definition):
TypeId={EA99500D-8D52-FC52-B5A5-10DCD1E9D2BD},{5C324096-D928-76DB-E9E7-E629DCC261B1}=SCOM2019MS.SCOM2019.LOCAL resulting hash: [935f3928-aa65-0753-ad6d-485d56491491]
This matches exactly to the example in the beginning.
How to use this knowledge.
Now we know how to create a management pack element or class instance id. But, is there any practical use this this knowledge? Yes! Absolutely. First of all, as we know that all class Ids are static, we can retrieve class definitions much faster. When you call
Get-SCOMClass -Name "<class.name.here>"
it actually does a text search in the database. Text searches are the most expensive from DB performance perspective. Now compare to:
Get-SCOMClass -Id "Guid"
Second command performs much faster. And it also protects you from duplicates.
Duplicates?
Well, most of management pack developers make sure that their class names and other text ids are globally unique. Usually, company name or author name and a subject is added to all class names a s prefix. However, no one could stop a malicious person from creating a dodgy management pack, with defines another class with name Microsoft.Windows.Computer
. For example:
SCOMQuickGet.GetManagementPackElementId("Dodgy.Windows.Library", "31bf3856ad364e35", "Microsoft.Windows.Computer")
this will give class id of:
[83f59a55-4e3e-8cdc-cac0-06d6b12cb0db]
this id is unique, thus will be successfully inserted into SCOM database. Since that, no one can guaranty, that class-by-name request returns the correct class. So, using Ids for class definition query, we make query faster and reliable (protected).
Querying class instances.
The same performance considerations apply here as well. Because we need to query for class instances more often, this improvement will have more value. If you know all key properties of a class instance, then you can create its Id and retrieve it much faster. Or, even, avoid DB query at all, if an Id was everything, you have been looking for. The above example showed the GetObject
extension method aiming for quicker object queries.
Based on this research, I created a C# class, which helps you build quick queries. You can download this class code from GitHub: SCOMQuickGet.cs
Brilliant article, I always wondered how this worked 🙂
LikeLike