Helpful/essential tools:
- SMLets – a PowerShell module for use with SCSM. http://smlets.codeplex.com
- Notepad++ - a powerful, free text editor that is useful for editing complex JSON files. http://notepad-plus-plus.org/ (Visual Studio is better, but if you need something free this is a great option)
- JSONLint – useful for validating JSON to make sure it is properly formatted. http://jsonlint.com/
- SCSM Authoring Tool – useful for creating class extensions, custom relationship types, and form customizations. It assumed that the user of this document is already proficient with using the SCSM Authoring Tool. http://www.microsoft.com/en-us/download/details.aspx?id=40896
- Custom Forms documentation for the Cireson Portal - https://support.cireson.com/KnowledgeBase/View/51?selectedtab=enduser#/
Forms in SCSM console and the Cireson Portal are bound to a “type projection” which is a definition of the shape of the data model returned from the SCSM database. For example the incident form is bound by default to a type projection called System.WorkItem.Incident.ProjectionType. This projection includes the “seed” object which is the incident itself and related objects such as the related affected user, assigned to user, affected CIs, action log comments, etc.
This is the “shape” of the System.WorkItem.Incident.ProjectionType type projection:
As you can see the System.WorkItem.Incident is the seed class. This means that the first layer in the shape of the data is the incident object itself and all of its properties (including extended class properties) such as work item ID, title, description, urgency, impact, etc. Then you can see the list of “components”. These are the related objects such as primary owner which is a relationship to the System.User class. The target end point column shows the name of the end point of the relationship between the seed class (System.WorkItem.Incident) and the target class (System.User).
Visually we can think of the data as looking like this:
Note: only three related classes are shown for illustrative purposes. It is also possible to have multiple tiers of relationships.
When a new relationship type is added to the model none of the out of the box type projections are aware of that new relationship. Therefore a new type projection must be created that includes that custom relationship as a component. There are two ways to create a type projection:
- Manually creating the type projection XML using an XML editor. The easiest way to do this is to export out a management pack containing a type projection that is pretty close to what you want to include in your custom type projection and then removing what you don’t want and adding your custom relationship as a component. This is an advanced approach and should only be attempted by people experienced with hand editing management pack XML.
- Customize the form in the Authoring Console. When you add a user picker or a single object picker to the form and bind it to your custom relationship type, this will automatically create a type projection for you behind the scenes in the custom form management pack and will include the custom relationship type as a component.
Using either approach you will end up with a type projection that looks something like the type projection below. Using the authoring tool in this example, I have created the following:
- A custom class called ‘Company’
- A reference relationship between System.WorkItem.Incident and System.User called ‘AlternateContactUser’ where there can be only one user related to each incident but any user can be related to many incidents. This is called “target max cardinality = 1”.
- A reference relationship between System.WorkItem.Incident and Company called IncidentRelatesToCompany. This is also a “target max cardinality = 1” relationship type.
I then used the authoring tool to customize the form to add a tab and put a user picker for AlternateContactUser and a single instance picker for IncidentRelatesToCompany. This automatically created a new type projection for me and added the two components to the default type projection.
<TypeProjection ID="CustomForm_198e8c88_faa1_4da6_bc92_957be346f5df_TypeProjection" Accessibility="Public" Type="Alias_b45f9670_9c26_4c93_862c_27f1b6bc9bfa!System.WorkItem.Incident">
<Component Path="$Context/Path[Relationship='Alias_b45f9670_9c26_4c93_862c_27f1b6bc9bfa!System.WorkItem.IncidentPrimaryOwner']$" Alias="PrimaryOwner" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemAffectedUser']$" Alias="AffectedUser" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemAssignedToUser']$" Alias="AssignedUser" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemCreatedByUser']$" Alias="CreatedByUser" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItem.TroubleTicketClosedByUser']$" Alias="ClosedByUser" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItem.TroubleTicketResolvedByUser']$" Alias="ResolvedByUser" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItem.TroubleTicketHasActionLog']$" Alias="ActionLogs" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItem.TroubleTicketHasUserComment']$" Alias="UserComments" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItem.TroubleTicketHasAnalystComment']$" Alias="AnalystComments" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItem.TroubleTicketHasNotificationLog' TypeConstraint='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItem.TroubleTicket.SmtpNotificationLog']$" Alias="SMTPNotifications" />
<Component Path="$Context/Path[Relationship='Alias_e65a7b6d_8bfb_48fe_aed9_647b3d0f24d9!System.WorkItemContainsActivity']$" Alias="Activities">
<Component Path="$Context/Path[Relationship='Alias_e65a7b6d_8bfb_48fe_aed9_647b3d0f24d9!System.WorkItemContainsActivity' SeedRole='Target']$" Alias="ParentWorkItem" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemCreatedByUser']$" Alias="ActivityCreatedBy" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemAssignedToUser']$" Alias="ActivityAssignedTo" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemAboutConfigItem']$" Alias="ActivityAboutConfigItem" />
Component>
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemRelatesToWorkItem']$" Alias="RelatedWorkItems">
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemAffectedUser']$" Alias="RWIAffectedUser" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemAssignedToUser']$" Alias="RWIAssignedUser" />
Component>
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemRelatesToWorkItem' SeedRole='Target']$" Alias="RelatedWorkItemsSource">
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemAffectedUser']$" Alias="RWIAffectedUser" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemAssignedToUser']$" Alias="RWIAssignedUser" />
Component>
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemAboutConfigItem']$" Alias="AffectedConfigItems" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemRelatesToConfigItem']$" Alias="RelatedConfigItems" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemAboutConfigItem' TypeConstraint='System!System.Service']$" Alias="RelatedServiceRequests" />
<Component Path="$Context/Path[Relationship='Alias_2dc76ec8_6213_46f5_b7e7_94073fdb10fb!System.EntityLinksToKnowledgeDocument']$" Alias="RelatedKnowledgeArticles" />
<Component Path="$Context/Path[Relationship='Alias_42da915f_22b4_4b2d_bc33_b41a644fa533!System.WorkItemHasFileAttachment']$" Alias="FileAttachments">
<Component Path="$Context/Path[Relationship='Alias_be3619e4_18dd_4d49_8a3d_4344215e17b5!System.FileAttachmentAddedByUser']$" Alias="FileAttachmentAddedBy" />
Component>
<Component Path="$Context/Path[Relationship='AlternateContactUser']$" Alias="ComponentAlias_f28cf87b_1b05_4027_b502_c9075a7c2515" />
<Component Path="$Context/Path[Relationship='IncidentRelatesToCompany']$" Alias="ComponentAlias_7f8a8217_9b3d_4d18_9527_4ac1f4e90ca7" />
TypeProjection>
Note: if you are using this approach you should also include the components for displaying SLOs, activities, and file attachments as follows. Each of these are added at the top level of the type projection and are not nested inside of any other component. Listed with each component are the management pack references that need to be included for the MP aliases specified.
SLO related data:
<Component Path="$Target/Path[Relationship='SLACore!System.WorkItemHasSLAInstanceInformation' SeedRole='Source' TypeConstraint='SLACore!System.SLA.Instance.TimeInformation']$" Alias="SLAInstances">
<Component Path="$Context/Path[Relationship='SLACore!System.SLA.Instance.InformationRefersToSLAConfiguration']$" Alias="SLAConfiguration" />
Component>
<Reference Alias="SLACore">
<ID>System.SLA.LibraryID>
<Version>7.5.0.0Version>
<PublicKeyToken>31bf3856ad364e35PublicKeyToken>
Reference>
Activities:
<Component Path="$Target/Path[Relationship='CoreActivity!System.WorkItemContainsActivity']$" Alias="Activities">
<Component Path="$Target/Path[Relationship='WorkItem!System.WorkItemCreatedByUser']$" Alias="CreatedBy" />
<Component Path="$Target/Path[Relationship='WorkItem!System.WorkItemAssignedToUser']$" Alias="AssignedTo" />
<Component Path="$Target/Path[Relationship='WorkItem!System.WorkItemAboutConfigItem']$" Alias="AffectedConfigItems" />
<Component Path="$Target/Path[Relationship='CoreActivity!System.ReviewActivityHasReviewer']$" Alias="Reviewer">
<Component Path="$Target/Path[Relationship='CoreActivity!System.ReviewerIsUser']$" Alias="User" />
<Component Path="$Target/Path[Relationship='CoreActivity!System.ReviewerVotedByUser']$" Alias="VotedBy" />
Component>
Component>
<Reference Alias="CoreActivity">
<ID>System.WorkItem.Activity.LibraryID>
<Version>7.5.0.0Version>
<PublicKeyToken>31bf3856ad364e35PublicKeyToken>
Reference>
<Reference Alias="WorkItem">
<ID>System.WorkItem.LibraryID>
<Version>7.5.0.0Version>
<PublicKeyToken>31bf3856ad364e35PublicKeyToken>
Reference>
File attachments:
<Component Path="$Target/Path[Relationship='WorkItem!System.WorkItemHasFileAttachment']$" Alias="FileAttachments">
<Component Path="$Target/Path[Relationship='SupportingItem!System.FileAttachmentAddedByUser']$" Alias="FileAttachmentAddedBy" />
Component>
<Reference Alias="WorkItem">
<ID>System.WorkItem.LibraryID>
<Version>7.5.0.0Version>
<PublicKeyToken>31bf3856ad364e35PublicKeyToken>
<Reference>
<Reference Alias="SupportingItem">
<ID>System.SupportingItem.LibraryID>
<Version>7.5.1561.0Version>
<PublicKeyToken>31bf3856ad364e35PublicKeyToken>
<Reference>
After importing the management pack I can look up the ID of the type projection using the following SMLets PowerShell command:
(Get-SCSMTypeProjection -Name CustomForm_198e8c88_faa1_4da6_bc92_957be346f5df_TypeProjection).Id
Note: in your environment use the type projection name (the ID attribute highlighted above on the TypeProjection element).
In my environment the GUID resulted as f391770f-b611-957f-9cde-8ddc3f294039. This GUID is generated based on the name of the type projection so the GUID in your environment will be different.
Out of the box, the Cireson Portal uses a certain default type projection which obviously won’t include your custom relationship type(s). In order to add your custom relationships to a form in the Cireson Portal you need to create a custom form and bind it to your custom type projection and assign that custom form to the right set of users.
To begin with, copy the file from C:\inetpub\CiresonPortal\Scripts\Forms\Templates\incident.js (or choose a different workitem form .js file if you are working with a different work item type) to C:\inetpub\CiresonPortal\CustomSpace.
Open the C:\inetpub\CiresonPotral\CustomSpace\incident.js file in a good text editor like Visual Studio or Notepad++.
Collapse down the two forms like this so you can see what you are working with.
Put your cursor before the “Default ”. Now expand the Default form and Shift+Click right after the comma and before the “DefaultEndUser” to select the entire form.
Copy the selected text to your clipboard.
Paste the text on your clipboard in just before the very last curly brace in the file.
Now look for the area in between the end of the DefaultEndUser form definition and your new form that is a copy of the Default form definition.
Add a comma after the DefaultEndUser form definition finaly curly brace and before the “Default” and rename the new “Default” form to something like “CustomForm”.
Now you have a new form definition with the exact same configuration as the default form but that has an ID of CustomForm.
Now you need to target this custom form to the Active Directory group that you want and instruct the Cireson Portal to bind it to your custom type projection.
Log in as an SCSM administrator to the Cireson Portal. Click on your name in the upper right hand corner and click Admin Settings.
At the bottom of the page there is a section for assigning forms to AD groups:
Click the Add button and it will add a row to the table for you to fill out.
In the AD Group with Access textbox enter the username of the group that you want to target the form to. If you want all analysts to see it you can use the same AD group that you configured the cache builder config file to use. Note: This AD group must exist in the Cireson Portal cache DB.
Next specify the Form ID which in this example is ‘CustomForm’.
You can leave the Ordinal as 0. In the case where a given user logging into the portal is a member of more than one group that has a form targeted at it, the ordinal can be used to decide which form takes precedence. The smaller number wins.
You can choose the form type. In this example we will leave it with the default of ‘incident’.
Lastly enter the type projection GUID that you retrieved from SCSM using the SMLets PowerShell command above.
Then click the Update button.
It should look something like this when you are done:
Note: there is no need to also click the Save button at the bottom of the page.
In addition to this form setting if you wish to have all Default Templates use a custom projection as well you will need to update the Admin Setting for the default template to use a template that is based on your custom projection. Instructions to do so can be found in this KB article: https://support.cireson.com/KnowledgeBase/View/88#/
Now, you can log out and log back in. Provided you are a member of the specified group you should be getting the new form. Of course, you won’t be able to tell yet that it is the new form because we haven’t changed anything in the form definition.
Open an incident form and hit F12 in your keyboard to bring up the developer panel in your browser. Select the Console tab. At the command prompt type pageForm.viewModel and hit enter. You will see the view model something like this (depending on your browser):
Click the triangle to expand it:
Then scroll down and click the triangle next to NameRelationship to expand it:
Likely, near or at the bottom of this list of relationships you will find your two relationships. The “Name” attribute will be the same as the Target name of the relationship in the management pack XML:
As you can see the first one is the user relationship and the second one is the relationship to the company class.
Now we need to edit the form definition to add the additional controls to the form. Open up your XML editor where the incident.js file is at.
The easiest way to do this is to copy some existing fields and copy them. For example here I am copying the row of controls that are in the General section of the incident form that contains the support group, assigned to user, and primary owner user.
If I copy and paste that just below, I will have the following (don’t forget the comma between the two row defintions):
Now we can simply edit the second set to bind to what we want. First remove the unnecessary second user picker line since we only need to add one user picker.
We’ll edit the user picker first since it is easiest.
All we need to do is specify the PropertyDisplayName as ‘Alternate contact user’ and change the property name to "Target_05d24629_400f_4646_be3e_8698b0fc7b89" (At least that is what it is in this example. Yours will be different.).
Next we will change the enumeration field to a single object picker and bind it to the company relationship. First, change the DataType attribute to ObjectPIcker. Then change the PropertyDisplayName attribute to ‘Company’ and the PropertyName to "Target_e756a355_4d02_4950_8e59_b7fb820c6074" (At least that is what it is in this example. Yours will be different.). Then rename the EnumId attribute to ClassId since EnumId is relevant only to enumeration fields and we need to specify the ClassId attribute when we are working with an ObjectPicker control. Then we need to change the GUID from the enumeration GUID to the class GUID. You can look up the class ID using an SMLets command like this:
(Get-SCSMClass Company).Id
In your situation the class name will be different.
The result of this command for the Company class in my environment is:
12d84789-7e5a-6302-671b-44fdadd830c2
The result is this:
Now, save the file.
Open the incident form in the Cireson Portal again and hit CTRL + F5 to force an update to the browser cache to download the new form definition.
You should see something like this on your form:
Note: there is a bug currently that we will fix for v3 where the auto suggest as you type in the object picker control (company in this case) displays results of users instead of objects of the class that is configured (company class in this case). If you use the magnifying glass instead to search for the object it will work properly.
Necessary components to make sure are included in your custom type projection
<Component Path="$Target/Path[Relationship='SLACore!System.WorkItemHasSLAInstanceInformation' SeedRole='Source' TypeConstraint='SLACore!System.SLA.Instance.TimeInformation']$" Alias="SLAInstances">
<Component Path="$Context/Path[Relationship='SLACore!System.SLA.Instance.InformationRefersToSLAConfiguration']$" Alias="SLAConfiguration" />
<Component>
SLACore is the alias for this MP:
<Reference Alias="SLACore">
<ID>System.SLA.LibraryID>
<Version>7.5.0.0Version>
<PublicKeyToken>31bf3856ad364e35PublicKeyToken>
<Reference>
<Component Path="$Target/Path[Relationship='CoreActivity!System.WorkItemContainsActivity']$" Alias="Activities">
<Component Path="$Target/Path[Relationship='WorkItem!System.WorkItemCreatedByUser']$" Alias="CreatedBy" />
<Component Path="$Target/Path[Relationship='WorkItem!System.WorkItemAssignedToUser']$" Alias="AssignedTo" />
<Component Path="$Target/Path[Relationship='WorkItem!System.WorkItemAboutConfigItem']$" Alias="AffectedConfigItems" />
<Component Path="$Target/Path[Relationship='CoreActivity!System.ReviewActivityHasReviewer']$" Alias="Reviewer">
<Component Path="$Target/Path[Relationship='CoreActivity!System.ReviewerIsUser']$" Alias="User" />
<Component Path="$Target/Path[Relationship='CoreActivity!System.ReviewerVotedByUser']$" Alias="VotedBy" />
<Component>
<Component>
<Reference Alias="CoreActivity">
<ID>System.WorkItem.Activity.LibraryID>
<Version>7.5.0.0Version>
<PublicKeyToken>31bf3856ad364e35PublicKeyToken>
<Reference>
<Reference Alias="WorkItem">
<ID>System.WorkItem.LibraryID>
<Version>7.5.0.0Version>
<PublicKeyToken>31bf3856ad364e35PublicKeyToken>
<Reference>
Troubleshooting
Symptom: After following the above steps to add a new relationship, the relationship value does not save when the workitem is first opened from template, however saves after the second attempt with the workitem has been opened.
Fix: When creating workitems from template (such as change requests) any existing templates won’t have this new projection ID in the XML of the management Packs. You will need to either create new templates or export the MPs that contain your templates and change the projection IDs.