Check list: creating new work item based on template using SCSM SDK

When you want to apply template on work item it’s extremely easy: you need to call ApplyTemplate method of the EnterpriseManagementObject or EnterpriseManagementObjectProjection class instance (with one exception – template shouldn’t contains activity for Service Requests, Change Requests or Release Records). But what if you want to create new object based on template? From first view it’s look not very hard – just call appropriated constructor. But in fact this is just first step of list of required steps: required or optional.

First of all, you need to select right constructor. You have choice between CreatableEnterpriseManagementObject  (please note: to create NEW object you must use CreatableEnterpriseManagementObject class instead of EnterpriseManagementObject) or EnterpriseManagementObjectProjection. From first view you can think that choice between what you want to do with object later – operate with simple properties only or with relationships too. This is not true. Please note that ALL templates created from SCSM console are created based on Type Projection. This is same type projection that form is target to. For instance, Service Request form’s target is System.WorkItem.ServiceRequestProjection. And each template created for Service Request class (yes, if you remember, when you select target for template in console it’s class, but not type projection. You can be confused when you faced with it first time. I was :) ) will created based on same type projection: System.WorkItem.ServiceRequestProjection. This is very easy to check:

write-host ""
$form = Get-SCSMForm Microsoft.EnterpriseManagement.ServiceManager.Applications.ServiceRequest.Forms.ServiceRequestForm
write-host ("Form's target: " + $form.Target.GetElement().Name)
write-host ""
$template = Get-SCSMObjectTemplate -Name "ServiceManager.ServiceRequest.Library.Template.DefaultServiceRequest"

write-host ("Template's target: " + $template.TypeID.GetElement().Name)
write-host ""

Result:
image

That’s important if you will create your own class\form. You no need to change something to target your template to new type projection.

If we talking about creating new object, the summary of all of this is that we always must use EnterpriseManagementObjectProjection class to create new object.

Step 1. Get template

But if you read documentation properly then you have seen that the only constructor that match our goal is EnterpriseManagementObjectProjection (EnterpriseManagementGroup, ManagementPackObjectTemplate). And before call it we must to get template’s object. To do this we have different options:

  1. Get by internal ID (Guid). This is fasters way in all sense: very simple code and excellent performance.
  2. By Name. This is method have (almost) same performance but required much more code.
  3. By Display Name. This is same as p.2 in volume of code but have much worst performance. Try to avoid it.

From my practice I’m always using option 1. If you want to find internal ID it’s much easily to get it using PowerShell:

Get-SCSMObjectTemplate | ? {$_.DisplayName -eq "Default Service Request"} | select DisplayName, Id

image

To get template by internal ID you need to call GetTemplate method of Template property (please note: here and below I will assume that MG variable of EnterpriseManagementGroup)

ManagementPackObjectTemplate template = MG.Templates.GetObjectTemplate(new Guid("03bc9162-041f-c987-8ce4-a5547cd9ca04"));

But if you want to get template by Name you need to little bit more code:

ManagementPackObjectTemplateCriteria crTemplate = new ManagementPackObjectTemplateCriteria("Name = 'ServiceManager.ServiceRequest.Library.Template.DefaultServiceRequest'");
ManagementPackObjectTemplate template = MG.Templates.GetObjectTemplates(crTemplate).FirstOrDefault();

// the good thing is to check if template is not null
if(template == null)
  throw new Exception("Template not found");

You can change Name in criteria to DisplayName to search by DisplayName. But please note: search by DisplayName have a big performance penalty. To find all properties that can used in criteria just call ManagementPackObjectTemplateCriteria.GetValidPropertyNames() method. Using PowerShell (import SMLets or native cmdlets before call this):

[Microsoft.EnterpriseManagement.Configuration.ManagementPackObjectTemplateCriteria]::GetValidPropertyNames()

Result:

image

Step 2. Create new object

The next step is to create new object. As discussed above, we must use EnterpriseManagementObjectProjection (EnterpriseManagementGroup, ManagementPackObjectTemplate). constructor. The template variable declared at step 1.

EnterpriseManagementObjectProjection emop = new EnterpriseManagementObjectProjection(MG, template);

But unfortunately this is not the end. If you will call Commit method at this moment then your object will created but with some restrictions:

  • there is no prefix will set for object itself and all child activities, if any
  • there is no DisplayName will set
  • there is no Created By

If the last thing is not very important than first two is extremely important.

Step 3. Set prefix for created object

You can set prefix for each work item type using console. Those settings are stored inside of SCSM by using standard classes – each class for each of work item type (except Activity: all activities are use one settings class). Please refer to table above to find class and it’s property for each of work item type.

