Good afternoon, readers! Gabriel Taylor here, back with a script I’ve used to fend off an SCSM “feature” I’ve run into with multiple clients which will hopefully help you as well.
Short version: Have you come across times where review activities, despite being approved or rejected by the assigned user, fail to enter the “Completed” state, only to find that the cause is an unassigned reviewer instance sitting in the list of reviewers for which no user has cast any votes? The PowerShell script attached to this blog post is a workflow which removes those blank instance automatically, allowing your request processes to continue unhindered.
The Problem
Back in the early days of Service Manager, there was a situation where in review activities which were saved before any reviewers were assigned were automatically entering the “Completed” state. Since the activity was listed as “Completed”, the Change Managers had to add the correct Reviewers and return the activity to the “In Progress” state before the process could be properly handled. Any time anyone created a review activity (including Change Request containing a review activity) and saved the ticket before populating the list of reviewers, this problem would occur. A fix was created to address the issue, however instead of changing the default behavior of the review activity completion workflow to not automatically complete if no reviewers were present, a different course was taken. Instead, the creation workflow was modified to detect whether or not any reviewers were present and, if not, add a blank reviewer object, ready for assignment to whichever user or group would be responsible.
I assume that the team had a good reason for choosing to implement that solution as a fix, and considering the state of Service Manager at the time, it worked well enough. Since Orchestrator was still Opalis, Service Manager lacked a strong PowerShell interface, and console-generated Workflows couldn’t be used to add users to relationships, it didn’t cause any problems. Change Managers had to manually add reviewers regardless, so it just saved them a fraction of a second per review activity, since they only had to reassign the existing unassigned reviewer instance rather than click the button to create a new one.
Unfortunately, the behavior lives on in the current version, where there are considerably more ways to leverage automation with Service Manager. It is now relatively easy, via PowerShell or Orchestrator, to create workflows that can dynamically assign a reviewer based upon data submitted by the Change or Service Requester. For example, perhaps a different reviewer needs to be assigned to a review activity depending upon the department of the requester. It isn’t very complicated to have a workflow scrape the requester’s data and add the appropriate reviewer to a review activity. The problem is that, since the custom workflow is going to run after the creation of the request, and thus the creation of the review activity, Service Manager will have already added an unassigned reviewer instance to the review activity in question before the custom workflow is able to apply the correct reviewer. This means that after the correct reviewer approves or denies the request, either via email, the Self-Service Portal, or any other interface, the request will fail to proceed because that unassigned reviewer instance will still exist without a vote, preventing Service Manager from properly calculating the approval condition and leaving the review activity in an “In Progress” state until someone happens to notice the problem and remove the unassigned reviewer instance.
For organizations that don’t know about this behavior but implement dynamic reviewer assignment workflows, it can cause a huge headache until the problem is identified.
The Solution
Fortunately, simply removing the unassigned reviewer instances forces Service Manager to recalculate the approval conditions and move the affected request along. Unfortunately, having the Change Managers or Service Desk technicians be responsible for routinely checking on the progress of review activities and manually removing any unassigned reviewer instances is not really an option.
The best way to go about addressing the situation is to automate the removal of unassigned reviewer instances. However, this comes with its own caveat – without deep knowledge of the Service Manager SDK and ability to deal directly with the .NET Framework, the identification of unassigned reviewer instances is not possible with the out-of-box Service Manager PowerShell module or with the Service Manager Integration Pack for Orchestrator. The only solution to automate this today leverages the SMLets PowerShell module available on Codeplex.
Even with SMLets’ additional cmdlets, however, the necessary scripting needed for this task is not the most intuitive. Fortunately, I’ve gone ahead and written the script already. The script is designed to be run at regular intervals as a workflow, whether via a Scheduled Task, a Management Pack Workflow, or Orchestrator. If you’d like to skip the explanation and just download and go, you’ll want to read the next paragraph before scrolling down to the bottom to download. For those who want more details, read ahead!
Please note that if you plan on running the script on a Service Manager management server, nothing needs to be changed or provided to the script for it to work. Set it up as a Scheduled Task and call it a day. If you plan on running it from any other server (such as via Orchestrator), you will want to replace the $SMServer variable’s value with the DNS or NetBIOS name of one of your Service Manager management servers.
The Script
The script is arranged into regions for easy navigation via the PowerShell ISE, though it fortunately isn’t very long. The first region is here:
#region Variables $SMServer = $env:COMPUTERNAME $SMLetsModule = "SMLets" $ClassName = "System.Reviewer" $ProjName = "System.ReviewerProjection" $CritObjType = "Microsoft.EnterpriseManagement.Common.ObjectProjectionCriteria" #endregion
This first region defines a number of variables which will be used later in the script. The only one which you may want to change is the first, $SMServer. As stated above, if the script will not be running on a Service Manager management server, this will need to be changed to the DNS or NetBIOS name of a management server. The other variables all store class and object type names which will be used below. Because the factor defining whether a reviewer instance is assigned or unassigned is a relationship, rather than a property, there isn’t an easy way to query Service Manager for the offending instances. The ObjectProjectionCriteria object type, the full name of which is stored in the $CritObjType variable, will need to be built using data from the type projection relating the Reviewer class to the User class and criteria defining our conditions in order to retrieve the unassigned reviewer instances.
#region XML Criteria ## Declare the criteria defining the Reviewers without Is User users $CriteriaHS = @' <Criteria xmlns="http://Microsoft.EnterpriseManagement.Core.Criteria/"> <Reference Id="System.WorkItem.Activity.Library" PublicKeyToken="31bf3856ad364e35" Version="7.5.0.0" Alias="CoreActivity" /> <Reference Id="System.Library" PublicKeyToken="31bf3856ad364e35" Version="7.5.0.0" Alias="CoreSystem" /> <Expression> <UnaryExpression> <ValueExpression> <GenericProperty Path="$Context/Path[Relationship='CoreActivity!System.ReviewerIsUser' TypeConstraint='CoreSystem!System.User']$">Id</GenericProperty> </ValueExpression> <Operator>IsNull</Operator> </UnaryExpression> </Expression> </Criteria> '@ #endregion
This region defines the XML criteria argument which will be used to build the ObjectProjectionCriteria object. This is the same criteria which would be used in a notification workflow or a view. It states that we are looking for instances of the System.Reviewer class where the System.ReviewerIsUser relationship does not exist. The System.ReviewerIsUser relationship is the relationship which denotes which user or group is assigned to the reviewer instance.
#region Module Import and Preparation ## Set the identity of the default management server for SMLets cmdlets to target to the specified SMServer $smdefaultcomputer = $SMServer ## Import SMLets PowerShell Module Write-Verbose "[$(Get-Date)] Loading SMLets PowerShell module ..." $ExistingModules = (Get-Module | ForEach-Object{$_.name}) -join " " If(!$ExistingModules.Contains($SMLetsModule)) { Import-Module $SMLetsModule -Force -Verbose:$false } ## Get the Class Write-Verbose "[$(Get-Date)] Retrieving the $($ClassName) class ..." $ClassObj = Get-SCClass -ComputerName $SMServer -Name $ClassName ## Get the type projection Write-Verbose "[$(Get-Date)] Retrieving the $($ProjName) type projection ..." $ProjObj = Get-SCSMTypeProjection $ProjName -ComputerName $SMServer ## Create the criteria object Write-Verbose "[$(Get-Date)] Creating the ObjectProjectionCriteria object ..." $CritObj = New-Object $CritObjType $CriteriaHS,$ProjObj.__Base,$ProjObj.ManagementGroup #endregion
This next region imports the SMLets PowerShell module and retrieves the necessary data from Service Manager to build the ObjectProjectionCriteria object, storing it in a variable for usage in the next region.
#region Collect Instances ## Create an array of the returned instances (here they are PSCustomObjects) Write-Verbose "[$(Get-Date)] Retrieving any unassigned Reviewer instances ..." $ProjObjInstances = Get-SCSMObjectProjection -ComputerName $SMServer -criteria $CritObj ## Check to ensure the array isn't empty, then add the ProjObjInstances' IDs to an array $IdArray = @() If($ProjObjInstances.count -ne 0) { ForEach($ObjInst in $ProjObjInstances) { $ObjId = $ObjInst.ReviewerId $IdArray += $ObjId } } #endregion
This region leverages the completed ObjectProjectionCriteria object to finally retrieve all of the unassigned reviewer instances from Service Manager. It then extracts the Id property from each, storing them in an array, so that they can be identified and removed in the next region.
#region Remove Bad Instances ## Check to make sure IdArray isn't empty, then get the reviewers as Enterprise Management Instances and remove them If($IdArray.count -ne 0) { Write-Verbose "[$(Get-Date)] Unassigned Reviewer instances found; removing ..." ForEach($Id in $IdArray) { Get-SCClassInstance -Class $ClassObj -ComputerName $SMServer -Filter "ReviewerId -eq $Id" | Remove-SCClassInstance } } Else { Write-Verbose "[$(Get-Date)] No Unassigned Reviewer instances found; ending script ..." } #endregion
This final section takes any unassigned reviewer instances which were found and loads them again from Service Manager, then removes them. The reason we have to use Get-SCClassInstance to retrieve each object again instead of using the objects returned from Get-SCSMObjectProjection above is that Get-SCSMObjectProjection returns objects of the type “PSCustomObject”, while Remove-SCClassInstance requires “EnterpriseManagementObject” type objects as input. Get-SCClassInstance can leverage the Id properties extracted from Get-SCSMObjectProjection’s results and stored in the $IDArray variable to retrieve the necessary objects to be piped to Remove-SCClassInstance.
With that command, the script completes and all unassigned reviewer instances are removed.
The speed of the script depends primarily on the performance and load of your Service Manager environment, along with the number of unassigned reviewer instances present when it runs. The script does not have a large impact on Service Manager’s performance, fortunately. In the Service Manager environments of the clients at which I have implemented this script, I’ve been able to have the script run automatically every 5 minutes without having any impact on Service Manager’s performance.
Closing Thoughts and Download
If you are now or plan to leverage dynamic reviewer assignment in Service Manager, having the ability to automatically remove the unassigned reviewer instances which will be created will save time and trouble. This script, whether used as-is or adapted into your dynamic reviewer assignment workflow, will enable you to do just that.
If you have any questions or comments, please leave them in the comment box below! Thanks much for reading!
Standard disclaimer: This management pack and its contained workflows and script are not provided by Microsoft and is thus not supported by Microsoft. ALWAYS test any scripts you find on the internet in your Test or Development environment before using in Production!