Copy a work item type using Azure DevOps API’s
Published: 2022-06-18 09:40:50

We all love how we can manage engagements in Azure DevOps (ADO). We can create Epics, Features, User Stories and track our progress on Kanban boards. You can customize work item types to suit the needs of your business and project with ease. What if you wanted to copy a work item type you created and use it for another business case? There is no way to copy the existing work item to a new work item type. If you only have a few fields, well that’s no big deal, but if you have multiple pages, multiple groups on the page and multiple fields in the groups that becomes a monumental task.

In this article I will explain how to copy a work item using the Azure DevOPs API’s. The documentation shows you how to do the basics of adding a new process, adding a work item type, and adding groups and such but there are a few things missing.

This is how the code is structured. I thought it may be good to understand the flow before diving into the code.

Create new work item type
Create pages for new work item type by looping through target work item type and adding missing pages
Create stages for new work item type
Remove any default stages not in copy from work item
Loop through each page in target work item type
Loop through each section in each page
Loop thru each group in each section
Loop through each control in each group
Add field to new work item type (a control holds only 1 field from what I have seen)
Add group to given section
Add control to given group
Once the new work item type is created you then need to add the stages from the work item you are copying from. This will give you the basic Work Item type with the states added from the work item target. Once the work item type is created by default it will have one page and four sections. From Left to right the first three sections reflect the three columns on the page. I have yet to figure out what the fourth section. By adding the work item types it makes it somewhat easier to add the fields. You just loop through each page in the layout and add the fields.

There are a few undocumented pieces of this puzzle that need to be addressed and a few field types that require special attention. By default, the System.Description field is added to the page on work item creation. If you for some reason renamed that field, you need to deal with this field differently than the others. The Description field will show up as the first group in the first section. This was the biggest revelation and the hardest to discover. The reason is when you look at the UI the description field does not have a group. So, the real issue here is that it’s a multi-line text box (HTML).

How about the multi-line text box somewhere else on the page? Again, remember everything needs to be in a group, but this field is not in a group when you look at it in the UI. So, after a few hours digging through fiddler traffic, I was able to find what was not documented regarding multi-line text fields. You must create a group and add the multi-line field as a control in the group. This was the [piece](https://1fid.com/tiktok-profile-pictures/) of the puzzle I was missing. I found the request that was being sent when a multi-line field got placed on the page and was able to figure out what they were going. Now granted the documentation on adding a group does show that a control can be part of the request, but it doesn’t specify that multi-line text fields are a special case.

Then it’s just add the group using the API as seen at the end of this code block.

```
# multi line text fields cannot be inside a group. they are their own group on the UI
if($grp.controls[0].controlType -eq "HtmlFieldControl")
{
    isMultiLine = $true
    # first add the field to the work item
    $addCtl = @{
       referenceName = $grp.controls[0].id
       order = "$null"
       readOnly = "$false"
       label = $grp.label.Trim()
       visible = "$true"

   # must encapsulate true false in quotes to register
   defaultValue = if($fld.type -eq "boolean")
                    {"$false"}
                    else {""}
   required = if($fld.type -eq "boolean")
                 {"$true"} 
                 else {"$false"} 
 $ctlJSON = ConvertTo-Json -InputObject $addCtl

 # add field to work item type
 # https://docs.microsoft.com/en-us/rest/api/azure/devops/processes/fields/add?view=azure-devops-rest-7.1
 # POST https://dev.azure.com/{organization}/_apis/work/processes/{processId}/workItemTypes/{witRefName}/fields?api-version=7.1-preview.2
 $field = $null
 $fieldURL = $userParams.HTTP_preFix + "://dev.azure.com/" + $userParams.VSTSMasterAcct + "/_apis/work/processes/" + $proc.typeId  + "/workitemTypes/" + $newWKItem.referenceName + "/fields?api-version=7.1-preview.2"
 $field = Invoke-RestMethod -Uri $fieldURL -Method Post -ContentType "application/json" -Headers $authorization -Body $ctlJSON
 Write-Host $field

# now add the Multi line field to the page in a group with no name 
$addGroup = @{
          Contribution = "$null"    
          height = "$null"
          id = "$null"
          inherited = "$null"
          isContribution = "$false"
          label = $grp.label.Trim()
          visible = "$true"
          order = "$null"
          overridden = "$null"
               controls = @( @{
                   contribution = "$null"
                   controlType = "$null"
                   height = "$null"
                   id = $grp.controls[0].id
                   inherited = "$null"
                   isContribution = "$false"
                   label = $grp.controls[0].label.Trim()
                   metadata = "$null"
                   order = "$null"
                   overridden = "$null"
                   visible = "$true"
                   watermark = "$null"
               })

    }
    $grpJSON = ConvertTo-Json -InputObject $addGroup
    # POST https://dev.azure.com/{organization}/_apis/work/processes/{processId}/workItemTypes/{witRefName}/layout/pages/{pageId}/sections/{sectionId}/groups?api-version=7.1-preview.1
    $groupURL = $userParams.HTTP_preFix + "://dev.azure.com/" + $userParams.VSTSMasterAcct + "/_apis/work/processes/" + $proc.typeId  + "/workitemtypes/" + $newWKItem.referenceName + "/layout/pages/" + $pgExists.id + "/sections/" + $newSection.id + "/groups?api-version=7.1-preview.1"   
    $group = Invoke-RestMethod -Uri $groupURL -Method Post -ContentType "application/json" -Headers $authorization -Body $grpJSON
    Write-Host "Multi line field " $group
    $newGrp = $group  
}
```