Work Item Type Class Name Property
Incident System.WorkItem.Incident.GeneralSetting PrefixForId
Service Request System.GlobalSetting.ServiceRequestSettings ServiceRequestPrefix
Change Request System.GlobalSetting.ChangeSettings SystemWorkItemChangeRequestIdPrefix
Release Record System.GlobalSetting.ReleaseSettings SystemWorkItemReleaseRecordIdPrefix
Problem System.GlobalSetting.ProblemSettings ProblemIdPrefix
Manual Activity System.GlobalSetting.ActivitySettings SystemWorkItemActivityManualActivityIdPrefix
Review Activity System.GlobalSetting.ActivitySettings SystemWorkItemActivityReviewActivityIdPrefix
Parallel Activity System.GlobalSetting.ActivitySettings SystemWorkItemActivityParallelActivityIdPrefix
Sequential Activity System.GlobalSetting.ActivitySettings SystemWorkItemActivitySequentialActivityIdPrefix
Dependent Activity System.GlobalSetting.ActivitySettings SystemWorkItemActivityDependentActivityIdPrefix
Runbook Automation Activity System.GlobalSetting.ActivitySettings MicrosoftSystemCenterOrchestratorRunbookAutomationActivityBaseIdPrefix

To get those settings we must get EnterpriseManagementObject and read appropriated property’s value. But before this moment we talking only about classes and nothing about criteria that we can use to find instance of those classes. Lucky for us, all those classes are singleton. That’s mean that can be only one instance of that class and instance will created right after management pack with this class imported to SCSM. The internal ID of created instance is always equal to class id.

Now we are ready to get prefix. But before this, we must somehow to find class of our created work item. To do this we have two options: get template’s target type or check if object is instance of specified class. From first look the option one is most useful: we already have a template’s object and it’s easy to get it target without performance penalty. But this approach has a two disadvantage. The first is that you still need to make call against SDK to find target. The problem here that TypeID property of the template object is just reference. Everything that you can find from this property is ID of the target type. So you still need to call GetElement method and this method will do call to SDK (and database) to find type’s object. But more important is another thing: template’s target is not always target to basic type listed in table above. For instance, if you will create class derived from any simple work item and will create template based on this class. the target will be your custom class, but you must use use settings for Incident class. The excellent example is  Microsoft.SystemCenter.WorkItem.SCOMIncident class: it’s derived from Incident but use the same prefix.

So the best way is use the IsInstanceOf method of the EnterpriseManagementObject class. This method return true if current object is instance of given class or any class derived from given:

// code from Step 1 and 2 ...
if (emo.IsInstanceOf(MG.EntityTypes.GetClass(new Guid("a604b942-4c7b-2fb2-28dc-61dc6f465c68"))))
{
  // this is incident
}

Now we can get setting’s object and fill prefix:

// code from Step 1 and 2 ...
string prefix = "";
if (emop.Object.IsInstanceOf(MG.EntityTypes.GetClass(new Guid("a604b942-4c7b-2fb2-28dc-61dc6f465c68"))))
{
  // this is incident
  EnterpriseManagementObject objSettings = this.MG.EntityObjects.GetObject<EnterpriseManagementObject>(new Guid("613c9f3e-9b94-1fef-4088-16c33bfd0be1"), ObjectQueryOptions.Default);
   prefix = (string)objSettings[null, "PrefixForId"].Value;

} else if // ..... check for other classes

//set prefix
emop.Object[null, "Id"].Value = prefix + (string)emo[null, "Id"].Value;

Step 4. Set status

This is extremely important step, especially for work items with child activities, like Service Request, Change Request and Release Records. The problem here the workflow that handle the activities will not start properly if status of the work item is empty. You must set status depend from work item’s type:

Work Item Type Status Value (enumeration’s name)
Incident IncidentStatusEnum.Active (but in fact, you can set any other)
Service Request ServiceRequestStatusEnum.New
Change Request ChangeStatusEnum.New
Release Record ReleaseStatusEnum.New
Problem ProblemStatusEnum.Active (but in fact, you can set any other)

To set status you must get enumeration and set it as value of the Status property:

// code from Step 1 and 2 ...
if (emo.IsInstanceOf(MG.EntityTypes.GetClass(new Guid("a604b942-4c7b-2fb2-28dc-61dc6f465c68"))))
{
  // this is incident
  // code from Step 3
  emop.Object[null, "Status"].Value = MG.EntityTypes.GetEnumeration(new Guid("5e2d3932-ca6d-1515-7310-6f58584df73e"));
} //... code from Step 4

Step 5. Set prefixes for child activities

Now we must set prefixes for child activities. Keep in mind that one (or more) of child activity can be parallel or sequential activity, so you must set prefixes recursive. To do this you must get all activities added with System.WorkItemContainsActivity relationship. To set prefix I’m usual have created two function: to get all activities recursive and to set prefix for activity:

