Migrating Azure DevOps Projects - Part 2: Boards & Work Items

3 minute read

Migrating Azure DevOps Part 2 - illustration showing Azure Boards, Azure Repos, Azure Pipelines, Azure Test Plans, and Azure Artifacts with Azure Boards highlighted

In Part 1, we covered how to migrate Repos and Wikis. Now we’ll look into the more complex challenge of migrating Boards and Work Items.

This process is more demanding and requires additional tooling. Fortunately, azure-devops-migration-tools, created by Martin Hinshelwood, provides a way to move Work Items, Test Plans & Suites, Pipelines and more between organizations.

The learning curve can, however, be steep—but don’t worry! We’ll break it down step by step and walk through the entire process in this part. 💪

💾Installation

Before we begin, azure-devops-migration-tools must be installed. Since it’s a Windows application, it can be installed using winget:

winget install nkdagility.azure-devops-migration-tools

For other options check the installation documentation.

✍️Creating a Config File

The tool runs from the command line and requires a JSON configuration file to define how the migration should be performed. A basic starting point for the config file looks as follows:

example.conf.json:

{
  "Serilog": {
    "MinimumLevel": "Information"
  },
  "MigrationTools": {
    "Version": "16.0",
    "Endpoints": {
      "Source": {
        "EndpointType": "TfsTeamProjectEndpoint",
        "Collection": "https://dev.azure.com/old-org/",
        "Project": "old-project",
        "Authentication": {
          "AuthenticationMode": "AccessToken",
          "AccessToken": "[Personal Access Token Old Org]"
        }
      },
      "Target": {
        "EndpointType": "TfsTeamProjectEndpoint",
        "Collection": "https://dev.azure.com/new-org/",
        "Project": "new-project",
        "Authentication": {
          "AuthenticationMode": "AccessToken",
          "AccessToken": "[Personal Access Token New Org]"
        },
        "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId"
      }
    },
    "CommonTools": {
      "TfsUserMappingTool": {
        "Enabled": true,
        "IdentityFieldsToCheck": [
          "System.AssignedTo",
          "System.ChangedBy",
          "System.CreatedBy"
        ]
      }
    },
    "Processors": [
      {
        "ProcessorType": "TfsWorkItemMigrationProcessor",
        "Enabled": true,
        "WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc"
      }
    ]
  }
}

To use this config, you’ll need to update the following fields:

  • Collection: The URL of your Azure DevOps source and target organizations.
  • Project: The names of the source and target projects.

💡 If the project name contains spaces, ensure the Project field reflects that exactly (don’t use HTML-encoded %20 for spaces).

  • AccessToken: Personal Access Token (PAT) for both the source and target organizations.

🔐Personal Access Tokens (PAT)

The migration tool requires full access PAT tokens for both the source and target organizations. These can be created from User settings -> Personal Access Tokens:

Personal Access Token settings in Azure DevOps

If your DevOps policy restricts full access, I’ve still had success by setting each individual scope to the highest level available.

⚙️Target Project and ReflectedWorkItemId Process Field

The migration tool uses a special field, ReflectedWorkItemId, to track the migration of work items. This field must be created on all work items that will be migrated.

What I usually do is create a new inherited process in the target organization that matches the source project’s process. This ensures that the ReflectedWorkItemId field is added to all work items in the target project.

Subsequent migrations can, of course, use the same process.

Creating a new inherited process in Azure DevOps

Adding the ReflectedWorkItemId Field to the First Work Item Type (Bug)

Adding the ReflectedWorkItemId Field to the First Work Item Type (Bug)

Adding the ReflectedWorkItemId Field to the Next Work Item Type when the field already exists

Adding the ReflectedWorkItemId Field to the Next Work Item Type when the field already exists

Once the ReflectedWorkItemId field has been added to all work item types, you can create the target project using the inherited process.

Creating a project with the new inherited process

Creating a project with the new inherited process

💡Microsoft offers a separate tool for exporting and importing inherited processes. If you’ve made extensive process customizations, it’s worth checking out. You can find the tool here: VSTS Process Migrator.

📋Board Settings

If you’ve customized Board Settings (such as columns or swimlanes), make sure to replicate these settings in the target project before executing the migration. This ensures that the work items are placed correctly on the target board.

Make sure to replicate Board settings before migration Make sure to replicate Board settings before migration

🚀Executing the Migrating Processes

Given that the Source and Target configuration is correct you should now be able to start a migration with the following command:

devopsmigration execute --config .\example.conf.json

If all goes well, your work items and iterations should now be migrated successfully. Don’t forget to validate the migrated data and make any adjustments as necessary.

Migration execution output showing successful work item migration


That concludes Part 2 of this series! ✅ Stay tuned for Part 3, where we’ll dive deeper into migrating Azure DevOps Test Plans, Pipelines, and more. 🚀


🔹 Azure DevOps Migration Series 🔹

🚀 Part 1: Migrating Repos & Wikis | 🔗LinkedIn

📊 Part 2: Migrating Boards & Work Items | 🔗LinkedIn

🧪 Part 3: (Coming soon!)