Using the SDK to Create and Edit Objects and Relationships Using Type Projections

Using the SDK to Create and Edit Objects and Relationships Using Type Projections

  • Comments 23
  • Likes
One of the things that people often ask about is how to use the Service Manager SDK to:
  • create new objects, 
  • update objects,
  • add relationships, 
  • remove relationships
One of the keys to using the SDK in this way is to use type projections.  
We have had a few blog posts on this in the past in introduce the concepts and examples:
Getting Started with Type Projections
More with Type Projections
Getting and Working with Type Projections – Basic
These blogs posts were a little abstract though.  
This blog post shows you how to work with type projections using incident management as an example. 
The Visual Studio project is attached for reference.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.EnterpriseManagement;
using Microsoft.EnterpriseManagement.Common;
using Microsoft.EnterpriseManagement.Configuration;
using Microsoft.EnterpriseManagement.Packaging;
using Microsoft.EnterpriseManagement.ConnectorFramework;

namespace IncidentTypeProjection
{
    class Program
    {
        static void Main(string[] args)
        {
            //First get a connection to the Management Group
            EnterpriseManagementGroup emg = new EnterpriseManagementGroup("localhost");
            
            //======================================================
            //Example #1 - Creating a single incident object
            //======================================================
            Console.WriteLine("Creating an incident object....");
            //Get the System.WorkItem.Incident class
            ManagementPackClass classIncident = 
                emg.EntityTypes.GetClass(new Guid("A604B942-4C7B-2FB2-28DC-61DC6F465C68")); 
                
            
            //Also get the Medium urgency and impact enums 
            //since they are required values to create a new incident
            //More information on working with enums here: 
            //http://blogs.technet.com/b/servicemanager/archive/2010/05/25/programmatically-working-with-enumerations.aspx 
            ManagementPackEnumeration enumUrgencyMedium = 
                emg.EntityTypes.GetEnumeration(new Guid("02625C30-08C6-4181-B2ED-222FA473280E"));
            ManagementPackEnumeration enumImpactMedium = 
                emg.EntityTypes.GetEnumeration(new Guid("80CC222B-2653-2F68-8CEE-3A7DD3B723C1"));

            //Now create a CreatableEnterpriseManagementObject so we can create a 
            //new incident object and populate its properties and Commit() it.
            CreatableEnterpriseManagementObject cemoIncident = 
                new CreatableEnterpriseManagementObject(emg,classIncident);
            //Just doing this for demo purposes.  Obviously a GUID is not a good display name!
            String strTestID = Guid.NewGuid().ToString();  
            //Set some property values
            cemoIncident[classIncident, "DisplayName"].Value = strTestID;
            cemoIncident[classIncident, "Title"].Value = strTestID;
            cemoIncident[classIncident, "Urgency"].Value = enumUrgencyMedium;
            cemoIncident[classIncident, "Impact"].Value = enumImpactMedium;
            //And submit...
            cemoIncident.Commit();
            Console.WriteLine("Incident object created named: " + strTestID);

            
            //======================================================
            //Example #2 - Creating a Relationship between to Objects - Incidnet and User via the Affected User relationship
            //======================================================
            //Get the incident
            Console.WriteLine("Creating an relationship between the incidena and a user....");
            String strIncidentByTitleCriteria = 
                String.Format(@"<Criteria xmlns=""http://Microsoft.EnterpriseManagement.Core.Criteria/"">" + 
                      "<Expression>" +
                        "<SimpleExpression>" +
                          "<ValueExpressionLeft>" +
                            "<Property>$Target/Property[Type='System.WorkItem.Incident']/Title$</Property>" + 
                          "</ValueExpressionLeft>" + 
                          "<Operator>Equal</Operator>" +
                          "<ValueExpressionRight>" +
                            "<Value>" + strTestID + "</Value>" +
                          "</ValueExpressionRight>" +
                        "</SimpleExpression>" +
                      "</Expression>" +
                    "</Criteria>");

            //System.WorkItem.Incident.Library MP
            ManagementPack mpIncidentLibrary = 
                emg.ManagementPacks.GetManagementPack(new Guid("DD26C521-7C2D-58C0-0980-DAC2DACB0900"));  

            //Get the incident using the criteria from above...
            EnterpriseManagementObjectCriteria emocIncidnetByTitle = 
                new EnterpriseManagementObjectCriteria((String)strIncidentByTitleCriteria,classIncident,mpIncidentLibrary,emg);
            IObjectReader<EnterpriseManagementObject> readerEmoIncidentAffectedUsers = 
                emg.EntityObjects.GetObjectReader<EnterpriseManagementObject>(emocIncidnetByTitle,ObjectQueryOptions.Default);
            EnterpriseManagementObject emoIncident = readerEmoIncidentAffectedUsers.ElementAt(0);

            //Get a user..
            //System.Domain.User class
            ManagementPackClass classUser = 
                emg.EntityTypes.GetClass(new Guid("ECA3C52A-F273-5CDC-F165-3EB95A2B26CF")); 
            IObjectReader<EnterpriseManagementObject> readerEmoDomainUsers = 
                emg.EntityObjects.GetObjectReader<EnterpriseManagementObject>(classUser,ObjectQueryOptions.Default);
            //Just getting whatever the first user is that comes back for demo purposes
            EnterpriseManagementObject emoDomainUser = readerEmoDomainUsers.ElementAt(0); 

            //Create a Relationship between the service and the user
            //System.WorkItemAfectedUser relationship type
            ManagementPackRelationship relIncidentAffectedUser = 
                emg.EntityTypes.GetRelationshipClass(new Guid("DFF9BE66-38B0-B6D6-6144-A412A3EBD4CE"));
            CreatableEnterpriseManagementRelationshipObject cemroIncidentAffectedUser = 
                new CreatableEnterpriseManagementRelationshipObject(emg, relIncidentAffectedUser);
            //Set the source and target...
            cemroIncidentAffectedUser.SetSource(emoIncident);
            cemroIncidentAffectedUser.SetTarget(emoDomainUser);
            //And submit...
            cemroIncidentAffectedUser.Commit();
            Console.WriteLine("Relationship created.");
            
            //======================================================
            //Example #3 - Creating relationships in bulk using Incremental Discovery Data
            //======================================================
            Console.WriteLine("Creating relationships to affected Windows computers...");

            //First get the class and relationship type we want to work with
            //Microsoft.Windows.Computer class
            ManagementPackClass classWindowsComputer = 
                emg.EntityTypes.GetClass(new Guid("EA99500D-8D52-FC52-B5A5-10DCD1E9D2BD"));
            //System.WorkItemAboutConfigItem relationship type
            ManagementPackRelationship relAffectedConfigurationItem = 
                emg.EntityTypes.GetRelationshipClass(new Guid("B73A6094-C64C-B0FF-9706-1822DF5C2E82")); 

            //Now create a "bucket" (IncrementalDiscoveryData class) for putting 
            //updates into so we can submit them all at once.
            IncrementalDiscoveryData iddRelationshipsToAdd = new IncrementalDiscoveryData();
            
            //Get all the Windows computers in the system.  
            //This is not typical.  Just doing this for demo purposes only.
            IObjectReader<EnterpriseManagementObject> readerEmoWindowsComputers = 
                emg.EntityObjects.GetObjectReader<EnterpriseManagementObject>(classWindowsComputer,ObjectQueryOptions.Default);
            foreach (EnterpriseManagementObject emoWindowsComputer in readerEmoWindowsComputers)
            {
                CreatableEnterpriseManagementRelationshipObject cemroAffectedConfigurationItem = 
new CreatableEnterpriseManagementRelationshipObject(emg, relAffectedConfigurationItem);
                //Set the source and target...
                cemroAffectedConfigurationItem.SetSource(emoIncident);
                cemroAffectedConfigurationItem.SetTarget(emoWindowsComputer);
                //Add it to the bucket...
                iddRelationshipsToAdd.Add(cemroAffectedConfigurationItem);
            }

            //And submit...
            iddRelationshipsToAdd.Overwrite(emg);
            Console.WriteLine("Relationships created.");

            
            //======================================================
            //Example #4 - Getting type projection objects and iterating through them
            //======================================================
            Console.WriteLine("Getting incidents and showing the users computers that are affected by them...");
            
            //Getting incidents and then list the computers and users they are affecting
            //System.WorkItem.Incident.ProjectionType
            ManagementPackTypeProjection mptpIncident = 
                emg.EntityTypes.GetTypeProjection(new Guid("285CB0A2-F276-BCCB-563E-BB721DF7CDEC")); 
            ObjectProjectionCriteria opcIncident = new ObjectProjectionCriteria(mptpIncident);
            IObjectProjectionReader<EnterpriseManagementObject> oprIncidents = 
                emg.EntityObjects.GetObjectProjectionReader<EnterpriseManagementObject>(opcIncident, ObjectQueryOptions.Default);
            foreach (EnterpriseManagementObjectProjection emopIncident in oprIncidents)
            {
                if (emopIncident[relIncidentAffectedUser.Target].Count > 0)
                {
                    //Show the affected user
                    Console.WriteLine(emopIncident.Object.DisplayName);
                }
                foreach (IComposableProjection icpAffectedConfigurationItem in emopIncident[relAffectedConfigurationItem.Target])
                {
                    //Show each of the affected configuration items
                    Console.WriteLine("\t" + icpAffectedConfigurationItem.Object.DisplayName);
                }
            }

            Console.WriteLine("Done showing incidents with related users and computers.");
            
            
            //======================================================
            //Example #5 - Creating objects and relationships at the same time via type projection
            //======================================================
            Console.WriteLine("Creating an incident and its relationships via a type projection...");
            //First create the seed object
            EnterpriseManagementObjectProjection emopIncidentToCreate = new EnterpriseManagementObjectProjection(emg, classIncident);

            //Just using this for testing.  Obviously using a GUID for a incidnet display name is not a good idea...
            String strIncidentName = Guid.NewGuid().ToString(); 
            //Set some properties on the object
            emopIncidentToCreate.Object[classIncident, "DisplayName"].Value = strIncidentName;
            emopIncidentToCreate.Object[classIncident, "Title"].Value = strIncidentName;
            emopIncidentToCreate.Object[classIncident, "Impact"].Value = enumImpactMedium;
            emopIncidentToCreate.Object[classIncident, "Urgency"].Value = enumUrgencyMedium;

            //Then relate it to other objects as needed
            IObjectReader<EnterpriseManagementObject> readerUsers = 
                emg.EntityObjects.GetObjectReader<EnterpriseManagementObject>(classUser, ObjectQueryOptions.Default);
            
            foreach (EnterpriseManagementObject emoUser in readerUsers )
            {
                emopIncidentToCreate.Add(emoUser,relAffectedConfigurationItem.Target);
            }

            //And submit...
            emopIncidentToCreate.Overwrite();
           
            Console.WriteLine("Incident and relationships created...");
            
            
            //======================================================
            //Example #6 - Updating objects and relationships at the same time via type projection
            //======================================================
            Console.WriteLine("Updating an incident and its relationships at the same time via a type projection...");
            //First get the EnterpriseManagmentObjectProjection
            IObjectProjectionReader<EnterpriseManagementObject> oprIncidentsToUpdate = 
                emg.EntityObjects.GetObjectProjectionReader<EnterpriseManagementObject>(opcIncident, ObjectQueryOptions.Default);
            foreach (EnterpriseManagementObjectProjection emopIncident in oprIncidentsToUpdate)
            {
                //Update a property
                emopIncident.Object[classIncident, "DisplayName"].Value = 
                    emopIncident.Object[classIncident, "DisplayName"].Value + " - Updated";
                //Remove a relationship
                foreach (IComposableProjection icpAffectedUser in emopIncident[relIncidentAffectedUser.Target])
                {
                    icpAffectedUser.Remove();
                }
                //Add a relationship
                //Just getting the first user here as an example
                EnterpriseManagementObject emoUserToAdd = 
                    emg.EntityObjects.GetObjectReader<EnterpriseManagementObject>(classUser,ObjectQueryOptions.Default).ElementAt(0); 
                emopIncident.Add(emoUserToAdd, relIncidentAffectedUser.Target);
            }
        }
    }
}
Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Hey Travis,

    I'm wondering when and why you'd want to use emop.Overwrite() rather than emop.Commit(). In one of your many blog posts, I remember seeing a comment for when to use one over the other, but I can no longer find that.

    In my case, I'm adding users to the Approvers list of a Review Activity.

  • @Tim -

    .Commit() is typically used when you are creating new things and .Overwrite() should be used when you want to overwrite something (i.e. update it).  .Commit() will fail if there is already an object created with the same key property values.

  • Hi Travis, is it possible to have an example of how to edit an existing incident property by ID?

    Cheers,

    Rob

  • I couldn't find out how to reference Incident ID but I have since found [Type='WorkItem!System.WorkItem']/Id$ which works. Are these published anywhere?

    Travis, thanks very much for this article, it has allowed me to begin a working solution.

  • @Rob -

    These "generic" properties are "documented" here in this blog post at the beginning:

    blogs.msdn.com/.../using-objectqueryoptions-configuring-object-retrieval-from-the-cmdb-part-ii.aspx

    You can put a $ in front of any of those properties and get to that information like you did for $Id

  • Hi Travis,

    Tim asked you about Commit() and Overwrite(). I find I generally commit for new or existing items and it works ok. I've also tried Overwrite, and it works ok, too.

    The problem I have is when a workitem is open for editing in a form.

    I have custom tasks that make changes and save the workitem. These work ok, but if I then try and save the workitem by clicking OK I either get an error (about the item being changed by another process) or the changes I made manually on the form have been lost when I re-edit the workitem.

    I've tried both Commit() and Overwrite() without much luck. Also, it does not appear possible to refresh a form from a custom task?

    I notice that some of your built-in tasks manage all this ok...

    What am I doing wrong?

    Thanks,

    Rob

  • @Rob -

    The reason mine work in that scenario and yours dont is because I am using some secret squirrel undocumented APIs that update the in memory instance of the object instead of updating the database directly.  The changes that the user makes in this way are committed with any other changes that are made on the form all at the same time and hence there is no error.

    These APIs are not publicly communicated because they are changing namespaces in the next release and any code written against them will break.  The code that uses them will need to be updated and recompiled to work with the next version of SCSM.  If it is absolutely critical for you to use these APIs you can contact me offline to discuss.

  • @Travis - thanks for the honest answer :)

    It isn't critical, no, but it would be nice if R2 has a supported way to do the same thing. For now, I don't allow my custom tasks to be run against workitems that are open in a form, displaying a message intructing the analyst to re-run the task from a view.

    Cheers,

    Rob

  • Hey Travis;

    I was just wondering how to you set the AffectedUser value? to the current user that is raising the ticket?

  • @waran -

    It is similar to this call:

    emopIncidentToCreate.Add(emoUser,relAffectedConfigurationItem.Target);

    except you would get and use a different relationship type instead of relAffectedConfigurationItem.

  • I'm completely lost - I need to retrieve the logged on user but I am puzzled on how to find the guid for the user class...Is this the ManagementPackId?

    Regards,

    Kris

  • @Kris -

    You can look up class GUIDs on the ManagedType table in the ServiceManager database.  See this blog post for more details on the database schema:

    blogs.technet.com/.../service-manager-database-tour-useful-queries.aspx

    You can also just use the GUID from above in my example.  That is the same for all installations of SCSM.

  • Hi Travis,

      I find there are many Guids in your code. Could you tell me why do you choose those Guid value? For example, in Example 1, you choose Guid "A604B942-4C7B-2FB2-28DC-61DC6F465C68" when you get the System.WorkItem.Incident class. Are all incident class Guid A604B942-4C7B-2FB2-28DC-61DC6F465C68, no matter in which MP, or different in different MP. If former, how to get Guid of system class such as incident class, change request class and so on. If later, how to know the Guid when I write code.

      In example 1, you constructed a CreatableEnterpriseManagementObject instance when you created the Incident Class instance. If I construct an EnterpriseManagementObject instance instead, are there any differences?

      Thanks!

  • @Blunt -

    The GUID of MP elements like classes, views, properties, relationship types, etc. are a hash of the following:

    * Management Pack ID

    * Management Pack Public Key Token

    * Management Pack Element ID

    Therefore, in every installation of SCSM (except for internal test builds) the GUIDs for these things are the same.

    You can look up these GUIDs in different tables in the database.  For example the ManagedType table contains the class GUIDs.

  • Hey Travis,

    I'm wondering how can I create an incident with its affected user ( having his display name as a String) through sdk

    Thank you