void SetIdPrefix(EnterpriseManagementObject emo)
{
  string prefix = "";
 // check all prefixes for activity
 EnterpriseManagementObject objSettings = MG.EntityObjects.GetObject<EnterpriseManagementObject>(new Guid("5e04a50d-01d1-6fce-7946-15580aa8681d"), ObjectQueryOptions.Default);
 // set default prefix
  prefix = (string)objSettings[null, "SystemWorkItemActivityIdPrefix"].Value;
  // check more specified atctivities
  // manual activity
if (emo.IsInstanceOf(new Guid("7ac62bd4-8fce-a150-3b40-16a39a61383d")))
                {
                    prefix = (string)objSettings[null, "SystemWorkItemActivityManualActivityIdPrefix"].Value;
                }
                else if // check other types with same way

  emo[null, "Id"].Value = prefix + (string)emo[null, "Id"].Value;
}

void SetIdPrefixToChildActivity(IComposableProjection objProjectionComponent)
{
  ManagementPackRelationship relContainsActivity = MG.EntityTypes.GetRelationshipClass(new Guid("2da498be-0485-b2b2-d520-6ebd1698e61b"));
  foreach (IComposableProjection objCurComponent in objProjectionComponent[relContainsActivity.Target])
  {
    SetIdPrefix(objCurComponent.Object);
    // call recursive
    SetIdPrefixToChildActivity(objCurComponent);
  }
}

// ... Code from Step 1 to 4

SetIdPrefixToChildActivity(emop);

Step 6. Set Display Name

Set display name is very easy task: you need concatenate ID and Title.

//... code from step 1 to 5
emop.Object[null, "DisplayName"].Value = (string)emop.Object[null, "Id"].Value + " - " + (string)emop.Object[null, "Title"].Value;

but keep in mind that you must also set display name for all child activity. So we must add new line to SetIdPrefix function:

void SetIdPrefix(EnterpriseManagementObject emo)
{
  string prefix = "";
 // check all prefixes for activity
 EnterpriseManagementObject objSettings = this.MG.EntityObjects.GetObject<EnterpriseManagementObject>(Constants.Class_System_GlobalSetting_ActivitySettings, ObjectQueryOptions.Default);
 // set default prefix
  prefix = (string)objSettings[null, "SystemWorkItemActivityIdPrefix"].Value;
  // check more specified atctivities
  // manual acvtity
if (emo.IsInstanceOf(new Guid("7ac62bd4-8fce-a150-3b40-16a39a61383d")))
                {
                    prefix = (string)objSettings[null, "SystemWorkItemActivityManualActivityIdPrefix"].Value;
                }
                else if // check other types with same way

  emo[null, "Id"].Value = prefix + (string)emo[null, "Id"].Value;
  // set display name
  emo[null, "DisplayName"].Value = (string)emo[null, "Id"].Value + " - " + (string)emo[null, "Title"].Value;
}

Step 7. Set Created By

This nothing special here, just set relationship. I’m use the [me] token to find current user’s object:

// .. code from Step 1 to 6
string getByTokenCriteria = @"<Criteria  xmlns='http://Microsoft.EnterpriseManagement.Core.Criteria/'>
  <Expression>
    <SimpleExpression>
      <ValueExpressionLeft>
        <GenericProperty>Id</GenericProperty>
      </ValueExpressionLeft>
      <Operator>Equal</Operator>
      <ValueExpressionRight>
        <Token>[me]</Token>
      </ValueExpressionRight>
    </SimpleExpression>
  </Expression>
</Criteria>";

ManagementPackRelationship relCreatedByUser = MG.EntityTypes.GetRelationshipClass(new Guid("df738111-c7a2-b450-5872-c5f3b927481a"));
ManagementPackClass clMSFTADUserBase = MG.EntityTypes.GetClass(new Guid("3567434d-015f-8dcc-f188-0a407f3a2168"));

EnterpriseManagementObjectCriteria crGetUserByDOmainAndName = new EnterpriseManagementObjectCriteria(getByTokenCriteria, clMSFTADUserBase, MG);
EnterpriseManagementObject objCurrentUser = MG.EntityObjects.GetObjectReader<EnterpriseManagementObject>(crGetUserByDOmainAndName, ObjectQueryOptions.Default).FirstOrDefault();

if (objCurrentUser != null)
{
  emop.Add(objCurrentUser, relCreatedByUser.Target);
}

With same way you can set Affected User for incidents, if required.

Step 8. Save changes

Just do it:

emop.Commit();

That’s all. Now your work items created from SDK will look same as created from console.

Share

This entry was posted in How-To and tagged , , . Bookmark the permalink.
%d bloggers like this: