← Back to list

Azure DevOps Cloud’dan Azure DevOps Server’a Migration

Güvenlik ve veri yönetimi kuralları nedeniyle, buluttaki tüm Azure DevOps verilerini kendi Azure DevOps Server ortamımıza taşımaya karar…

Devopsfatihh · 2025-08-15 08:17 · 1 claps · 6.0 min read
#azure-devops-services #azure-devops-server #work-item #on-premise
Open on Medium ↗
Wiki topics: ☁️ · DevOps & Cloud

Azure DevOps Cloud’dan Azure DevOps Server’a Migration

Güvenlik ve veri yönetimi kuralları nedeniyle, buluttaki tüm Azure DevOps verilerini kendi Azure DevOps Server ortamımıza taşımaya karar verdik. Azure DevOps’un buluttan lokale geçişi resmi olarak desteklemediğini bildiğimiz için, bu işin biraz zahmetli olabileceğini tahmin ediyorduk.

Git depolarını taşımak çocuk oyuncağıydı. Script’ler ve komut satırı araçlarıyla bu işi hızlıca hallettik, hiçbir sorun çıkmadı. (Merak edenler için, bir sonraki yazımda bu script’lerin detaylarını paylaşacağım!)

Boards’taki Work Items verilerine geldiğimizde ise işler biraz karıştı. Özellikle Basic Process altyapısında manuel olarak eklenmiş bug ve error gibi task’lar ve içerisinde yine manuel oluşturulan field’lar, mevcut sürecin sınırlarını zorluyordu. Tarihçe, bağlantılar ve ekler gibi detayları korumak istediğimizde, yerleşik yöntemler yetersiz kaldı ve orada biraz tıkandık.

Araç Tercihi Neden Önemliydi?

Microsoft’un kendi import/export araçları, süreç şablonları farklı olduğunda ya da büyük veri setleriyle çalışırken çoğu zaman yetersiz kalıyor. Bizim için birkaç şey olmazsa olmazdı:

  • Work Item’ların Geçmişinin Korunması: Verilerin tarihçesi, yani geçmiş kayıtları, eksiksiz şekilde taşınmalıydı.
  • Özel Alanların Eşleştirilmesi: Custom field’ların doğru map edilmesi, veri bütünlüğü için çok önemliydi.
  • Bağlantılar ve Eklerin Taşınması: Tüm ilişkili link’ler ve ekler, yeni ortama sorunsuz aktarılmalıydı.
  • Büyük Veri Setlerinde Stabilite: Aracın büyük miktarda veriyi kaldırabilmesi ve çökmemesi gerekiyordu.

Bu ihtiyaçları karşılamak için topluluk tarafından geliştirilen ve aktif destek sunan nkdAgility’nin Azure DevOps Migration Tools’unu tercih ettik. Bu araç, tam anlamıyla beklentilerimizi karşıladı ve verileri güvenle taşımamızı sağladı. Üstelik, takıldığımız yerlerde açtığımız issue’lara ve discussion’lara hızlıca geri dönüş alarak süreci çok daha rahat yönetebildik. Bu yüzden nkdAgility ekibine kocaman bir teşekkür borçluyuz.

Azure DevOps Migration Tools Kurulum

1. Gereksinimler

Öncelikle, bu aracı çalıştırmak için sisteminde neler olması gerektiğini kontrol edelim:

Minimum Gereksinimler:

  • Windows 10/11 ya da Windows Server 2016 veya üstü.
  • .NET Framework 4.7.2 veya daha yeni bir sürüm.
  • .NET 6.0 Runtime (bunu birazdan kuracağız).
  • PowerShell 5.1 veya üstü.
  • 4GB RAM (ama 8GB olursa daha rahat edersin).
  • İnternet bağlantısı (indirmeler ve bağlantılar için).

Yetki Gereksinimleri:

  • Kaynak (Azure DevOps Cloud): Full Access PAT token’ı lazım.
  • Hedef (Azure DevOps Server): Collection Administrator yetkisi şart.
  • Hedef (Azure DevOps Server): Http ise domain-adminuser-password

2. Kurulum Adımları

Kuruluma geçmeden önce sistemini hazır hale getirmen gerekiyor. İşte yapman gerekenler:

NET Runtime Kurulumu

Önce .NET 6.0 Runtime’ı kuruyoruz. Ayrıca, .NET Framework 4.8’in de yüklü olduğundan emin olalım.

# Admin PowerShell'de çalıştırın

# .NET 6.0 Runtime'ı indir ve kur
Invoke-WebRequest -Uri "https://dot.net/v1/dotnet-install.ps1" -OutFile "dotnet-install.ps1"
.\dotnet-install.ps1 -Runtime dotnet -Version 6.0.25

# .NET Framework 4.8 (eğer yoksa)
# https://dotnet.microsoft.com/download/dotnet-framework/net48

