PowerShell for SCSM: Bulk Update Classifications

Bulk updating Service Manager classifications, or for that matter any list value is sometimes required. The easiest way to do this is by using PowerShell and this blog post will outline some scripts that help achieve this.
Classification Design Considerations
But first, it is better to avoid having to make bulk changes. So here are some design considerations for list values, and in particular classifications. In the end it will depend on your organisation business practices, analysts and your reporting needs.
Classifications - less is usually better
Don't create a classification just because you think it might be needed. All list values should be used ie in reports or sometimes for views in the console.
It is easy for business areas to go overboard and have too many classifications that never get used. If you are not going to report on the classification, then there is not much point in getting analysts to set the value.
When there are too many choices, choices that are too similar or different hierarchy paths to similar choices, it is difficult for analysts to be consistent.
You can also have templates that are created by admins that use one value, but analysts logging jobs have, by word of mouth, used a different value. This splits the jobs across two different reporting values.
Check your existing classifications to see if any are not in use. You may be surprised at how analysts are using the values.
It is Easy to Add new Items
Adding new list values is easy and usually is done to add more detail to a higher level or for a new requirement. Analysts just have to start using the new value.
Occasionally, the new value will trigger a bulk update to move some jobs to the new value
And Easy to Rename
Renaming list values is also easy, as you are only changing the display name of the list enumeration (Enum). It does not actually change the Enum value that is stored in the databases by Service Manager.
Sometimes renaming a list value to a display name eg" System - Do Not Use - Original Value" (and move it to the bottom of the list), can be easier than deleting the value. This has the advantage that it will flow on to any groomed jobs in the data warehouse, as the original Enum is still in use.
But Removing List Values is more Complex
Try to avoid removing list values as this will not remove the data in list fields on existing or completed jobs (ie the Enums remain on the job). This can lead to reporting problems where the removed list value shows up with the Enum value rather than the display name.
Removing list values is one of the primary reasons that you may need to make bulk classification changes (and why you are reading this blog). It involves remapping the old list value to a new list value on all jobs in the operational database. You can then remove the list value.
Updating the groomed jobs in the data warehouse is out of scope for this blog post, as the PowerShell commands below will only work on the Operational database.
Enumerations
First we need to deal with Enumerations - the list values.
Each list value is stored as an Enumeration (Enum), this is essentially a flat structure in Service Manager. In the console when you see "List Item 1\List Item 2\List Item 3" but this is a derived value using the parent\child information on the Enum, only the final Enum value is stored in the field on the object.
To view the all Enums in Service Manager you can use:
Get-SCSMEnumeration |Sort Displayname | Select Displayname, Name
This will produce a long list of Enums and it is a bit overwhelming. It gives a list of the Enums, but with no Path information.
It is possible to target one Enum and get all related child Enums i.e. the hierarchy in the list from that point. From the list of all the Enums generated above you could target any specific Enum to see if there were any child Enums.
To get just the Incident classifications you can use the IncidentClassificationEnum as this is the overall parent Enum for the Classification list:
$rootClassificationEnum = Get-SCSMEnumeration -Name IncidentClassificationEnum$
$AllClassificationChildEnum = get-scsmchildenumeration -Enumeration $rootClassificationEnum
$AllClassificationChildEnum | Select DisplayName, Name
And an example for all Tier Queues:
$rootTierQueuesEnum = Get-SCSMEnumeration -Name TierQueuesEnum$
$AllTierQueueChildEnum = get-scsmchildenumeration -Enumeration $rootTierQueuesEnum
$AllTierQueueChildEnum | Select DisplayName, Name
Enumeration ID and Name
Where possible use the ID or Name value of the Enumeration as these are going to be unique. In the scripts below I have sometimes used display name as it is human friendly, but it might not be unique.
If you are doing a bulk updates , you need to be very sure you are updating the correct incidents.I would use the Enum Name or ID where possible just to be sure that you are targeting the correct Incidents.
Bulk Update Classifications: Scripts
These are a number of ways to do things with PowerShell and these scripts are what made sense to me. There maybe better ways or more efficient, but hopefully these will give you a good starting point.
Download the scripts from Technet Gallery here, as coping off this page might contain unwanted HTML tags.
Script 1: Get a List of Classification and Tier Queue Enums
This will produce a list on the screen of the Classification and Tier Queue Enums.
AllClassificationTierQueueEnums.ps1
###################################################################################
#
# Display all Incident Classification and Tier Queue Enumerations
#
###################################################################################
Write-Host
Write-Host "Incident Classification Enumerations"
Write-Host "=================================================================================="
$rootClassificationEnum = Get-SCSMEnumeration -Name IncidentClassificationEnum$
$AllClassificationChildEnum = get-scsmchildenumeration -Enumeration $rootClassificationEnum
$AllClassificationChildEnum | Select DisplayName, Name, ID
Write-Host
Write-Host "Incident Support Group (Tier Queue) Enumerations"
Write-Host "=================================================================================="
$rootTierQueuesEnum = Get-SCSMEnumeration -Name TierQueuesEnum$
$AllTierQueueChildEnum = get-scsmchildenumeration -Enumeration $rootTierQueuesEnum
$AllTierQueueChildEnum | Select DisplayName, Name, ID
Example Output:

