Owners of Configuration Item should review

Anyone who’s dug around in the underpinnings of Service Manager (especially while customizing activities) has probably seen the default bool property called “OwnersOfConfigItemShouldReview” that’s part of the Review activity class, but not exposed on the form. You might also have seen the default relationship System.ConfigItemOwnedByUser that’s on the Configuration Item base class (for Windows computer CIs, this is exposed in the form as the Custodian relation).

Maybe you, like me, have wondered why MS didn’t implement this? it seems a very useful feature. ever heard someone say “if they want to change MY server, they are going to have to ask me FIRST”.

One of my clients requested this functionality as part of their change management process, but we ended up using it in several service offerings, including financial approvals for purchases based on Provance’s Cost Center CIs. I’ve thought about this a couple of times, and I liked the way it worked for my client so much that I spent a weekend writing a clean implementation to share here.

Unrelated note: if you haven’t looked at Provance, I urge you to look into it. we partner with them frequently. Their two keystone products are the ITAM MPs (an excellent extension to the Service Manager CMDB that adds asset life cycle, pre-purchase  assets, software asset management, physical locations, cost centers, financial locations, organizational structure, and a lot of other cool stuff) and the DMP (a massive improvement on the CSV connector that allows you to visually configure data from other sources through type projections).

Prerequisites:

You’ll need the open source SMLets. This PowerShell module implements a lot more functionality then the out-of-the-box SM Snap-ins, and I find them a lot easier to use. This code was written against Beta 4 for System Center 2012 with SP1, however, it should be compatible with later editions.

The Solution:

First, we need to expose this property in the form. this is relatively easy. you don’t even need to extend the class, just customize the form with a new check box control bound to this property. I’d include an example, but only one form customization can exist for each form, and merging form customization is a much harder problem then it appears. you’re better off just doing this for yourself to extend your current customized environment, rather then trying to reuse a demo. don’t forget to Seal the MP, so you can create templates based on the customized form.

Second, we need to implement a workflow to populate the CI owners. I’ve uploaded the PowerShell rewrite. you’ll need to add a new workflow. this workflow should be triggered by a database object update, of the Review Activity Class, where the before status does not equal “In Progress” and the after status does equal “In Progress”.

In this new workflow, you’ll want to add a PowerShell activity, import the provided script from above, then make sure you add a script property “ID”, that is bound to the review activity ID (Note: don’t use the “ID (Internal)” property, which is actually a GUID, but the “ID” property, which is a string of the work item ID, i.e. “RA1234”).

Import-Module SMLets

#get class definitions
$WIClass = Get-scsmclass System.WorkItem$
$ACClass = Get-SCSMClass System.WorkItem.Activity$
$RAClass = Get-SCSMClass System.WorkItem.Activity.ReviewActivity$
$reviewerClass = get-scsmclass System.Reviewer$

#get relationship definitions
$ContainsActivity = Get-scsmrelationshipClass System.WorkItemContainsActivity$
$AffectsCI = Get-SCSMRelationshipClass System.WorkItemAboutConfigItem$
$CIOwnedBy = Get-SCSMRelationshipClass System.ConfigItemOwnedByUser$
$ACHasReviwer = Get-SCSMRelationshipClass System.ReviewActivityHasReviewer$
$ReviewerUser = Get-SCSMRelationshipClass System.ReviewerIsUser$