Migration Tools Kurulumu

Azure DevOps Migration Tools’u kurmak için birkaç seçeneğiniz var. Ben en kolayı olan WinGet yöntemini öneriyorum, ama diğer yolları da anlatacağım.

Yöntem 1: WinGet ile (Önerilen)

# WinGet yoksa önce onu kurun
# Microsoft Store'dan "App Installer" yükleyin

# Migration Tools'u kur
winget install nkdAgility.AzureDevOpsMigrationTools

# Kurulum sonrası kontrol
devopsmigration version

WinGet, hayatı kolaylaştırıyor. Eğer sisteminizde yoksa, Microsoft Store’dan “App Installer”ı yükleyin.

Yöntem 2: .NET Tool olarak

Eğer WinGet kullanmak istemezseniz .NET Tool ile de kurabilirsiniz.

# Global tool olarak kur
dotnet tool install -g devopsmigration

# Path'e ekle (gerekirse)
$env:Path += ";$env:USERPROFILE\.dotnet\tools"

Yöntem 3: Manuel İndirme

Hiçbir aracı kullanmak istemezseniz, GitHub’dan manuel olarak indirebilirsiniz.

# GitHub'dan indir
$url = "https://github.com/nkdAgility/azure-devops-migration-tools/releases/download/v16.2.9/MigrationTools-16.2.9.zip"
$output = "C:\Tools\MigrationTools.zip"

Invoke-WebRequest -Uri $url -OutFile $output
Expand-Archive -Path $output -DestinationPath "C:\Tools\MigrationTools"

# Path'e ekle
$env:Path += ";C:\Tools\MigrationTools"

3. Migration’a Hazırlık

Kurulum bitti, şimdi sistemi migration için hazırlayalım.

A. Çalışma Dizini Oluşturma

Tüm dosyaları düzenli tutmak için bir çalışma dizini oluşturuyoruz.

New-Item -ItemType Directory -Path "C:\AzureDevOpsMigration" -Force
New-Item -ItemType Directory -Path "C:\AzureDevOpsMigration\Logs" -Force
New-Item -ItemType Directory -Path "C:\AzureDevOpsMigration\Configs" -Force
New-Item -ItemType Directory -Path "C:\AzureDevOpsMigration\Attachments" -Force

cd C:\AzureDevOpsMigration

B. PAT Token Oluşturma

Hem kaynak (Cloud) hem de hedef (Server) için PAT token’ları oluşturmanız gerekiyor. Server’da kurulu olan Azure DevOps henüz https değil ise domain-user-password ile ilerleyebilirsiniz.

Kaynak (Azure DevOps Cloud):

  1. https://dev.azure.com/YOUR_ORG/_usersSettings/tokens adresine git.
  2. Yeni token oluştur, kapsamı (scope) Full Access yap.

Hedef (Azure DevOps Server):

  1. http://YOUR_SERVER/DefaultCollection/_usersSettings/tokens adresine git.
  2. Yeni token oluştur, kapsamı Full Access yap.

C. Hedef Ortamı Hazırlama

Hedef sistemde bazı ayarları yapmamız gerekiyor.

Inherited Process Oluşturma (UI üzerinden):

  • Organization Settings > Process > Basic > … > Create inherited process
  • İsim: “Basic Custom”

Projeyi Yeni Process’e Geçirme:

  • Organization Settings > Projects > YOUR_PROJECT > … > Change process
  • Seç: “Basic Custom”

Custom Field Ekleme:

  • Process > Basic Custom > Epic/Issue/Task > New field
  • İsim: ReflectedWorkItemId
  • Tür: Text (single line)

4. Konfigürasyon Dosyası Oluşturma

Migration’ı başlatmadan önce bir config dosyası oluşturmamız lazım. İşte basit bir şablon:


{
  "$schema": "https://raw.githubusercontent.com/nkdAgility/azure-devops-migration-tools/main/src/MigrationTools/_configuration/configuration.schema.json",
  "MigrationTools": {
    "Version": "16.0",
    "Endpoints": {
      "Source": {
        "EndpointType": "TfsTeamProjectEndpoint",
        "Collection": "https://dev.azure.com/SOURCE_ORG",
        "Project": "SOURCE_PROJECT",
        "Authentication": {
          "AuthenticationMode": "AccessToken",
          "AccessToken": "SOURCE_PAT_TOKEN"
        },
        "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId",
        "AllowCrossProjectLinking": false
      },
      "Target": {
        "EndpointType": "TfsTeamProjectEndpoint",
        "Collection": "http://TARGET_SERVER/DefaultCollection",
        "Project": "TARGET_PROJECT",
        "Authentication": {
          "AuthenticationMode": "Windows",
          "NetworkCredentials": {
            "Domain": "DOMAIN",
            "UserName": "USERNAME",
            "Password": "PASSWORD"
          }
        },
        "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId",
        "AllowCrossProjectLinking": false
      }
    },
    "CommonTools": {
      "FieldMappingTool": {
        "Enabled": true,
        "FieldMaps": [
          {
            "FieldMapType": "FieldValueMap",
            "WorkItemTypeName": "*",
            "sourceField": "System.State",
            "targetField": "System.State",
            "defaultValue": "To Do",
            "valueMapping": {
              "New": "To Do",
              "Active": "Doing",
              "In Progress": "Doing",
              "Resolved": "Done",
              "Closed": "Done",
              "Done": "Done"
            }
          }
        ]
      },
      "TfsWorkItemTypeValidatorTool": {
        "Enabled": false,
        "IncludeWorkItemtypes": [
          "Shared Steps",
          "Test Case",
          "Test Plan",
          "Test Suite",
          "Bug",
          "Task",
          "User Story",
          "Feature",
          "Epic"
        ]
      }
    },
    "Processors": [
      {
        "ProcessorType": "TfsWorkItemMigrationProcessor",
        "SourceName": "Source",
        "TargetName": "Target",
        "Enabled": true,
        "UpdateCreatedDate": true,
        "UpdateCreatedBy": true,
        "WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.WorkItemType] IN ('Issue', 'Task', 'Epic', 'Bug', 'Error') AND [System.WorkItemType] NOT IN ('Test Case', 'Test Plan', 'Test Suite', 'Shared Steps', 'Shared Parameter', 'Code Review Request', 'Code Review Response', 'Feedback Request', 'Feedback Response') ORDER BY [System.Id]",
        "LinkMigration": true,
        "AttachmentMigration": true,
        "AttachmentWorkingPath": "C:\\AzureDevOpsMigration\\Attachments",
        "FilterWorkItemsThatAlreadyExistInTarget": true,
        "PauseAfterEachWorkItem": false,
        "WorkItemCreateRetryLimit": 5,
        "GenerateMigrationComment": true,
        "BypassRules": true,
        "MaxGracefulFailures": 100
      }
    ],
    "CommonEnrichersConfig": [
      {
        "$type": "WorkItemTypeMappingEnricherOptions",
        "Enabled": true,
        "Mappings": {
          "Issue": "Issue",
          "Task": "Task",
          "Epic": "Epic",
          "Bug": "Bug",
          "Error": "Error"
        }
      }
    ],
    "Serilog": {
      "MinimumLevel": "Information",
      "WriteTo": [
        {
          "Name": "File",
          "Args": {
            "path": "logs/migration-test-.txt",
            "rollingInterval": "Day"
          }
        },
        {
          "Name": "Console",
          "Args": {
            "outputTemplate": "{Timestamp:HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}"
          }
        }
      ]
    }
  }
}

5. Migration’ı Çalıştırma

Artık her şey hazır, sıra migration’ı başlatmakta

A. Bağlantıyı Test Etme

Önce kaynak ve hedef sistemlere bağlanıp her şeyin çalıştığından emin olalım.

param(
    [string]$SourcePAT = "YOUR_SOURCE_PAT",
    [string]$TargetPAT = "YOUR_TARGET_PAT",
    [string]$SourceOrg = "https://dev.azure.com/YOUR_ORG",
    [string]$TargetOrg = "http://YOUR_SERVER/DefaultCollection"
)

$sourceHeaders = @{ Authorization = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$SourcePAT")) }
try {
    $source = Invoke-RestMethod -Uri "$SourceOrg/_apis/projects?api-version=6.0" -Headers $sourceHeaders
    Write-Host "✓ Source OK: $($source.count) projects" -ForegroundColor Green
} catch {
    Write-Error "Source failed: $_"
}

$targetHeaders = @{ Authorization = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$TargetPAT")) }
try {
    $target = Invoke-RestMethod -Uri "$TargetOrg/_apis/projects?api-version=6.0" -Headers $targetHeaders
    Write-Host "✓ Target OK: $($target.count) projects" -ForegroundColor Green
} catch {
    Write-Error "Target failed: $_"
}

B. Migration’ı Başlatma

Config dosyasını düzenledikten sonra migration’ı başlatabilirsiniz.

# Config'i düzenle (PAT, URL, proje adları)
notepad C:\AzureDevOpsMigration\config.json

# Migration'ı başlat
devopsmigration execute --config "C:\AzureDevOpsMigration\config.json"

Karşılaştığımız Zorluklar ve Çözüm için Kullandığımız Konfigürasyon

Veri taşıma sürecinde birkaç engelle karşılaştık, ama her biri için bir yol bulduk. Yaşadığımız temel sorunlardan bazılarına aşağıda ulaşabilirsiniz ayrıca nkdAgility Azure DevOps Migration Tools’ta kullandığımız konfigürasyon detaylarınıda aşağıda belirttim:

1. Field Validation Hataları

İlk çalıştırmada sistem bir hata mesajı verdi: “Bazı work item tipleri veya alanları hedef sistemde mevcut değil!” Mesela, Cloud’da 400+ kullanıcıyı listeleyen ‘Custom.xxxx’ alanı Server’da mevcut değildi. Aynı şekilde, ‘Custom.ReflectedWorkItemId’ alanı da ‘Feedback Response’ için Server’da yoktu. Bu sorunu çözmek için sadece gerekli work item tiplerini belirttik.

2. Process Template Uyumsuzlukları

Her iki tarafta da “Basic” şablonunu kullanıyorduk, ama Cloud’da özel olarak eklenmiş Bug ve Error tipleri Server’da tanımlı değildi. Bu uyumsuzluğu WorkItemTypeMappingEnricherOptions ile çözdük, work item tiplerini açıkça eşleştirerek (ör. “Issue” → “Issue”, “Bug” → “Bug”) sistemi rahatlattık.

3. Feedback Response Work Item Sorunu

Azure DevOps’un Feedback Response tipine özel alan eklemek mümkün olmadı, bu da validation hatalarına neden oldu. Çözüm olarak, WIQLQuery içinde Feedback Response’ı hariç tuttuk ve sadece istediğimiz tipleri (Issue, Task, Epic, Bug, Error) işleme aldık.

4. Authentication ve Yetki Sorunları

Cloud’dan Server’a bağlantıda yetkilendirme kısmı biraz uğraştırdı:

  • PAT token’ların kapsam (scope) ayarlarını doğru yapmak için epey kafa yorduk.
  • Windows Authentication ile AccessToken farkları kafa karıştırıcıydı.
  • Collection Administrator yetkisi olmadan bazı adımları geçmek imkânsızdı. Bu yüzden BypassRules: true ile bazı kural doğrulamalarını atladık ve gerekli yetkileri sağladık.

Aşağıda, bu zorlukları aşmamızı sağlayan konfigürasyon dosyamızı paylaşıyorum. Ayrıca, süreci takip edebilmek için Serilog ile loglama ayarlarını ekledik.

Konfigürasyon Dosyası

{
  "CommonTools": {
    "TfsWorkItemTypeValidatorTool": {
      "Enabled": false,
      "IncludeWorkItemtypes": [
        "Shared Steps",
        "Test Case",
        "Test Plan",
        "Test Suite",
        "Bug",
        "Task",
        "User Story",
        "Feature",
        "Epic"
      ]
    }
  },
  "Processors": [
    {
      "ProcessorType": "TfsWorkItemMigrationProcessor",
      "SourceName": "Source",
      "TargetName": "Target",
      "Enabled": true,
      "UpdateCreatedDate": true,
      "UpdateCreatedBy": true,
      "WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.WorkItemType] IN ('Issue', 'Task', 'Epic', 'Bug', 'Error') AND [System.WorkItemType] NOT IN ('Test Case', 'Test Plan', 'Test Suite', 'Shared Steps', 'Shared Parameter', 'Code Review Request', 'Code Review Response', 'Feedback Request', 'Feedback Response') ORDER BY [System.Id]",
      "LinkMigration": false,
      "AttachmentMigration": false,
      "FilterWorkItemsThatAlreadyExistInTarget": false,
      "PauseAfterEachWorkItem": false,
      "WorkItemCreateRetryLimit": 5,
      "GenerateMigrationComment": true,
      "BypassRules": true,
      "MaxGracefulFailures": 100
    }
  ],
  "CommonEnrichersConfig": [
    {
      "$type": "WorkItemTypeMappingEnricherOptions",
      "Enabled": true,
      "Mappings": {
        "Issue": "Issue",
        "Task": "Task",
        "Epic": "Epic",
        "Bug": "Bug",
        "Error": "Error"
      }
    }
  ],
  "Serilog": {
    "MinimumLevel": "Debug",
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "logs/migration-test-.txt",
          "rollingInterval": "Day"
        }
      },
      {
        "Name": "Console",
        "Args": {
          "outputTemplate": "{Timestamp:HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}"
        }
      }
    ]
  }
}

Bu ayarlarla süreci sorunsuz tamamladık. nkdAgility ekibine, takıldığım yerlerde hızlı destekleri için tekrardan teşekkür ederim


메타데이터
post_id
e02b5845e1da
slug
azure-devops-clouddan-on-premise-server-a-migration-e02b5845e1da
url
https://medium.com/@devopsfatihh/azure-devops-clouddan-on-premise-server-a-migration-e02b5845e1da
canonical_url
https://medium.com/@devopsfatihh/azure-devops-clouddan-on-premise-server-a-migration-e02b5845e1da
author_url
https://medium.com/@devopsfatihh
status
ok
fetched_at
2026-08-01 02:08:18