Script 2: Enumeration Path of Enum
This script takes a Enum display name and gets the path for that Enum.
This allows you to check if a display name is unique and if not to see where it has been used. You could also modify this script to get the path for all Enums by taking the filter off the $MatchingEnums variable.
EnumPath.ps1
###################################################################################
#
# Gets the Path of any Enum from user input of list item display name
#
###################################################################################
Write-Host ""
$EnumDisplayName = read-host "Enter the Display name of the Enum"
Write-Host ""
Write-Host ""
#Find Matching Enums
$MatchingEnums = Get-SCSMEnumeration | ? {$_.Displayname -eq $EnumDisplayName}
$EnumCount = 0
Write-Host "Results: $($MatchingEnums.Count) Enum Matches for: $EnumDisplayName"
Write-Host "=========================================================="
$EnumPath = ""
#Loop through each Enum that matches the Display Name Entered to get the path
Foreach ($Enum in $MatchingEnums)
{
$EnumPath = $Enum.Displayname
If ($Enum.Parent -ne $null)
{
Do {
#Get the current child's parent info
$Parent = Get-SCSMEnumeration -Id ($Enum.parent.Id)
$EnumPath = $EnumPath + " -> " + $Parent.DisplayName
#Reset the parent to be the next Child in the then loop
$Enum = $Parent
} Until ($Enum.Parent -eq $null)
}# End of childparent not null
$EnumPath
} # End foreach child
Write-Host ""
Write-Host "=========================================================="
Example Output:

