I was recently working with a customer on migrating their SCSM 2016 environment to a nice fresh SCSM 2019 UR2 environment and resolving a few of those first implementation mistakes. (See my recent post on Management Pack Naming Best Practices) The biggest part of the migration is the copying of the work items from one environment to another and having the confidence that the results are correct and in fact the two environments are the same.
Using the Cireson Lifecycle Management App makes the migration process much easier as it does all of the heavy lifting for us, but there are many things that might go wrong in the migration process that could throw off the data between the two environments. One major issue can be Workflows that execute on new work items. The reason behind this is the Lifecycle Management App (LMA) doesn’t “Copy” the data but rather re-creates the data in the destination server. So when the workflows run against the data, the workflows will detect the data as new work items. If your environment has a workflow that is just checking for when a work item is “Created” and not verifying other properties, then this workflow will run every time. This can then make adjustments, apply templets or otherwise adjust the data from the source data.
As an example, a workflow that looks for the word “Network” in the title on an IR when it is created and if it exists then apply a template that sets the support group to the Network team.
Now this will work for the majority of the time but when it comes to migration, this workflow will run on all existing IR’s, even those that are closed, because there is no criteria stopping that from running. The correct way to have a workflow like this would to also check for the status and the support group before applying the template.

Setting a more specific set of criteria will reduce the number of incorrect triggers of this workflow and will greatly reduce, but potentially not eliminate, the number of migrated work items that will be impacted when this workflow runs. Technically there could be some IR in the source data that is Active, has the word Network in the title and has been unassigned from a support group, but these are diminishing numbers.
Many customers have custom workflows that will not show up in the Administration > Workflows > Configuration area of the console because any custom authored workflows do not show up in this menu. Custom authored workflows are often used when criteria for the workflow is not supported by the out-of-box wizard. Such as a workflow to run when a user comment has been added to the Work Item.
So how do we know the data source and destination match?
With help from a colleague and good friend, the following SQL query was created to report a count of not only all the work item types but also their statuses. This way we can determine if a workflow is running and changing the status of a work item and therefore throwing our data integrity out the window.
The query grabs each work item type and counts each of the work items and then groups them together based on their status. To ensure that it is human readable it then joins the DisplayString tables to turn Enumeration GUIDs in to actual values.
Once run against both Source and Destination servers the results can be exported to Excel and compared.
with cte_AllWorkItemClasses (ManagedTypeId, BaseManagedTypeId) -- Recursively get all Work Item classes.
as (
SELECT ManagedType.ManagedTypeId, ManagedType.BaseManagedTypeId
FROM ServiceManager.dbo.ManagedType
where ManagedType.ManagedTypeId in ('F59821E2-0364-ED2C-19E3-752EFBB1ECE9') -- System.WorkItem
union all
SELECT ManagedType_Parent.ManagedTypeId, ManagedType_Parent.BaseManagedTypeId
FROM ServiceManager.dbo.ManagedType as ManagedType_Parent
inner join cte_AllWorkItemClasses as ActiveStatusesParent
on ActiveStatusesParent.ManagedTypeId = ManagedType_Parent.BaseManagedTypeId
),
cte_WorkItems as (
select
cte_AllWorkItemClasses.ManagedTypeId
,ManagedType.TypeName
,BaseManagedEntityId_WorkItems.DisplayName
,BaseManagedEntityId_WorkItems.BaseManagedTypeId
,COALESCE(DisplayString_SRStatus.DisplayName, DisplayString_IRStatus.DisplayName, DisplayString_PRStatus.DisplayName, DisplayString_CRStatus.DisplayName, DisplayString_RRStatus.DisplayName,
DisplayString_MAStatus.DisplayName, DisplayString_RAStatus.DisplayName, DisplayString_PAStatus.DisplayName, DisplayString_SAStatus.DisplayName, DisplayString_RBAStatus.DisplayName, DisplayString_DAStatus.DisplayName) as Status
from cte_AllWorkItemClasses
inner join ServiceManager.dbo.ManagedType
on ManagedType.ManagedTypeId = cte_AllWorkItemClasses.ManagedTypeId
and ManagedType.IsAbstract = 0
and IsSealed = 1
and IsExtensionType = 0
inner join ServiceManager.dbo.BaseManagedEntity as BaseManagedEntityId_WorkItems
on BaseManagedEntityId_WorkItems.BaseManagedTypeId = cte_AllWorkItemClasses.ManagedTypeId
left join ServiceManager.dbo.MT_System$WorkItem$ServiceRequest as ServiceRequest
on ServiceRequest.BaseManagedEntityId = BaseManagedEntityId_WorkItems.BaseManagedEntityId
left join ServiceManager.dbo.DisplayStringView as DisplayString_SRStatus on DisplayString_SRStatus.LTStringId = ServiceRequest.Status_6DBB4A46_48F2_4D89_CBF6_215182E99E0F
and DisplayString_SRStatus.LanguageCode = 'ENU'
left join ServiceManager.dbo.MT_System$WorkItem$Incident as Incident
on Incident.BaseManagedEntityId = BaseManagedEntityId_WorkItems.BaseManagedEntityId
left join ServiceManager.dbo.DisplayStringView as DisplayString_IRStatus on DisplayString_IRStatus.LTStringId = Incident.Status_785407A9_729D_3A74_A383_575DB0CD50ED
and DisplayString_IRStatus.LanguageCode = 'ENU'
left join ServiceManager.dbo.MT_System$WorkItem$Problem as Problem
on Problem.BaseManagedEntityId = BaseManagedEntityId_WorkItems.BaseManagedEntityId
left join ServiceManager.dbo.DisplayStringView as DisplayString_PRStatus on DisplayString_PRStatus.LTStringId = Problem.Status_3C8876F4_FCBF_148C_FBAF_4CF4F02C6187
and DisplayString_PRStatus.LanguageCode = 'ENU'
left join ServiceManager.dbo.MT_System$WorkItem$ChangeRequest as ChangeRequest
on ChangeRequest.BaseManagedEntityId = BaseManagedEntityId_WorkItems.BaseManagedEntityId
left join ServiceManager.dbo.DisplayStringView as DisplayString_CRStatus on DisplayString_CRStatus.LTStringId = ChangeRequest.Status_72C1BC70_443C_C96F_A624_A94F1C857138
and DisplayString_CRStatus.LanguageCode = 'ENU'
left join ServiceManager.dbo.MT_System$WorkItem$ReleaseRecord as ReleaseRecord
on ReleaseRecord.BaseManagedEntityId = BaseManagedEntityId_WorkItems.BaseManagedEntityId
left join ServiceManager.dbo.DisplayStringView as DisplayString_RRStatus on DisplayString_RRStatus.LTStringId = ReleaseRecord.Status_F7BFD782_80B2_10C2_04B3_7F4C042DB5D2
and DisplayString_RRStatus.LanguageCode = 'ENU'
left join ServiceManager.dbo.MT_System$WorkItem$Activity$ManualActivity as ManualActivity
on ManualActivity.BaseManagedEntityId = BaseManagedEntityId_WorkItems.BaseManagedEntityId
left join ServiceManager.dbo.DisplayStringView as DisplayString_MAStatus on DisplayString_MAStatus.LTStringId = ManualActivity.Status_8895EC8D_2CBF_0D9D_E8EC_524DEFA00014
and DisplayString_MAStatus.LanguageCode = 'ENU'
left join ServiceManager.dbo.MT_System$WorkItem$Activity$ReviewActivity as ReviewActivity
on ReviewActivity.BaseManagedEntityId = BaseManagedEntityId_WorkItems.BaseManagedEntityId
left join ServiceManager.dbo.DisplayStringView as DisplayString_RAStatus on DisplayString_RAStatus.LTStringId = ReviewActivity.Status_8895EC8D_2CBF_0D9D_E8EC_524DEFA00014
and DisplayString_RAStatus.LanguageCode = 'ENU'
left join ServiceManager.dbo.MT_System$WorkItem$Activity$ParallelActivity as ParallelActivity
on ParallelActivity.BaseManagedEntityId = BaseManagedEntityId_WorkItems.BaseManagedEntityId
left join ServiceManager.dbo.DisplayStringView as DisplayString_PAStatus on DisplayString_PAStatus.LTStringId = ParallelActivity.Status_8895EC8D_2CBF_0D9D_E8EC_524DEFA00014
and DisplayString_PAStatus.LanguageCode = 'ENU'
left join ServiceManager.dbo.MT_System$WorkItem$Activity$SequentialActivity as SequentialActivity
on SequentialActivity.BaseManagedEntityId = BaseManagedEntityId_WorkItems.BaseManagedEntityId
left join ServiceManager.dbo.DisplayStringView as DisplayString_SAStatus on DisplayString_SAStatus.LTStringId = SequentialActivity.Status_8895EC8D_2CBF_0D9D_E8EC_524DEFA00014
and DisplayString_SAStatus.LanguageCode = 'ENU'
left join ServiceManager.dbo.MT_System$WorkItem$Activity$DependentActivity as DependentActivity
on DependentActivity.BaseManagedEntityId = BaseManagedEntityId_WorkItems.BaseManagedEntityId
left join ServiceManager.dbo.DisplayStringView as DisplayString_DAStatus on DisplayString_DAStatus.LTStringId = DependentActivity.Status_8895EC8D_2CBF_0D9D_E8EC_524DEFA00014
and DisplayString_DAStatus.LanguageCode = 'ENU'
left join ServiceManager.dbo.MT_Microsoft$SystemCenter$Orchestrator$RunbookAutomationActivity as RunbookActivity
on RunbookActivity.BaseManagedEntityId = BaseManagedEntityId_WorkItems.BaseManagedEntityId
left join ServiceManager.dbo.DisplayStringView as DisplayString_RBAStatus on DisplayString_RBAStatus.LTStringId = RunbookActivity.Status_8895EC8D_2CBF_0D9D_E8EC_524DEFA00014
and DisplayString_RBAStatus.LanguageCode = 'ENU'
)
select
COUNT(cte_WorkItems.BaseManagedTypeId) as WorkItemCount
,cte_WorkItems.TypeName
,cte_WorkItems.Status
--,cte_WorkItems.DisplayName
from cte_WorkItems
group by TypeName, status
order by TypeName, WorkItemCount
Here is a screen shot of some results that show the count, the status and the class:
A quick match up in Excel can then make any discrepancies jump out for you to act on.
Hope this helps in your next migration project.