$ThisAC = Get-scsmObject -class $RACLass -filter "Name -eq $ID"
If ($thisac.OwnersOfConfigItemShouldReview ) {
  #loop through parent Activities until you find the absolute parent work item
  # I.e. any work item that isn't an activity
  $ABSParent = $ThisAC
  while ($ABSParent.IsInstanceOf($ACClass)) {
    $ABSPID = (Get-SCSMRelationshipObject -Bytarget $ABSParent | ? {$_.relationshipid -eq $ContainsActivity.id -and $_.IsDeleted -eq $False}).SourceObject.name
    $ABSParent = Get-scsmObject -class $WIClass -filter "Name -eq $ABSPID"
  }

  #Build a list of current reviewers and a list of empty reviewers
  $CurrentReviewers = new-object System.Collections.ArrayList
  $EmptyReviewers = new-object System.Collections.ArrayList
  $ShouldClearEmptyReviewers = $False
  $ReviewerRels = Get-SCSMRelationshipObject -BySource $ThisAC | ? {$_.Relationshipid -eq $ACHasReviwer.id -and $_.IsDeleted -eq $False}
  if ($ReviewerRels) {  #Early Exit for empty $ReviewerRels
    Foreach ($reviewerRel in $ReviewerRels) {
      #get the reviewer object and user
      $ReviewerObject = get-scsmobject -id $reviewerRel.targetObject.id
      $ReviewUserRel = Get-SCSMRelationshipObject -BySource $ReviewerObject | ? {$_.Relationshipid -eq $ReviewerUser.id -and $_.IsDeleted -eq $False}
      if ($ReviewUserRel) {
        $CurrentReviewer = get-scsmobject -id $ReviewUserRel.TargetObject.id
        #Reviewer Exists, add them to the list
        #there shouldn't be duplicate reviewers on an AC, but don't add duplicates if they exist.
        if ($CurrentReviewers -notcontains $CurrentReviewer) { $res = $CurrentReviewers.Add( $CurrentReviewer ) }
      } Else {
        #found an empty reviewer, add it to the list to be removed
        if ($ReviewerObject) {$Res = $EmptyReviewers.Add( $ReviewerObject ) }
      }
    }
  }

  #build list of owners of CIs relates to Abs parent.
  $AffectedCIs = Get-SCSMRelatedObject $absParent -Relationship $AffectsCI
  $CIOwners = new-object System.Collections.ArrayList
  ForEach ($CI in $AffectedCIs) {
    $Owner = Get-SCSMRelatedObject $ci -Relationship $CIOwnedBy
    #don't add duplicates
    if (($CIOwners -notcontains $owner) -and ($CurrentReviewers -notcontains $owner)) { $res = $CIOwners.Add( $Owner ) }
  }

  Foreach ($owner in $CIOwners) {
    #Check if reviewer already exists
    if ($owner -and ($CurrentReviewers -notcontains $owner)){

      #Create Reviewer object
      $reviewerArgs = @{
        "ReviewerId" = "{0}"
        "Comments" = $null
        "Veto" = $true
        "MustVote" = $true
      }
      $reviewer = new-scsmobject -class $reviewerClass -prop $reviewerArgs -nocommit
      #Relate Reviewer to RA
      $ACRel = new-scsmrelationshipobject -Relationship $ACHasReviwer -source $ThisAC -target $reviewer -nocommit
      #Relate Owner to Reviewer
      $RevRel = new-scsmrelationshipobject -Relationship $ReviewerUser -Source $reviewer -target $Owner -nocommit
      #Commit
      $ACRel.Commit()
      $RevRel.Commit()
      #added a reviewer, so clean up any empty reviewers
      $ShouldClearEmptyReviewers = $true
    }
  }

  #clean up any empty reviewers
  if ($ShouldClearEmptyReviewers -and $EmptyReviewers.Count -gt 0) {
    Foreach ($EmptyReviewer in $EmptyReviewers) {
      Remove-ScsmObject $EmptyReviewer -force
    }
  }
}

In Other News:

Sharp-eye’d veterans of ServiceManager will note that this version has a bug. because it runs after the activity is already in progress, users added by this workflow may not get notifications that are triggered by the AC going to In Progress status, which is a very common way of notifying reviewers. this adds a race condition because notification workflow is triggered on the same activity. Sometimes owner reviewers get notified, sometimes not, depending on which workflow runs first.

If we had access to the out-of-the-box Activity Status workflows, then that would be the ideal place to insert the new behavior, similar to how the out of the box “Line Manager” review works; i.e. the user could be added just prior to the activity actually being set In Progress so subsequent workflows receive the fully updated list of reviewers. Given that the only option available to add that behavior would be to completely replace of all of the out-of-the-box activity workflows, that’s not really feasible at this point. (although it is something I have considered; I’ve never been 100% happy with the way activities work, but that’s another story)

The original client fixed this by adding a separate boolean flag, that triggered the notification workflow. The specifics of that implementation is left to the reader.

Advertisements