Script 3: Dump the current Incident Classifications and Tier Queues
If you are updating classifications or tier queues, it is a good idea to dump the current data first. This provides a backup and a file you can use to roll back to if anything goes wrong. It also provides a file that you can analyse in Excel to work out what data needs to change.
Change the export path to suit your environment: $ExportPath = C:\Temp\IRList.csv
Note: this only gets basic information that is stored directly on the Incident ie no relationships like Assigned To User, Created by User etc.
IREnumDumpCSV.ps1
###################################################################################
#
# Get a list of All Incidents Classification and Tier Queue Enums (no path)
#
###################################################################################
$ExportPath = "C:\Temp\IRList.csv"
$IncidentsArray = @()
$WorkItemClass = Get-SCSMClass System.WorkItem.Incident$
$AllIncidents = Get-SCSMObject -Class $WorkItemClass
ForEach ($Incident in $AllIncidents)
{
$IRExportObject = New-Object System.Object
$IRExportObject | Add-Member -type NoteProperty -name ID -Value $Incident.ID
$IRExportObject | Add-Member -type NoteProperty -name Title -Value $Incident.Title
$IRExportObject | Add-Member -type NoteProperty -name TierQueue -Value $Incident.TierQueue.DisplayName
$IRExportObject | Add-Member -type NoteProperty -name TierQueueEnum -Value $Incident.TierQueue.Name
$IRExportObject | Add-Member -type NoteProperty -name Classification -Value $Incident.Classification.DisplayName
$IRExportObject | Add-Member -type NoteProperty -name ClassificationEnum -Value $Incident.Classification.Name
$IRExportObject | Add-Member -type NoteProperty -name Source -Value $Incident.Source.DisplayName
$IRExportObject | Add-Member -type NoteProperty -name CreatedDate -Value $Incident.CreatedDate
$IncidentsArray += $IRExportObject
} # End of For Each Incident
$IncidentsArray | Sort ID -Descending | Export-csv $ExportPath -NoTypeInformation
Script 4 & 5: Import CSV file to Update Specific Incidents
This script will update a csv file of specific incidents. The script uses specific Enum name values as these are unique. Use the Script 3 above as a starting point to dump the existing values and add a new columns for NewClassificationEnum or NewTierQueueEnum to update the required property.
Example CSV Input file for Updating Classifications

UpdateClassificationFromCSV.ps1
###################################################################################
#
# Import CSV File to Update Incident Classification
#
# Required CSV file Columns ID, NewClassificationEnum
#
###################################################################################
$WorkItemClass = Get-SCSMClass System.WorkItem.Incident$
$UpdatedIncidentsArray = Import-Csv C:\Temp\IRListUpdated.csv
ForEach ($IR in $UpdatedIncidentsArray)
{
$WorkItemObject = Get-SCSMObject -Class $WorkItemClass -Filter "Id -eq $($IR.ID)"
# Remove line below if you DO NOT want to update Classification
$WorkItemObject | Set-SCSMObject -Property Classification -Value $IR.NewClassificationEnum
Write-Host "Updated $($IR.ID)"
} # End for each IR
UpdateTierQueueFromCSV.ps1
###################################################################################
#
# Import CSV File to Update Tier Queues
#
# Required CSV file Columns ID, NewTierQueueEnum
#
###################################################################################
$WorkItemClass = Get-SCSMClass System.WorkItem.Incident$
$UpdatedIncidentsArray = Import-Csv C:\Temp\IRListUpdated.csv
ForEach ($IR in $UpdatedIncidentsArray)
{
$WorkItemObject = Get-SCSMObject -Class $WorkItemClass -Filter "Id -eq $($IR.ID)"
# Remove line below if you DO NOT want to update Support Group (Tier Queue)
$WorkItemObject | Set-SCSMObject -Property TierQueue -Value $IR.NewTierQueueEnum
Write-Host "Updated $($IR.ID)"
} # End for each IR
Script 6 & 7: Update All Classifications that Match a Value
These two scripts will allow you to replace one classification list value with another list value based on their display names or the Enum Name. Be careful as if the scripts matches, it will be updated. The list values must be created before the script runs.
The Display Name script will check to see that both the new and old display names are unique and will produce an error on the screen if they are not. If they are unique it will run and will display on screen the Incidents that were updated.
The Enum Name script assumes that the values are unique and does not check for unique values.
UpdateClassificationDisplayName.ps1
###################################################################################
#
# Get a Specific Enumeration Value and Copy it to ALL Incidents that match a specific Classification
# The script assumes a unique list value for each Display name
#
###################################################################################
##### Change these values to your List Display Names #####
$OriginalEnumDisplayName = "List Value"
$NewEnumDisplayName = "New Value"
# Change this if using a different class from Incident
$rootClassificationEnum = Get-SCSMEnumeration -Name IncidentClassificationEnum$
#Gets all the Enums from the root Incident Classification level
$AllClassificationChildEnum = get-scsmchildenumeration -Enumeration $rootClassificationEnum
# Define the Incident class
$WorkItemClass = Get-SCSMClass System.WorkItem.Incident$
# Get all Incidents
$AllIncidents = Get-SCSMObject -Class $WorkItemClass
#Counters for Enum matches
$NewEnumMatch=0
$OldEnumMatch=0
# This takes both list value Display Names and gets the related Enum object. It sets a counter to check how many Enums match the display name
ForEach ($Enum in $AllClassificationChildEnum)
{
If ($Enum.displayname -eq $NewEnumDisplayName)
{
$NewEnum = $Enum
Write-Host "New Enum: `"$($NewEnum.Displayname)`" with Name: $($NewEnum.name) "
$NewEnumMatch++
} # End of IF
If ($Enum.displayname -eq $OriginalEnumDisplayName)
{
$OldEnum = $Enum
Write-Host "Original Enum: `"$($OldEnum.Displayname)`" with Name: $($OldEnum.name) "
$OldEnumMatch++
} # End of IF
} # End of ForEach Enum
Write-Host ""
Write-Host ""
#Both Enum Display Names have to be Unique for the update to work ie the result should be 2 (`1+1)
$EnumMatch = $NewEnumMatch + $OldEnumMatch
If ($EnumMatch -eq 2)
{
ForEach ($Incident in $AllIncidents)
{
If ($Incident.Classification.DisplayName -eq $OriginalEnumDisplayName)
{
$Incident | Set-SCSMObject -Property Classification -Value $NewEnum.name
Write-Host "Updated $($Incident.ID) to Classification: $NewEnumDisplayName"
} # End of IF
} # End of ForEach Incident
} # End if EnumMatch -eq
Else
{
Write-Host ""
Write-host "This script requires a unique display name for the both list values"
Write-Host " New List Value Matches: $NewEnumMatch"
Write-Host " Old List Value Matches: $OldEnumMatch"
}
UpdateClassificationEnumName.ps1
###################################################################################
#
# Uses a Specific Enumeration Name Value and Copies it to ALL Incidents that match another Enum Name
# As Enum Names are unique, the script does not check for unique values
#
###################################################################################
##### Change these values to your Enums #####
$OriginalEnumName = "Enum.a88bd20ba087494383d7f534fef2fcee"
$NewEnumName = "IncidentClassificationEnum.Email"
# Change this if using a different class from Incident
$rootClassificationEnum = Get-SCSMEnumeration -Name IncidentClassificationEnum$
#Gets all the Enums from the root Incident Classification level
$AllClassificationChildEnum = get-scsmchildenumeration -Enumeration $rootClassificationEnum
# Define the Incident class
$WorkItemClass = Get-SCSMClass System.WorkItem.Incident$
# Get all Incidents
$AllIncidents = Get-SCSMObject -Class $WorkItemClass
# Get the Original and New Enums
ForEach ($Enum in $AllClassificationChildEnum)
{
If ($Enum.Name -eq $OriginalEnumName)
{
$OldEnum = $Enum
Write-Host "Original Enum: $($OldEnum.Name) with Display Name: $($OldEnum.DisplayName) "
} # End of IF
If ($Enum.Name -eq $NewEnumName)
{
$NewEnum = $Enum
Write-Host "New Enum: $($NewEnum.Name) with Display Name: $($NewEnum.DisplayName) "
} # End of IF
} # End of ForEach Enum
Write-Host ""
Write-Host ""
# Loop through all incidents looking for a match on the Original Classification Enum Name and replace with the new Enum
ForEach ($Incident in $AllIncidents)
{
If ($Incident.Classification.Name -eq $OriginalEnumName)
{
$Incident | Set-SCSMObject -Property Classification -Value $NewEnum.name
Write-Host "Updated $($Incident.ID) to Classification: $($NewEnum.DisplayName)"
} # End of IF
} # End of ForEach Incident