From 0dd15de6fd6c30163b2917a7c8873cbc96b70c60 Mon Sep 17 00:00:00 2001 From: philspokas Date: Wed, 29 Mar 2023 12:54:31 -0700 Subject: [PATCH] cleanup folders --- .../github-tools => github-tools}/README.md | 0 .../migrate-project.ps1 | 0 .../github-tools => github-tools}/orgs.json | 0 .../migration-scripts/AllProjects.ps1 | 198 ----------- .../migration-scripts/Configuration.json | 15 - migration-tools/migration-scripts/README.md | 81 ----- .../migration-scripts/base-configuration.json | 54 --- .../migration-scripts/create-manifest.ps1 | 38 -- .../migration-scripts/set-readonly.ps1 | 25 -- migration-tools/module-migrators/.gitignore | 3 - migration-tools/module-migrators/README.md | 89 ----- .../Migrate-ADO-AreaPaths.psm1 | 141 -------- .../Migrate-ADO-BuildQueues.psm1 | 162 --------- .../Migrate-ADO-Common.psm1 | 299 ---------------- .../Migrate-ADO-Groups.psm1 | 334 ------------------ .../Migrate-ADO-IterationPaths.psm1 | 136 ------- .../Migrate-ADO-Pipelines.psm1 | 27 -- .../supporting-modules/Migrate-ADO-Repos.psm1 | 224 ------------ .../supporting-modules/Migrate-ADO-Teams.psm1 | 152 -------- .../supporting-modules/Migrate-ADO-Users.psm1 | 154 -------- .../supporting-modules/README.md | 111 ------ .../Tests/Get-Test-Context.psm1 | 32 -- .../Tests/Migrate-ADO-AreaPaths-Tests.ps1 | 21 -- tools/migrate-external-repos.ps1 | 33 ++ 24 files changed, 33 insertions(+), 2296 deletions(-) rename {admin-tools/github-tools => github-tools}/README.md (100%) rename {admin-tools/github-tools => github-tools}/migrate-project.ps1 (100%) rename {admin-tools/github-tools => github-tools}/orgs.json (100%) delete mode 100644 migration-tools/migration-scripts/AllProjects.ps1 delete mode 100644 migration-tools/migration-scripts/Configuration.json delete mode 100644 migration-tools/migration-scripts/README.md delete mode 100644 migration-tools/migration-scripts/base-configuration.json delete mode 100644 migration-tools/migration-scripts/create-manifest.ps1 delete mode 100644 migration-tools/migration-scripts/set-readonly.ps1 delete mode 100644 migration-tools/module-migrators/.gitignore delete mode 100644 migration-tools/module-migrators/README.md delete mode 100644 migration-tools/module-migrators/supporting-modules/Migrate-ADO-AreaPaths.psm1 delete mode 100644 migration-tools/module-migrators/supporting-modules/Migrate-ADO-BuildQueues.psm1 delete mode 100644 migration-tools/module-migrators/supporting-modules/Migrate-ADO-Common.psm1 delete mode 100644 migration-tools/module-migrators/supporting-modules/Migrate-ADO-Groups.psm1 delete mode 100644 migration-tools/module-migrators/supporting-modules/Migrate-ADO-IterationPaths.psm1 delete mode 100644 migration-tools/module-migrators/supporting-modules/Migrate-ADO-Pipelines.psm1 delete mode 100644 migration-tools/module-migrators/supporting-modules/Migrate-ADO-Repos.psm1 delete mode 100644 migration-tools/module-migrators/supporting-modules/Migrate-ADO-Teams.psm1 delete mode 100644 migration-tools/module-migrators/supporting-modules/Migrate-ADO-Users.psm1 delete mode 100644 migration-tools/module-migrators/supporting-modules/README.md delete mode 100644 migration-tools/module-migrators/supporting-modules/Tests/Get-Test-Context.psm1 delete mode 100644 migration-tools/module-migrators/supporting-modules/Tests/Migrate-ADO-AreaPaths-Tests.ps1 diff --git a/admin-tools/github-tools/README.md b/github-tools/README.md similarity index 100% rename from admin-tools/github-tools/README.md rename to github-tools/README.md diff --git a/admin-tools/github-tools/migrate-project.ps1 b/github-tools/migrate-project.ps1 similarity index 100% rename from admin-tools/github-tools/migrate-project.ps1 rename to github-tools/migrate-project.ps1 diff --git a/admin-tools/github-tools/orgs.json b/github-tools/orgs.json similarity index 100% rename from admin-tools/github-tools/orgs.json rename to github-tools/orgs.json diff --git a/migration-tools/migration-scripts/AllProjects.ps1 b/migration-tools/migration-scripts/AllProjects.ps1 deleted file mode 100644 index edd15d0..0000000 --- a/migration-tools/migration-scripts/AllProjects.ps1 +++ /dev/null @@ -1,198 +0,0 @@ -Import-Module Migrate-ADO -Force - -# ------------------------------------------------------------------------------------- -# -------------- Specifiy What Parts of the Migration Should Be Skipped --------------- -#region ------------------------------------------------------------------------------- -# Setting any of the below values to true will trigger a whatif condition rather than -# the actual migration. -[Boolean]$SKIP_MigrateOrgUsers = $FALSE -[Boolean]$SKIP_MigrateTeams = $FALSE -[Boolean]$SKIP_MigrateGroups = $FALSE -[Boolean]$SKIP_MigrateAreaPaths = $FALSE -[Boolean]$SKIP_MigrateIterationPaths = $FALSE -[Boolean]$SKIP_MigrateBuildQueues = $FALSE -[Boolean]$SKIP_MigrateRepos = $FALSE -[Boolean]$SKIP_MigrateWorkItems = $FALSE - -# Validate the above configuration is okay -if (($SKIP_MigrateAreaPaths -or $SKIP_MigrateIterationPaths) -and !$SKIP_MigrateWorkItems) { - throw "If you plan to migrate work items, then you need to migrate both the area and iteration paths for a project." -} -#endregion - -# ------------------------------------------------------------------------------------- -# ---------------- Set up files for logging & get configuration values ---------------- -#region ------------------------------------------------------------------------------- -$runDate = (get-date).ToString('yyyy-MM-dd HHmmss') -$configuration = [Object](Get-Content 'migration-scripts\Configuration.json' | Out-String | ConvertFrom-Json) - -$projectPath = Get-ProjectFolderPath ` - -RunDate $runDate ` - -Root $configuration.ProjectDirectory - -$env:MIGRATION_LOGS_PATH = $projectPath - -$projects = Import-Csv $configuration.ProjectsCsv - -$env:MIGRATION_LOGS_PATH = $projectPath - -Set-ProjectFolders ` - -RunDate $runDate ` - -Projects $projects ` - -SourceOrg $configuration.SourceProject.OrgName ` - -SourcePAT $configuration.SourceProject.PAT ` - -TargetOrg $configuration.TargetProject.OrgName ` - -TargetPAT $configuration.TargetProject.PAT ` - -SavedAzureQuery $configuration.SavedAzureQuery ` - -MSConfigPath $configuration.MsConfigPath ` - -Root $configuration.ProjectDirectory -#endregion - -# ------------------------------------------------------------------------------------- -# ---------------- Start The Migration At the Org Level ------------------------------- -#region ------------------------------------------------------------------------------- -Write-Log -Message ' ' -Write-Log -Message '------------------------------------------------------------------------------------------------' -Write-Log -Message "-- Migrate $($configuration.SourceProject.OrgName) to $($configuration.TargetProject.OrgName) --" -Write-Log -Message '------------------------------------------------------------------------------------------------' -Write-Log -Message ' ' - -# ======================================== -# ====== Migrate Users On Org Level ====== -#region ================================== -Start-ADOUserMigration ` - -SourceOrgName $configuration.SourceProject.OrgName ` - -SourcePat $configuration.SourceProject.PAT ` - -TargetOrgName $configuration.TargetProject.OrgName ` - -TargetPAT $configuration.TargetProject.PAT ` - -WhatIf:$SKIP_MigrateOrgUsers -#endregion - -# -------------------------------------------------------------------------------------- -# ---------------- For each project in the CSV file - preform a migration -------------- -#region -------------------------------------------------------------------------------- -foreach ($project in $projects) { - # Get project folder & set logging path w/ env variable - $projectPath = Get-ProjectFolderPath ` - -RunDate $runDate ` - -SourceProject $project.SourceProject ` - -TargetProject $project.TargetProject ` - -Root $configuration.ProjectDirectory - - $env:MIGRATION_LOGS_PATH = $projectPath - - # Get Headers - $sourceHeaders = New-HTTPHeaders ` - -PersonalAccessToken $configuration.SourceProject.PAT - $targetHeaders = New-HTTPHeaders ` - -PersonalAccessToken $configuration.TargetProject.PAT - - Write-Log -Message ' ' - Write-Log -Message '--------------------------------------------------------------------' - Write-Log -Message "-- Migrate $($project.sourceProject) to $($project.TargetProject) --" - Write-Log -Message '--------------------------------------------------------------------' - Write-Log -Message ' ' - - # ======================================== - # ============ Migrate Teams ============= - #region ================================== - Start-ADOTeamsMigration ` - -SourceHeaders $sourceHeaders ` - -SourceOrgName $configuration.SourceProject.OrgName ` - -SourceProjectName $project.SourceProject ` - -TargetHeaders $targetHeaders ` - -TargetOrgName $configuration.TargetProject.OrgName ` - -TargetProjectName $project.TargetProject ` - -WhatIf:$SKIP_MigrateTeams - #endregion - - # ======================================== - # =========== Migrate Groups ============= - #region ================================== - Start-ADOGroupsMigration ` - -SourcePAT $configuration.SourceProject.PAT ` - -SourceOrgName $configuration.SourceProject.OrgName ` - -SourceProjectName $project.SourceProject ` - -TargetPAT $configuration.TargetProject.PAT ` - -TargetOrgName $configuration.TargetProject.OrgName ` - -TargetProjectName $project.TargetProject ` - -WhatIf:$SKIP_MigrateGroups - #endregion - - # ======================================== - # ========== Migrate Area Paths ========== - #region ================================== - Start-ADOAreaPathsMigration ` - -SourceProjectName $project.SourceProject ` - -SourceOrgName $configuration.SourceProject.OrgName ` - -SourceHeaders $sourceHeaders ` - -TargetProjectName $project.TargetProject ` - -TargetOrgName $configuration.TargetProject.OrgName ` - -TargetHeaders $targetHeaders ` - -WhatIf:$SKIP_MigrateAreaPaths - #endregion - - # ======================================== - # ======= Migrate Iteration Paths ======== - #region ================================== - Start-ADOIterationPathsMigration ` - -SourceProjectName $project.SourceProject ` - -SourceOrgName $configuration.SourceProject.OrgName ` - -SourceHeaders $sourceHeaders ` - -TargetProjectName $project.TargetProject ` - -TargetOrgName $configuration.TargetProject.OrgName ` - -TargetHeaders $targetHeaders ` - -WhatIf:$SKIP_MigrateIterationPaths - #endregion - - # ======================================== - # ========= Migrate Build Queues ========= - #region ================================== - Start-ADOBuildQueuesMigration ` - -SourceProjectName $project.SourceProject ` - -SourceOrgName $configuration.SourceProject.OrgName ` - -SourceHeaders $sourceHeaders ` - -TargetProjectName $project.TargetProject ` - -TargetOrgName $configuration.TargetProject.OrgName ` - -TargetHeaders $targetHeaders ` - -WhatIf:$SKIP_MigrateBuildQueues - #endregion - - # ======================================== - # ============ Migrate Repos ============= - #region ================================== - Start-ADORepoMigration ` - -SourceProjectName $project.SourceProject ` - -SourceOrgName $configuration.SourceProject.OrgName ` - -SourceHeaders $sourceHeaders ` - -TargetProjectName $project.TargetProject ` - -TargetOrgName $configuration.TargetProject.OrgName ` - -TargetHeaders $targetHeaders ` - -ReposPath $projectPath ` - -WhatIf:$SKIP_MigrateRepos - #endregion - - # ======================================== - # ========== Migrate work items ========== - #region ================================== - if (!$SKIP_MigrateWorkItems) { - $savedPath = $(Get-Location).Path - - Set-Location -Path $configuration.WorkItemMigratorDirectory - dotnet run --validate "$projectPath\ProjectConfiguration.json" - dotnet run --migrate "$projectPath\ProjectConfiguration.json" - - Set-Location -Path $savedpath - } - else { - Write-Host "What if: Preforming the operation `"Migrate work items from source project $($project.SourceProject)`" on target `"Target project $($project.TargetProject)`"" - } - #endregion - - # ======================================== - # ========== Migration Finished ========== - # ======================================== - Write-Log "Done migrating $($project.SourceProject) to $($project.TargetProject)" -LogLevel SUCCESS -} -#endregion -#endregion \ No newline at end of file diff --git a/migration-tools/migration-scripts/Configuration.json b/migration-tools/migration-scripts/Configuration.json deleted file mode 100644 index 188499f..0000000 --- a/migration-tools/migration-scripts/Configuration.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "TargetProject": { - "OrgName": "", - "PAT": "" - }, - "SourceProject": { - "OrgName": "", - "PAT": "" - }, - "SavedAzureQuery": "My Queries/All Items", - "ProjectDirectory": "", - "ProjectsCsv": ".\\migration-scripts\\Projects.csv", - "MSConfigPath": ".\\migration-scripts\\base-configuration.json", - "WorkItemMigratorDirectory": "" -} \ No newline at end of file diff --git a/migration-tools/migration-scripts/README.md b/migration-tools/migration-scripts/README.md deleted file mode 100644 index de6ba8e..0000000 --- a/migration-tools/migration-scripts/README.md +++ /dev/null @@ -1,81 +0,0 @@ -# Migration Scripts Directory -This directory holds pre-written scripts and configuration files that links all of the migration modules under the `supporting-modules` directory as well as the [Microsoft VSTS Work Item Migrator tool](https://github.com/microsoft/vsts-work-item-migrator) to preform a full DevOps migration. - ---- - -# Projects.csv -The `Projects.csv` file is where you define a list of source projects and the corresponding target project to migrate to. -```csv -SourceProject,TargetProject -Source-Project-Name1,Target-Project-Name1 -Source-Project-Name2,Target-Project-Name2 -Source-Project-Name3,Target-Project-Name3 -``` -- Lines are separated by new lines, not commas. -- Source project names and target project names are separated by commas. - -The first line of the CSV acts as the header, these lines should not be modified (if they are modified you will need to update the header names in the `AllProjects.ps1` script as well. - -All following lines define a source project to migrate from and a target project to migrate to. - -# Configuration.json -The `Configuration.json` file is used to set up file locations for logging, PAT tokens for authentication and other information required for running the `migration-scripts/AllProjects.ps1` script. - -##### PROPERTIES -| Property Name | VSTS Only? | Data Type | Description -|---------------------------|------------|-----------|------------- -| TargetProject | | Object | An object consisting of an OrgName and a PAT -| └─ OrgName | | String | The organization name for the target project -| └─ PAT | | String | The personal access token you created (or need to create) for the target project -| SourceProject | | Object | An object consisting of an OrgName and a PAT -| └─ OrgName | | String | The organization name for the source project -| └─ PAT | | String | The personal access token you created (or need to create) for the source project -| SavedAzureQuery | ✔️ | String | Only required if using the [Microsoft VSTS Work Item Migrator tool](https://github.com/microsoft/vsts-work-item-migrator) , read more here: ['query' parameter documentation](https://github.com/microsoft/vsts-work-item-migrator/blob/master/WiMigrator/migration-configuration.md#query-the-name-of-the-query-to-use-for-identifying-work-items-to-migrate-note-query-must-be-a-flat) -| ProjectDirectory | | String | The directory where logging, repos and auto-generated configuration files will be placed. Make sure this path is not nested too deeply or file paths may be too long. -| ProjectscCsv | | String | The path of the csv file holding the list of projects you want to migrate. This csv is included in the repo and the path is provided as a relative path, so you should not need to update this setting. -| MsConfigPath | ✔️ | String | Only required if using the [Microsoft VSTS Work Item Migrator tool](https://github.com/microsoft/vsts-work-item-migrator) . This is the configuration file that will be copied and modified for each project. This path is set relatively and the configuration file is provided in the repo so you should not need to update this setting. -| WorkItemMigratorDirectory | ✔️ | String | Only required if using the [Microsoft VSTS Work Item Migrator tool](https://github.com/microsoft/vsts-work-item-migrator) . This is the directory you cloned the migration tool too. Be sure to include the directory `WiMigrator` at the end of the cloned repository path. - ----------- - -**VSTS Only** means that the configuration property is only required if you are using the VSTS work item migrator. - -# base-configuration.json ([VSTS only](https://github.com/microsoft/vsts-work-item-migrator)) -A pre-configured configuration file used by the [Microsoft VSTS Work Item Migrator tool](https://github.com/microsoft/vsts-work-item-migrator). -Read more about [base-configuration here](https://github.com/microsoft/vsts-work-item-migrator/blob/master/WiMigrator/migration-configuration.md) - -For each project migration defined in the `Projects.csv` the `base-configuration.json` file is copied, modified and saved in that projects directory before a migration is preformed. - -# create-manifest.ps1 -The `create-manifest.ps1` script creates a new PowerShell distribution manifest file (.psd1) under your `Documents\WindowsPowerShell\Modules` directory. This allows you to use the command `Import-Module Migrate-ADO` to import all of the modules listed under the `$IncludedModules` list in the file. - -This script should be run when the repo is first cloned and whenever the `create-manifest.ps1` script is updated. - -# AllProjects.ps1 -The `AllProjects.ps1` script preforms a full migration of the following DevOps items: -- Area Paths - - Using the `Start-ADOAreaPathsMigration` cmdlet under `supporting-modules` -- Iteration Paths - - Using the `StartADOIterationPathsMigration` cmdlet under `supporting-modules` -- Build Queues - - Using the `Start-ADOBuildQueuesMigration` cmdlet under `supporting-modules` -- Repos - - Using the `Start-ADORepoMigration` cmdlet under `supporting-modules` -- Work Items - - Using the [Microsoft VSTS Work Item Migrator tool](https://github.com/microsoft/vsts-work-item-migrator) - -The script starts importing the `Projects.csv` and setting a migration run date, which is then used to create a migration directory under the path specified in the `Configuration.json` file. - -Each migration defined under `Projects.csv` gets it's own folder where a copy of `base-configuration.json` is created and configured specifically for that migration. All of the migrations are nested under a folder dated with the migration run date set above. - -After the project directories are created for each project, the script preforms a migration for each project. - - -# Migration Notes -- default iteration path is not set a team -- default area path is not set for a team - -# Set source to read only -- set repos isDisabled flag to true (manually via UI this pass) -- Move all members of Contributors to Readers. members of groups such as Project Admins, Build Admins, project Collection admins are not affected. Additionally, any specific user assignments will still be valid - diff --git a/migration-tools/migration-scripts/base-configuration.json b/migration-tools/migration-scripts/base-configuration.json deleted file mode 100644 index 4a76936..0000000 --- a/migration-tools/migration-scripts/base-configuration.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "source-connection": { - "account": "", - "project": "", - "access-token": "", - "use-integrated-auth": "false" - }, - "target-connection": { - "account": "", - "project": "", - "access-token": "", - "use-integrated-auth": "false" - }, - "query": "My Queries/All Items", - "heartbeat-frequency-in-seconds": 30, - "query-page-size": 20000, - "parallelism": 1, - "max-attachment-size": 62914560, - "link-parallelism": 1, - "attachment-upload-chunk-size": 1048576, - "skip-existing": false, - "move-history": false, - "move-history-limit": 200, - "move-git-links": false, - "move-attachments": false, - "move-links": true, - "source-post-move-tag": "", - "target-post-move-tag": "", - "skip-work-items-with-type-missing-fields": false, - "skip-work-items-with-missing-area-path": false, - "skip-work-items-with-missing-iteration-path": false, - "default-area-path": "contoso-project\\missing area path", - "default-iteration-path": "contoso-project\\missing iteration path", - "clear-identity-display-names": false, - "ensure-identities": false, - "include-web-link": true, - "log-level-for-file": "information", - "field-replacements": { - - }, - "send-email-notification": false, - "email-notification": { - "smtp-server": "127.0.0.1", - "use-ssl": false, - "port": "25", - "from-address": "wimigrator@example.com", - "user-name": "un", - "password": "pw", - "recipient-addresses": [ - "test1@test.com", - "test2@test.com" - ] - } -} diff --git a/migration-tools/migration-scripts/create-manifest.ps1 b/migration-tools/migration-scripts/create-manifest.ps1 deleted file mode 100644 index 844a27e..0000000 --- a/migration-tools/migration-scripts/create-manifest.ps1 +++ /dev/null @@ -1,38 +0,0 @@ -# ----------- CONFIGURE VARIABLES HERE -$IncludedModules = @( - "$(Get-Location)\Supporting-Modules\Migrate-ADO-AreaPaths.psm1", - "$(Get-Location)\Supporting-Modules\Migrate-ADO-IterationPaths.psm1", - "$(Get-Location)\Supporting-Modules\Migrate-ADO-Users.psm1", - "$(Get-Location)\Supporting-Modules\Migrate-ADO-Teams.psm1", - "$(Get-Location)\Supporting-Modules\Migrate-ADO-Groups.psm1", - "$(Get-Location)\Supporting-Modules\Migrate-ADO-BuildQueues.psm1", - "$(Get-Location)\supporting-modules\Migrate-ADO-Repos.psm1", - "$(Get-Location)\Supporting-Modules\Migrate-ADO-Common.psm1", - "$(Get-Location)\Supporting-Modules\Migrate-ADO-Pipelines.psm1" -) - -# Make sure files are the correct paths -$validPath = Test-Path $IncludedModules[0] - -if(!$validPath){ - throw "The file paths appear to be incorrect... `n - Make sure you are in the repo root directory when running this script." -} - -$Version = '1.0.0.0' -$Description = 'Azure Devops Migration classes, functions and enums.' -$Path = "$($env:PSModulePath.Split(";")[0])\Migrate-ADO" -$FileName = "Migrate-ADO.psd1" - -New-Item -Path $Path -ItemType Directory -Force - -Write-Host $Path -ForegroundColor Gray - -# ---------- CREATES A NEW MANIFEST FOR PACKAGED MODULES -New-ModuleManifest ` - -Path "$Path\$FileName" ` - -NestedModules $IncludedModules ` - -Guid (New-Guid) ` - -ModuleVersion $Version ` - -Description $Description ` - -PowerShellVersion 5.1.0.0 \ No newline at end of file diff --git a/migration-tools/migration-scripts/set-readonly.ps1 b/migration-tools/migration-scripts/set-readonly.ps1 deleted file mode 100644 index 22f30c3..0000000 --- a/migration-tools/migration-scripts/set-readonly.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -# Move all Contributors to Readers -# -# Members of groups such as Project Administrator or Build Administrator groups are not affected - -$groups = Get-ADOGroups ` - -OrgName $SourceOrgName ` - -ProjectName $SourceProjectName ` - -PersonalAccessToken $SourcePAT -$contributors = $groups | Where-Object { $_.Name -eq "Contributors" } -$readers = $groups | Where-Object { $_.Name -eq "Readers" } - -write-host "Adding all Contributor group user members to Readers ..." -foreach ($u in $contributors.UserMembers) { - az devops security group membership add --group-id $readers.Descriptor --member-id $u.PrincipalName --detect $false -} -foreach ($g in $contributors.GroupMembers) { - az devops security group membership add --group-id $readers.Descriptor --member-id $g.PrincipalName --detect $false -} -foreach ($g in $contributors.GroupMembers) { - az devops security group membership remove --group-id $contributors.Descriptor --member-id $g.PrincipalName --detect $false -} -foreach ($u in $contributors.UserMembers) { - az devops security group membership remove --group-id $contributors.Descriptor --member-id $u.PrincipalName --detect $false --yes -} - diff --git a/migration-tools/module-migrators/.gitignore b/migration-tools/module-migrators/.gitignore deleted file mode 100644 index 8b1b6e4..0000000 --- a/migration-tools/module-migrators/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Logging files -Projects */ -_work \ No newline at end of file diff --git a/migration-tools/module-migrators/README.md b/migration-tools/module-migrators/README.md deleted file mode 100644 index 3e328e6..0000000 --- a/migration-tools/module-migrators/README.md +++ /dev/null @@ -1,89 +0,0 @@ -# Introduction - -DevOps Migrator modules and driver script to migrate multiple projects in bulk. Read through the documentation below to learn how to get started with your first migration! - -The modules provided support the following migration options: -- Migrate Users (On the ORG level) -- Migrate Teams (On the project level) -- Migrate Team Members (On the project level) -- Migrate Area Paths -- Migrate Iterations -- Migrate Repos -- Migrate Build Queues - -Migrating work items is not supported in this tool, see dependencies for additional options. - -# Dependencies -- You will need the [Microsoft VSTS Work Item Migrator](https://github.com/microsoft/vsts-work-item-migrator) or something comparable to migrate work items. -- PowerShell 5.1.0 or later -- [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) - - [Azure CLI DevOps Extension](https://docs.microsoft.com/en-us/azure/devops/cli/?view=azure-devops) - -# Getting Started -There is some simple set up that needs to be done before you can run any of the migration modules. Included in the repo are two directories... -- 📂 Migration-Scripts -- 📂 Supporting-Modules - -The `migration-scripts` directory holds scripts, configuration files and a list of projects in the form of a `.csv`. -The `supporting-modules` directory holds `psm1` module files that do the heavy lifting in the migration. Theoretically, a migration can be run completely from the command-line, the items nested under the `migration-scripts` directory are not technically required, but rather act as a centralized place to run repeat migrations in bulk. - ---- -### STEP 1: Installing the modules manifest -The first step to running a migration will be installing the modules manifest. There is also a pre-defined script provided under the `migration-scripts` directory that will automatically do this for you. - -##### STEPS: -- Open the PowerShell script `migration-scripts/create-manifest.ps1` -- Run the script -- The script will create a manifest of all the required modules in your `WindowsPowerShell/Modules` directory. This will allow you to use `Import-Module` to load all of the required modules. - -You should only have to run this script the first time cloning this repo or when the `migration-scripts/create-manifest.ps1` file is changed. Read more about the [`create-manifest.ps1` script here](migration-scripts/README.md#create-manifest.ps1) - ---- -### All steps listed below are only required if you plan on using the `migration-scripts/AllProjects.ps1` script, which is recommended. ---- - - - -### STEP 2: Clone the Microsoft VSTS Work Item Migrator - -The modules provided in this repo do not handle any kind of work item migration, so if you would like to migrate work items I recommend the tool written by Microsoft, but you are not limited to this tool, if you choose to go another route just edit the `migration-scripts/AllProjects.ps1` file to use whatever tool you go with. - -##### STEPS: - -- Navigate to the [Microsoft VSTS Work Item Migrator](https://github.com/microsoft/vsts-work-item-migrator) GitHub and [clone the repo](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github/cloning-a-repository) to your local machine. - ---- - -### STEP 3: Configuring the Configuration.json file - -The `Configuration.json` file is used to set up file locations for logging, PAT tokens for authentication and other information required for running the `migration-scripts/AllProjects.ps1` script. - -##### STEPS - -- Open the file `migration-scripts/Configuration.json` and fill out the required fields... - - Read more about the [configuration fields here](migration-scripts/README.md#configuration.json) - ---- - -### STEP 4: Defining projects to migrate in the Projects.csv file - -##### STEPS - -- Open the file `migration-scripts/Projects.csv` and add the list of source projects to target projects you wish to migrate. -- Read more about the [Projects.csv file here](migration-scripts/README.md#Projects.csv) - -You should now be able to run a migration. - - - -# Running a Migration With The `AllProjects.ps1` Script - -After going through the above steps, navigate to `migration-scripts/AllProjects.ps1` and either invoke the script via the command line or run it within an IDE. - -All of the `Start-ADOMigration...` modules are independent of one another and can be commented out or removed is that particular migration is not desired. - -_(Area paths and Iteration paths need to be migrated if you plan to migrate work items with the microsoft tool)_ - -The `AllProjects.ps1` script is not required to run the migration modules. New scripts can be written around the modules for a more custom migration experience. - -Read more about the [`AllProjects.ps1` script here](migration-scripts/README.md#AllProjects.ps1). \ No newline at end of file diff --git a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-AreaPaths.psm1 b/migration-tools/module-migrators/supporting-modules/Migrate-ADO-AreaPaths.psm1 deleted file mode 100644 index dc13d9b..0000000 --- a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-AreaPaths.psm1 +++ /dev/null @@ -1,141 +0,0 @@ -class ADO_AreaPath { - [String]$Name - [ADO_AreaPath[]]$Children - - ADO_AreaPath( - [String]$name, - [ADO_AreaPath[]]$children - ) { - $this.Name = $name - $this.Children = $children - } -} - -function Start-ADOAreaPathsMigration { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$SourceProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$SourceOrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$SourceHeaders, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetOrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$TargetHeaders - ) - if ($PSCmdlet.ShouldProcess( - "Target project $TargetOrgName/$TargetProjectName", - "Migrate area paths from source project $SourceOrgName/$SourceProjectName") - ) { - Write-Log -Message ' ' - Write-Log -Message '------------------------' - Write-Log -Message '-- Migrate Area Paths --' - Write-Log -Message '------------------------' - Write-Log -Message ' ' - - $areaPaths = Get-AreaPaths ` - -ProjectName $SourceProjectName ` - -OrgName $SourceOrgName ` - -Headers $SourceHeaders - - if ($areaPaths) { - Push-AreaPaths ` - -ProjectName $TargetProjectName ` - -OrgName $TargetOrgName ` - -AreaPaths $areaPaths ` - -Headers $TargetHeaders - - } - else { - Write-Log -Message "No area paths to migrate in project $SourceProjectName" - } - } -} - -function ConvertTo-AreaPathObject { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [Object]$AreaPath - ) - if ($PSCmdlet.ShouldProcess($AreaPath.Name)) { - $ADOAreaPath = [ADO_AreaPath]::new($AreaPath.Name, [ADO_AreaPath[]]@()) - - if ($AreaPath.hasChildren) { - foreach ($child in $AreaPath.Children) { - $ADOAreaPath.Children += (ConvertTo-AreaPathObject -AreaPath $child) - } - } - - return $ADOAreaPath - } -} - -function Get-AreaPaths { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$Headers, - - [Parameter (Mandatory = $FALSE)] - [Int]$Depth = 100 - ) - if ($PSCmdlet.ShouldProcess($ProjectName)) { - $url = "https://dev.azure.com/$OrgName/$ProjectName/_apis/wit/classificationnodes/Areas?`$depth=$Depth&api-version=5.0-preview.2" - $results = Invoke-RestMethod -Method GET -Uri $url -Headers $headers - - [ADO_AreaPath[]]$areaPaths = @() - - foreach ($result in $results.Children) { - $areaPaths += (ConvertTo-AreaPathObject -AreaPath $result) - } - - return $areaPaths - } -} - -function Push-AreaPaths { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [ADO_AreaPath[]]$AreaPaths, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$Headers - ) - if ($PSCmdlet.ShouldProcess($ProjectName)) { - $targetAreaPaths = Get-AreaPaths -ProjectName $ProjectName -OrgName $OrgName -Headers $Headers - $url = "https://dev.azure.com/$OrgName/$ProjectName/_apis/wit/classificationnodes/Areas?api-version=6.0" - - foreach ($areaPath in $AreaPaths) { - if ($null -ne ($targetAreaPaths | Where-Object { $_.Name -ieq $areaPath.Name } )) { - Write-Log -Message "Area path [$($areaPath.Name)] already exists in target.. " - continue - } - - $body = $areaPath | ConvertTo-Json - Invoke-RestMethod -Method POST -Uri $url -Body $body -Headers $headers -ContentType "application/json" - } - } -} \ No newline at end of file diff --git a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-BuildQueues.psm1 b/migration-tools/module-migrators/supporting-modules/Migrate-ADO-BuildQueues.psm1 deleted file mode 100644 index 1f10f0a..0000000 --- a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-BuildQueues.psm1 +++ /dev/null @@ -1,162 +0,0 @@ -class ADO_BuildQueue { - [Int]$Id - [String]$ProjectId - [String]$Name - [Boolean]$IsHosted - [String]$PoolType - - ADO_BuildQueue( - [Object]$object - ) { - $this.Id = $object.id - $this.ProjectId = $object.projectId - $this.Name = $object.pool.name - $this.IsHosted = $object.pool.isHosted - $this.PoolType = $object.pool.poolType - } -} - -function Start-ADOBuildQueuesMigration { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$SourceProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$SourceOrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$SourceHeaders, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetOrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$TargetHeaders - ) - if ($PSCmdlet.ShouldProcess( - "Target project $TargetOrg/$TargetProjectName", - "Migrate build queries from source project $SourceOrg/$SourceProjectName") - ) { - Write-Log -Message ' ' - Write-Log -Message '--------------------------' - Write-Log -Message '-- Migrate Build Queues --' - Write-Log -Message '--------------------------' - Write-Log -Message ' ' - - $queues = Get-BuildQueues ` - -ProjectName $SourceProjectName ` - -OrgName $SourceOrgName ` - -Headers $Sourceheaders - - Push-BuildQueues ` - -ProjectName $TargetProjectName ` - -OrgName $TargetOrgName ` - -Queues $queues ` - -Headers $TargetHeaders - } -} - -function Get-BuildQueues { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$Headers - ) - if ($PSCmdlet.ShouldProcess($ProjectName)) { - $project = Get-ADOProjects -org $OrgName -Headers $Headers -ProjectName $ProjectName - - $url = "https://dev.azure.com/$OrgName/$($project.id)/_apis/distributedtask/queues?api-version=5.1-preview" - - $results = Invoke-RestMethod -Method Get -uri $url -Headers $Headers - - [ADO_BuildQueue[]]$queues = @() - - foreach ($result in $results.value) { - $queues += [ADO_BuildQueue]::new($result) - } - - return $queues - } -} - -function Push-BuildQueues { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [ADO_BuildQueue[]]$queues, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$Headers - ) - if ($PSCmdlet.ShouldProcess($ProjectName)) { - $targetQueues = Get-BuildQueues -ProjectName $ProjectName -org $OrgName -headers $Headers - - foreach ($queue in $queues) { - if ($queue.IsHosted -or $queue.Name -eq "Default") { - continue - } - - if ($null -ne ($targetQueues | Where-Object { $_.Name -ieq $queue.Name })) { - Write-Log -Message "Build queue [$($queue.Name)] already exists in target.. " - continue - } - - Write-Log -Message "Attempting to create [$($queue.Name)] in target.. " - try { - New-BuildQueue -Headers $targetHeaders -ProjectName $ProjectName -OrgName $OrgName -Queue $queue - Write-Log -Message "Done!" -LogLevel SUCCESS - } - catch { - Write-Log -Message ($_ | ConvertFrom-Json).message ERROR - } - } - } -} - -function New-BuildQueue { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [ADO_BuildQueue]$queue, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$Headers - ) - if ($PSCmdlet.ShouldProcess($ProjectName)) { - $project = Get-ADOProjects -OrgName $OrgName -Headers $Headers -ProjectName $ProjectName - - $url = "https://dev.azure.com/$OrgName/$($project.id)/_apis/distributedtask/queues?api-version=5.1-preview&authorizePipelines=true" - - $body = @{ - "projectId" = $queue.ProjectId - "name" = $queue.Name - "id" = $queue.Id - } | ConvertTo-Json - - $results = Invoke-RestMethod -Method Post -uri $url -Headers $Headers -Body $body -ContentType "application/json" - - return $results - } -} \ No newline at end of file diff --git a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Common.psm1 b/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Common.psm1 deleted file mode 100644 index 8d515da..0000000 --- a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Common.psm1 +++ /dev/null @@ -1,299 +0,0 @@ -[Flags()] enum LogLevel { - DEBUG = 0 - INFO - SUCCESS - WARNING - ERROR -} - -function New-HTTPHeaders { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$PersonalAccessToken - ) - if ($PSCmdlet.ShouldProcess('DevOps PAT', 'Return basic auth HTTP headers')) { - $authToken = [System.Convert]::ToBase64String([System.Text.ASCIIEncoding]::ASCII.GetBytes([string]::Format("{0}:{1}", "", $PersonalAccessToken))) - $headers = @{'Authorization' = "Basic $authToken" } - return $headers - } -} - -# Log in & set context with the -# Azure Devops CLI -function Set-AzDevOpsContext { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$PersonalAccessToken, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $FALSE)] - [String]$ProjectName = '' - ) - if ($PSCmdlet.ShouldProcess('DevOps PAT', 'Set DevOps environment variable')) { - $Env:AZURE_DEVOPS_EXT_PAT = $PersonalAccessToken - az devops configure --defaults "project=$ProjectName" "organization=https://dev.azure.com/$OrgName" - } -} - -function Get-ADOProjects { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [Hashtable]$Headers, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $FALSE)] - [String]$ProjectName - ) - if ($PSCmdlet.ShouldProcess("$org/$ProjectName")) { - if ($ProjectName) { - $url = "https://dev.azure.com/$OrgName/_apis/projects/$ProjectName" - return Invoke-RestMethod -Method Get -uri $url -Headers $Headers - } - else { - $url = "https://dev.azure.com/$OrgName/_apis/projects?`$top=600&api-version=5.1" - $results = Invoke-RestMethod -Method Get -uri $url -Headers $Headers - return $results.value - } - } -} - -function Write-Log { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$Message, - - [Parameter (Mandatory = $FALSE)] - [LogLevel]$LogLevel = [LogLevel]::INFO, - - [Parameter (Mandatory = $FALSE)] - [String]$LogPath = ($env:MIGRATION_LOGS_PATH) - ) - if ($PSCmdlet.ShouldProcess("[$LogLevel]: $Message")) { - [System.ConsoleColor]$color = [System.ConsoleColor]::White - $Message = "[$LogLevel]: $Message" - - switch ($LogLevel) { - [LogLevel]::DEBUG { - $color = [System.ConsoleColor]::Gray - } - [LogLevel]::SUCCESS { - $color = [System.ConsoleColor]::Green - } - [LogLevel]::WARNING { - $color = [System.ConsoleColor]::Yellow - } - [LogLevel]::ERROR { - $color = [System.ConsoleColor]::Red - } - } - - Write-Host $Message -ForegroundColor $color - - if ($LogPath) { - Write-LogAsync ` - -Text $Message ` - -Level $LogLevel ` - -LogPath "$LogPath\logs\migration-$($LogLevel.ToString().ToLower()).log" ` - -UseMutex - Write-LogAsync ` - -Text $Message ` - -Level $LogLevel ` - -LogPath "$LogPath\logs\migration.log" ` - -UseMutex - } - } -} - -function Write-LogAsync { - [CmdletBinding(SupportsShouldProcess)] - param - ( - [Parameter(Mandatory = $TRUE)] - [String]$Text, - - [Parameter(Mandatory = $TRUE)] - [LogLevel]$Level, - - [Parameter(Mandatory = $TRUE)] - [string]$LogPath, - - [Parameter(Position = 3)] - [Switch]$UseMutex - ) - if ($PSCmdlet.ShouldProcess($LogPath, "Log '$Message' to")) { - Write-Verbose "Log: $LogPath" - [String]$date = (get-date).ToString() - if (Test-Path $LogPath) { - if ((Get-Item $LogPath).length -gt 5mb) { - $filenamedate = get-date -Format 'MM-dd-yy hh.mm.ss' - $archivelog = ("$LogPath.$filenamedate.archive").Replace('/', '-') - copy-item $LogPath -Destination $archivelog - Remove-Item $LogPath -force - Write-Verbose 'Rolled the log.' - } - } - else { - New-Item -Path $LogPath -ItemType File -Force - } - - $line = "[$date] [$Level] $text" - if ($UseMutex) { - $logMutex = New-Object System.Threading.Mutex($false, 'LogMutex') - $logMutex.WaitOne() | out-null - - $line | out-file -FilePath $LogPath -Append - $logMutex.ReleaseMutex() | out-null - } - else { - $line | out-file -FilePath $LogPath -Append - } - } -} - -function ConvertTo-Object { - begin { $object = New-Object Object } - - process { - - $_.GetEnumerator() | ForEach-Object { Add-Member -inputObject $object -memberType NoteProperty -name $_.Name -value $_.Value } - - } - - end { $object } -} - -function ConvertTo-Hashtable { - [CmdletBinding()] - [OutputType('hashtable')] - param ( - [Parameter(ValueFromPipeline)] - $InputObject - ) - - process { - ## Return null if the input is null. This can happen when calling the function - ## recursively and a property is null - if ($null -eq $InputObject) { - return $null - } - - ## Check if the input is an array or collection. If so, we also need to convert - ## those types into hash tables as well. This function will convert all child - ## objects into hash tables (if applicable) - if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) { - $collection = @( - foreach ($object in $InputObject) { - ConvertTo-Hashtable -InputObject $object - } - ) - - ## Return the array but don't enumerate it because the object may be pretty complex - Write-Output -NoEnumerate $collection - } - elseif ($InputObject -is [psobject]) { - ## If the object has properties that need enumeration - ## Convert it to its own hash table and return it - $hash = @{} - foreach ($property in $InputObject.PSObject.Properties) { - $hash[$property.Name] = ConvertTo-Hashtable -InputObject $property.Value - } - $hash - } - else { - ## If the object isn't an array, collection, or other object, it's already a hash table - ## So just return it. - $InputObject - } - } -} - -function Get-ProjectFolderPath { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$RunDate, - - [Parameter (Mandatory = $FALSE)] - [String]$SourceProject, - - [Parameter (Mandatory = $FALSE)] - [String]$TargetProject, - - [Parameter (Mandatory = $TRUE)] - [String]$Root - ) - if ($PSCmdlet.ShouldProcess("[$LogLevel]: $Message")) { - if (!$SourceProject -or !$TargetProject) { - return "$Root\Projects\$RunDate\OrgMigrationLogs" - } - return "$Root\Projects\$RunDate\$SourceProject-to-$TargetProject" - } -} - -function Set-ProjectFolders { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$RunDate, - - [Parameter (Mandatory = $TRUE)] - [Object[]]$Projects, - - [Parameter (Mandatory = $TRUE)] - [String]$SourceOrg, - - [Parameter (Mandatory = $TRUE)] - [String]$SourcePAT, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetOrg, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetPAT, - - [Parameter (Mandatory = $TRUE)] - [String]$SavedAzureQuery, - - [Parameter (Mandatory = $TRUE)] - [String]$MSConfigPath, - - [Parameter (Mandatory = $TRUE)] - [String]$Root - ) - - foreach ($project in $Projects) { - Write-Log -Message "Setting up workspace for migration $($Project.SourceProject) --> $($Project.TargetProject)" - - # Configure the JSON file (for migrating work items) - $baseJson = Get-Content -path $MSConfigPath | Out-String | ConvertFrom-Json - $baseJson.'source-connection'.'account' = "https://dev.azure.com/$SourceOrg" - $baseJson.'source-connection'.'project' = $Project.SourceProject - $baseJson.'source-connection'.'access-token' = $SourcePAT - - $baseJson.'target-connection'.'account' = "https://dev.azure.com/$TargetOrg" - $baseJson.'target-connection'.'project' = $Project.TargetProject - $baseJson.'target-connection'.'access-token' = $TargetPAT - - $baseJson.'query' = $SavedAzureQuery - - # Create the project folder - $ProjectFolder = Get-ProjectFolderPath ` - -RunDate $RunDate ` - -SourceProject $Project.SourceProject ` - -TargetProject $Project.TargetProject ` - -Root $Root - - New-Item -ItemType Directory -Force -Path $ProjectFolder - $baseJson | ConvertTo-Json -depth 100 | Out-File "$($ProjectFolder)\ProjectConfiguration.json" -Force - } - - Write-Log -Message '-------------------------- Done creating Directories --------------------------' -LogLevel DEBUG -} \ No newline at end of file diff --git a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Groups.psm1 b/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Groups.psm1 deleted file mode 100644 index 413a06e..0000000 --- a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Groups.psm1 +++ /dev/null @@ -1,334 +0,0 @@ -class ADO_Group { - [String]$Id - [String]$Name - [String]$PrincipalName - [String]$Description - [String]$Descriptor - [ADO_GroupMember[]]$UserMembers = @() - [ADO_Group[]]$GroupMembers = @() - - ADO_Group( - [String]$id, - [String]$name, - [String]$principalName, - [String]$description, - [String]$descriptor - ) { - $this.Id = $id - $this.Name = $name - $this.PrincipalName = $principalName - $this.Description = $description - $this.Descriptor = $descriptor - } -} - -class ADO_GroupMember { - [String]$Id - [String]$Name - [String]$PrincipalName - - ADO_GroupMember( - [String]$id, - [String]$name, - [String]$principalName - ) { - $this.Id = $id - $this.Name = $name - $this.PrincipalName = $principalName - } -} - -function Start-ADOGroupsMigration { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$SourceProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$SourceOrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$SourcePAT, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetOrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetPAT - ) - if ($PSCmdlet.ShouldProcess( - "Target project $TargetOrg/$TargetProjectName", - "Migrate groups & members from source project $SourceOrg/$SourceProjectName") - ) { - Write-Log -Message ' ' - Write-Log -Message '--------------------' - Write-Log -Message '-- Migrate Groups --' - Write-Log -Message '--------------------' - Write-Log -Message ' ' - - $groups = Get-ADOGroups ` - -OrgName $SourceOrgName ` - -ProjectName $SourceProjectName ` - -PersonalAccessToken $SourcePAT - - Push-ADOGroups ` - -PersonalAccessToken $TargetPAT ` - -OrgName $TargetOrgName ` - -ProjectName $TargetProjectName ` - -Groups $groups - } -} - -function Get-ADOGroups { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$PersonalAccessToken, - - [Parameter (Mandatory = $FALSE)] - [String]$GroupDisplayName - ) - if ($PSCmdlet.ShouldProcess("$org/$ProjectName")) { - Set-AzDevOpsContext ` - -PersonalAccessToken $PersonalAccessToken ` - -OrgName $OrgName ` - -ProjectName $ProjectName - - if ($GroupDisplayName) { - $groups = az devops security group list --query "graphGroups[?displayName == '$($GroupDisplayName)']" --detect $false | ConvertFrom-Json - if (!$groups) { - throw "Group called '$GroupDisplayName' cannot be found in '$OrgName/$ProjectName'" - } - } - else { - $groups = (az devops security group list --detect $false | ConvertFrom-Json).graphGroups - } - - [ADO_Group[]]$groupsFound = @() - foreach ($group in $groups) { - $group = [ADO_Group]::new($group.originId, $group.displayName, $group.principalName, $group.description, $group.descriptor) - $members = Get-ADOGroupMembers ` - -OrgName $OrgName ` - -ProjectName $ProjectName ` - -PersonalAccessToken $PersonalAccessToken ` - -GroupDescriptor $group.Descriptor - - $group.GroupMembers = $members.GroupGroupMembers - $group.UserMembers = $members.GroupUserMembers - - $groupsFound += $group - } - return $groupsFound - } -} - -function Get-ADOGroupMembers { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$PersonalAccessToken, - - [Parameter (Mandatory = $TRUE)] - [String]$GroupDescriptor - ) - if ($PSCmdlet.ShouldProcess("$org/$ProjectName")) { - Set-AzDevOpsContext ` - -PersonalAccessToken $PersonalAccessToken ` - -OrgName $OrgName ` - -ProjectName $ProjectName - - [ADO_GroupMember[]]$GroupUserMembers = @() - [ADO_Group[]]$GroupGroupMembers = @() - $members = az devops security group membership list --id $GroupDescriptor --detect $false | ConvertFrom-Json - if ($members) { - $descriptors = $members | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name - - foreach ($descriptor in $descriptors) { - $member = $members.$descriptor - if ($member.subjectKind -eq "user") { - $GroupUserMembers += [ADO_GroupMember]::new($member.originId, $member.displayName, $member.principalName) - } - else { - $GroupGroupMembers += [ADO_Group]::new($member.originId, $member.displayName, $member.principalName, $member.description, $member.descriptor) - } - } - } - - return @{ - "GroupUserMembers" = $GroupUserMembers - "GroupGroupMembers" = $GroupGroupMembers - } - } -} - -function Push-ADOGroups { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$PersonalAccessToken, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [ADO_Group[]]$Groups - ) - if ($PSCmdlet.ShouldProcess("$org/$ProjectName")) { - Set-AzDevOpsContext ` - -PersonalAccessToken $PersonalAccessToken ` - -OrgName $OrgName ` - -ProjectName $ProjectName - - $targetGroups = Get-ADOGroups ` - -OrgName $OrgName ` - -ProjectName $ProjectName ` - -PersonalAccessToken $PersonalAccessToken - - # Create all groups before adding members to each group - [ADO_Group[]]$newTargetGroups = @() - foreach ($group in $Groups) { - $existingGroup = $targetGroups | Where-Object { $_.Name -ieq $group.Name } - if ($null -ine $existingGroup) { - Write-Log -Message "Group [$($group.Name)] already exists in target.. " - $newTargetGroups += $existingGroup - continue - } - $result = New-ADOGroup ` - -PersonalAccessToken $PersonalAccessToken ` - -OrgName $OrgName ` - -ProjectName $ProjectName ` - -GroupName $group.Name ` - -GroupDescription $group.Description - - if ($null -ine $result.NewGroup) { - $newTargetGroups += $result.NewGroup - } - } - - foreach ($newTargetGroup in $newTargetGroups) { - [ADO_Group]$sourceGroup = $Groups | Where-Object { $_.Name -ieq $newTargetGroup.Name } - Push-GroupMembers ` - -OrgName $OrgName ` - -ProjectName $ProjectName ` - -PersonalAccessToken $PersonalAccessToken ` - -SourceGroup $sourceGroup ` - -TargetGroup $newTargetGroup - } - } -} - -function New-ADOGroup { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$PersonalAccessToken, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$GroupName, - - [Parameter (Mandatory = $FALSE)] - [String]$GroupDescription = "" - ) - if ($PSCmdlet.ShouldProcess("$org/$ProjectName")) { - Set-AzDevOpsContext ` - -PersonalAccessToken $PersonalAccessToken ` - -OrgName $OrgName ` - -ProjectName $ProjectName - - if ($Group.Description) { - $result = az devops security group create --name $GroupName --description $GroupDescription --detect $false - } - else { - $result = az devops security group create --name $GroupName --detect $false - } - if (!$result) { - Write-Log -Message "Could not create a new group with name '$($GroupName)'. The group name may be reserved by the system." -LogLevel ERROR - return @{ - "NewGroup" = $null - } - } - $result = $result | ConvertFrom-JSON - $newGroup = [ADO_Group]::new($result.originId, $result.displayName, $result.principalName, $result.description, $result.descriptor) - return @{ - "NewGroup" = $newGroup - } - } -} - -function Push-GroupMembers { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$PersonalAccessToken, - - [Parameter (Mandatory = $TRUE)] - [ADO_Group]$SourceGroup, - - [Parameter (Mandatory = $FALSE)] - [ADO_Group]$TargetGroup = $null - ) - if ($PSCmdlet.ShouldProcess("$GroupDisplayName")) { - Set-AzDevOpsContext ` - -PersonalAccessToken $PersonalAccessToken ` - -OrgName $OrgName ` - -ProjectName $ProjectName - - # Add user members - foreach ($userMember in $SourceGroup.UserMembers) { - if ($null -ne ($TargetGroup.UserMembers | Where-Object { $_.PrincipalName -ieq $userMember.PrincipalName } )) { - Write-Log -Message "Member [$($userMember.Name)] already exists in target group.. " - continue - } - az devops security group membership add --group-id $TargetGroup.Descriptor --member-id $userMember.PrincipalName --detect $false - } - # Add group members - foreach ($groupMember in $SourceGroup.GroupMembers) { - try { - $groupOnTarget = Get-ADOGroups ` - -OrgName $OrgName ` - -ProjectName $ProjectName ` - -PersonalAccessToken $PersonalAccessToken ` - -GroupDisplayName $groupMember.Name - - if ($null -ne ($TargetGroup.GroupMembers | Where-Object { $_.Name -ieq $groupMember.Name } )) { - Write-Log -Message "Member [$($groupMember.Name)] already exists in target group.. " - continue - } - az devops security group membership add --group-id $TargetGroup.Descriptor --member-id $groupOnTarget.PrincipalName --detect $false - } - catch { - Write-Log -Message $_.Exception.Message -LogLevel ERROR - } - } - } -} \ No newline at end of file diff --git a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-IterationPaths.psm1 b/migration-tools/module-migrators/supporting-modules/Migrate-ADO-IterationPaths.psm1 deleted file mode 100644 index f367f45..0000000 --- a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-IterationPaths.psm1 +++ /dev/null @@ -1,136 +0,0 @@ -class ADO_IterationPath { - [String]$Name - [ADO_IterationPath[]]$Children - - ADO_IterationPath( - [String]$name, - [ADO_IterationPath[]]$children - ) { - $this.Name = $name - $this.Children = $children - } -} - -function Start-ADOIterationPathsMigration { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$SourceProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$SourceOrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$SourceHeaders, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetOrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$TargetHeaders - ) - if ($PSCmdlet.ShouldProcess( - "Target project $TargetOrgName/$TargetProjectName", - "Migrate area paths from source project $SourceOrgName/$SourceProjectName") - ) { - Write-Log -Message ' ' - Write-Log -Message '-----------------------------' - Write-Log -Message '-- Migrate Iteration Paths --' - Write-Log -Message '-----------------------------' - Write-Log -Message ' ' - - $iterationPaths = Get-IterationPaths ` - -ProjectName $SourceProjectName ` - -OrgName $SourceOrgName ` - -Headers $SourceHeaders - - Push-IterationPaths ` - -ProjectName $TargetProjectName ` - -OrgName $TargetOrgName ` - -IterationPaths $iterationPaths ` - -Headers $TargetHeaders - } -} - -function ConvertTo-IterationPathObject { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [Object]$IterationPath - ) - if ($PSCmdlet.ShouldProcess($IterationPath.Name)) { - $ADOIterationPath = [ADO_IterationPath]::new($IterationPath.Name, [ADO_IterationPath[]]@()) - - if ($IterationPath.hasChildren) { - foreach ($child in $IterationPath.Children) { - $ADOIterationPath.Children += (ConvertTo-AreaPathObject -IterationPath $child) - } - } - - return $ADOIterationPath - } -} - -function Get-IterationPaths { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$Headers, - - [Parameter (Mandatory = $FALSE)] - [Int]$Depth = 100 - ) - if ($PSCmdlet.ShouldProcess($ProjectName)) { - $url = "https://dev.azure.com/$OrgName/$ProjectName/_apis/wit/classificationnodes/Iterations?`$depth=$Depth&api-version=6.0" - $results = Invoke-RestMethod -Method GET -Uri $url -Headers $headers - - [ADO_IterationPath[]]$iterationPaths = @() - - foreach ($result in $results.Children) { - $iterationPaths += (ConvertTo-IterationPathObject -IterationPath $result) - } - - return $iterationPaths - } -} - -function Push-IterationPaths { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [ADO_IterationPath[]]$IterationPaths, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$Headers - ) - if ($PSCmdlet.ShouldProcess($ProjectName)) { - $targetIterationPaths = Get-IterationPaths -ProjectName $ProjectName -OrgName $OrgName -Headers $Headers - - $url = "https://dev.azure.com/$OrgName/$ProjectName/_apis/wit/classificationnodes/Iterations?api-version=6.0" - - foreach ($iterationPath in $IterationPaths) { - if ($null -ne ($targetIterationPaths | Where-Object { $_.Name -ieq $iterationPath.Name } )) { - Write-Log -Message "Iteration path [$($iterationPath.Name)] already exists in target.. " - continue - } - - $body = $iterationPath | ConvertTo-Json - Invoke-RestMethod -Method POST -Uri $url -Body $body -Headers $headers -ContentType "application/json" - } - } -} \ No newline at end of file diff --git a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Pipelines.psm1 b/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Pipelines.psm1 deleted file mode 100644 index d9719ca..0000000 --- a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Pipelines.psm1 +++ /dev/null @@ -1,27 +0,0 @@ -function Get-Pipelines { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$Headers, - - [Parameter (Mandatory = $FALSE)] - [String]$RepoId = $NULL - ) - if ($PSCmdlet.ShouldProcess($ProjectName)) { - - $url = "https://dev.azure.com/$OrgName/$ProjectName/_apis/build/definitions?api-version=5.1" - if ($RepoId) { - $url = "https://dev.azure.com//$OrgName/$ProjectName/_apis/build/definitions?repositoryId=$RepoId&repositoryType=TfsGit"; - } - - $results = Invoke-RestMethod -Method Get -uri $url -Headers $headers - - return $results.value - } -} diff --git a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Repos.psm1 b/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Repos.psm1 deleted file mode 100644 index aa84808..0000000 --- a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Repos.psm1 +++ /dev/null @@ -1,224 +0,0 @@ -function Start-ADORepoMigration { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$SourceProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$SourceOrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$SourceHeaders, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetOrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$TargetHeaders, - - [Parameter (Mandatory = $TRUE)] - [String]$ReposPath - ) - if ($PSCmdlet.ShouldProcess( - "Target project $TargetOrg/$TargetProjectName", - "Migrate repos from source project $SourceOrg/$SourceProjectName") - ) { - Write-Log -Message ' ' - Write-Log -Message '-------------------' - Write-Log -Message '-- Migrate Repos --' - Write-Log -Message '-------------------' - Write-Log -Message ' ' - - $reposToPush = Copy-Repos ` - -SourceProjectName $SourceProjectName ` - -SourceOrgName $SourceOrgName ` - -SourceHeaders $sourceHeaders ` - -TargetProjectName $TargetProjectName ` - -TargetOrgName $TargetOrgName ` - -TargetHeaders $TargetHeaders ` - -ReposPath $ReposPath - - Push-Repos ` - -ProjectName $TargetProjectName ` - -OrgName $TargetOrgName ` - -Repos $reposToPush ` - -Headers $TargetHeaders ` - -ReposPath $ReposPath - } -} - -function Get-Repos { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$Headers - ) - if ($PSCmdlet.ShouldProcess($ProjectName)) { - $url = "https://dev.azure.com/$OrgName/$ProjectName/_apis/git/repositories?api-version=5.0" - - $results = Invoke-RestMethod -Method Get -uri $url -Headers $headers - - return $results.value - } -} - -function Copy-Repos { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$SourceProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$SourceOrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$SourceHeaders, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetOrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$TargetHeaders, - - [Parameter (Mandatory = $TRUE)] - [String]$ReposPath - ) - if ($PSCmdlet.ShouldProcess("path $ReposPath")) { - try { - $final = [object[]]@() - - $targetRepos = Get-Repos ` - -ProjectName $TargetProjectName ` - -OrgName $TargetOrgName ` - -Headers $TargetHeaders - $sourceRepos = Get-Repos ` - -ProjectName $SourceProjectName ` - -OrgName $SourceOrgName ` - -Headers $SourceHeaders - - foreach ($sourceRepo in $sourceRepos) { - - if ($null -ne ($targetRepos | Where-Object { $_.name -ieq $sourceRepo.name })) { - Write-Log -Message "Repo [$($sourceRepo.name)] already exists in target.. " - continue - } - - Write-Log -Message "Cloning $($sourceRepo.name)" - git clone $sourceRepo.remoteURL "`"$ReposPath\$($sourceRepo.name)`"" - $final += $sourceRepo - } - return $final - } - catch { - Write-Log -Message "Error cloning repos from org $SourceOrgName and project $SourceProjectName" -LogLevel ERROR - Write-Error -Messsage $_ -LogLevel ERROR - return - } - } -} - -function Push-Repos { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [Object[]]$Repos, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$Headers, - - [Parameter (Mandatory = $TRUE)] - [String]$ReposPath - ) - if ($PSCmdlet.ShouldProcess($ProjectName, "Push repos from $ReposPath")) { - $savedPath = $(Get-Location).Path - $targetRepos = Get-Repos -ProjectName $ProjectName -OrgName $OrgName -Headers $Headers - - foreach ($repo in $Repos) { - Write-Log -Message "Pushing repo $($repo.Name)" - - $targetRepo = $targetRepos | Where-Object { $_.name -ieq $repo.name } - if ($null -eq $targetRepo) { - try { - Write-Log -Message 'Initializing repository ... ' - New-GitRepository -ProjectName $ProjectName -OrgName $Orgname -RepoName $repo.name -Headers $Headers - } - catch { - Write-Log -Message "Error initializing repo: $_ " -LogLevel ERROR - } - } - - try { - Write-Log -Message 'Pushing repo ...' - Write-Log -Message "Entering path `"$ReposPath\$($repo.name)`"" - Set-Location "$ReposPath\$($repo.name)" - - $gitTarget = "https://$TargetOrgName@dev.azure.com/$TargetOrgName/$TargetProjectName/_git/" + $repo.name - - git remote add target $gitTarget - git push -u target --all - } - catch { - Write-Log -Message "Error adding remote: $_" -LogLevel ERROR - } - finally { - Set-Location $savedPath - } - } - } -} - -function New-GitRepository { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$RepoName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$Headers - ) - if ($PSCmdlet.ShouldProcess($ProjectName, "Push repos from $ReposPath")) { - $url = "$org/_apis/git/repositories?api-version=5.1" - } - $url = "https://dev.azure.com/$OrgName/_apis/git/repositories?api-version=5.1" - - $project = Get-ADOProjects -OrgName $OrgName -Headers $Headers -ProjectName $ProjectName - - $requestBody = @{ - name = $RepoName - project = @{ - id = $project.id - } - } | ConvertTo-Json - - try { - Invoke-RestMethod -Method post -uri $url -Headers $Headers -Body $requestBody -ContentType 'application/json' - } - catch { - Write-Log -Message "Error creating repo $RepoName in project $projectId : $($_.Exception) " - } - -} \ No newline at end of file diff --git a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Teams.psm1 b/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Teams.psm1 deleted file mode 100644 index 053c1f8..0000000 --- a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Teams.psm1 +++ /dev/null @@ -1,152 +0,0 @@ -class ADO_Team { - [String]$Id - [String]$Name - [String]$Description - - ADO_Team( - [String]$id, - [String]$name, - [String]$description - ) { - $this.Id = $id - $this.Name = $name - $this.Description = $description - } -} - -function Start-ADOTeamsMigration { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$SourceProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$SourceOrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$SourceHeaders, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetOrgName, - - [Parameter (Mandatory = $TRUE)] - [Hashtable]$TargetHeaders - ) - if ($PSCmdlet.ShouldProcess( - "Target project $TargetOrg/$TargetProjectName", - "Migrate teams from source project $SourceOrg/$SourceProjectName") - ) { - Write-Log -Message ' ' - Write-Log -Message '-------------------' - Write-Log -Message '-- Migrate Teams --' - Write-Log -Message '-------------------' - Write-Log -Message ' ' - - $teams = Get-ADOProjectTeams ` - -Headers $SourceHeaders ` - -OrgName $SourceOrgName ` - -ProjectName $SourceProjectName - - Push-ADOTeams ` - -Headers $TargetHeaders ` - -OrgName $TargetOrgName ` - -ProjectName $TargetProjectName ` - -Teams $teams - } -} - -function Get-ADOProjectTeams { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [Hashtable]$Headers, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $FALSE)] - [String]$TeamDisplayName - ) - if ($PSCmdlet.ShouldProcess("$org/$ProjectName")) { - $url = "https://dev.azure.com/$OrgName/_apis/projects/$ProjectName/teams?api-version=6.0" - $results = Invoke-RestMethod -Method Get -uri $url -Headers $Headers - - [ADO_Team[]]$teams = @() - foreach ($result in $results.value) { - $teams += [ADO_Team]::new($result.id, $result.name, $result.description) - } - - if ($TeamDisplayName) { - return $teams | Where-Object { $_.Name -eq $TeamDisplayName } - } - return $teams - } -} -function Push-ADOTeams { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [Hashtable]$Headers, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [ADO_Team[]]$Teams - ) - if ($PSCmdlet.ShouldProcess("$org/$ProjectName")) { - $targetTeams = Get-ADOProjectTeams ` - -Headers $Headers ` - -OrgName $OrgName ` - -ProjectName $ProjectName - - foreach ($team in $Teams) { - if ($null -ne ($targetTeams | Where-Object { $_.Name -ieq $team.Name } )) { - Write-Log -Message "Team [$($team.Name)] already exists in target.. " - continue - } - New-ADOTeam ` - -Headers $Headers ` - -OrgName $OrgName ` - -ProjectName $ProjectName ` - -Team $team - - } - } -} - -function New-ADOTeam { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [Hashtable]$Headers, - - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$ProjectName, - - [Parameter (Mandatory = $TRUE)] - [ADO_Team]$Team - ) - if ($PSCmdlet.ShouldProcess("$org/$ProjectName")) { - $url = "https://dev.azure.com/$OrgName/_apis/projects/$ProjectName/teams?api-version=6.0" - - $body = @{ - "name" = $Team.Name - "description" = $Team.Description - } | ConvertTo-Json - - Invoke-RestMethod -Method Post -uri $url -Body $body -Headers $Headers -ContentType "application/json" - } -} \ No newline at end of file diff --git a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Users.psm1 b/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Users.psm1 deleted file mode 100644 index 60a95d6..0000000 --- a/migration-tools/module-migrators/supporting-modules/Migrate-ADO-Users.psm1 +++ /dev/null @@ -1,154 +0,0 @@ -class ADO_User { - [String]$Id - [String]$PrincipalName - [String]$DisplayName - [String]$MailAddress - [String]$LicenseType - - ADO_User( - [String]$id, - [String]$principalName, - [String]$displayName, - [String]$mailAddress, - [String]$licenseType - ) { - $this.Id = $id - $this.PrincipalName = $principalName - $this.DisplayName = $displayName - $this.MailAddress = $mailAddress - $this.LicenseType = $licenseType - } -} - -function Start-ADOUserMigration { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$SourceOrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$SourcePat, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetOrgName, - - [Parameter (Mandatory = $TRUE)] - [string]$TargetPat - ) - if ($PSCmdlet.ShouldProcess( - "Target org $TargetOrgName", - "Migrate users from source org $SourceOrgName") - ) { - Write-Log -Message ' ' - Write-Log -Message '-----------------------' - Write-Log -Message '-- Migrate Org Users --' - Write-Log -Message '-----------------------' - Write-Log -Message ' ' - - [ADO_User[]]$sourceUsers = Get-ADOUsers ` - -OrgName $SourceOrgName ` - -PersonalAccessToken $SourcePat - - Push-ADOUsers ` - -OrgName $TargetOrgName ` - -PersonalAccessToken $TargetPat ` - -Users $sourceUsers - } -} - -function Push-ADOUsers { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$PersonalAccessToken, - - [Parameter (Mandatory = $TRUE)] - [ADO_User[]]$Users - ) - if ($PSCmdlet.ShouldProcess($OrgName)) { - [ADO_User[]]$targetUsers = Get-ADOUsers ` - -OrgName $OrgName ` - -PersonalAccessToken $PersonalAccessToken - - foreach ($user in $Users) { - # Check for duplicates - if ($null -ne ($targetUsers | Where-Object { $_.PrincipalName -ieq $user.PrincipalName } )) { - Write-Log -Message "User [$($user.PrincipalName)] already exists in target org '$OrgName'... " - continue - } - - # Add user - Write-Log -Message "Add user $($User.DisplayName)" - Add-ADOUser ` - -OrgName $OrgName ` - -PersonalAccessToken $PersonalAccessToken ` - -User $user ` - -ForceStakeholderIfNeeded - } - } -} - -function Add-ADOUser { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$PersonalAccessToken, - - [Parameter (Mandatory = $TRUE)] - [ADO_User]$User, - - # For testing or migrations to orgs with lower subscription plans - [Parameter (Mandatory = $FALSE)] - [Switch]$ForceStakeholderIfNeeded - ) - if ($PSCmdlet.ShouldProcess($OrgName, "Add ADO user $User")) { - Set-AzDevOpsContext ` - -PersonalAccessToken $PersonalAccessToken ` - -OrgName $OrgName - - $response = az devops user add --email-id $User.PrincipalName --license-type $User.LicenseType --detect $false - - if ($ForceStakeholderIfNeeded -and !$response) { - # Lower subscription plan detected - $newLicense = "stakeholder" - $response = az devops user add --email-id $User.PrincipalName --license-type $newLicense --detect $false - if ($response) { - Write-Log ` - -Message "User '$($User.DisplayName)' has been demoted from license $($User.LicenseType) to $newLicense because your subscription does not support that license type." ` - -LogLevel ERROR - } - } - } -} - -function Get-ADOUsers { - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter (Mandatory = $TRUE)] - [String]$OrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$PersonalAccessToken - ) - if ($PSCmdlet.ShouldProcess($OrgName)) { - Set-AzDevOpsContext ` - -PersonalAccessToken $PersonalAccessToken ` - -OrgName $OrgName - - $orgUsers = az devops user list --detect $False | ConvertFrom-Json - - # Convert to ADO User objects - [ADO_User[]]$users = @() - foreach ($orgUser in $orgUsers.members) { - $users += [ADO_User]::new($orgUser.user.originId, $orgUser.user.principalName, $orgUser.user.displayName, $orgUser.user.mailAddress, $orgUser.accessLevel.accountLicenseType) - } - - return $users - } -} \ No newline at end of file diff --git a/migration-tools/module-migrators/supporting-modules/README.md b/migration-tools/module-migrators/supporting-modules/README.md deleted file mode 100644 index c1de426..0000000 --- a/migration-tools/module-migrators/supporting-modules/README.md +++ /dev/null @@ -1,111 +0,0 @@ -# Supporting Modules Directory -This directory holds all of the migration logic provided by this repo. The following operations and their corresponding module files are what is currently supported: - ---- -### Migrate Azure DevOps Users -##### MODULE FILE: `Migrate-ADO-Users.psm1` -This module file provides the following functions (at the ORG level, not project specific): -- `Start-ADOUserMigration` - - Migrates users from one DevOps orginization to another. Relies on all other functions provided in this file. -- `Push-ADOUsers` - - Loops through a list of custom PowerShell `ADO_User` objects and compares for duplicates. If the user is not a duplicate they will be added to the target org with the `Add-ADOUser` function. -- `Add-ADOUser` - - Adds a user by their user principal name to a DevOps orginization and updates their license using the Azure DevOps CLI -- `Get-ADOUsers` - - Gets all users from a specific org and returns them as custom PowerShell `ADO_User` objects. ---- -### Migrate Azure DevOps Teams -##### MODULE FILE: `Migrate-ADO-Teams.psm1` -This module file provides the following functions: -- `Start-ADOTeamsMigration` - - Migrates EMPTY teams from one DevOps orginization to another. Relies on all other functions provided in this file. -- `Get-ADOProjectTeams` - - Gets a list of custom PowerShell `ADO_Team` objects for a specific org and project or a specific `ADO_Team` project if a team name is specified to filter by. -- `Push-ADOTeams` - - Loops through a list of custom PowerShell `ADO_Team` objects and compares for duplicates. If the team is not a duplicate it will be added on the target. -- `New-ADOTeam` - - Creates a new, empty team, in the specified org and project. The creation of a new team also triggers the creation of a new group. ---- -### Migrate Azure DevOps Teams -##### MODULE FILE: `Migrate-ADO-Groups.psm1` -This module file provides the following functions: -- `Start-ADOGroupsMigration` - - Migrates groups and their members from one DevOps orginization to another. Relies on all other functions provided in this file. -- `Get-ADOGroups` - - Gets a list of custom PowerShell `ADO_Group` objects for the specified org and project. -- `Get-ADOGroupMembers` - - Gets a list of user group members and group group members and returns a hashtable of custom PowerShell `ADO_Group` and `ADO_GroupMember` objects. -- `Push-ADOGroups` - - Loops through each source group and creates a new empty target group with `New-ADOGroup`. After all groups are created it re-loops through all of the newly created groups and pushes group members to each group using `Push-GroupMembers`. -- `New-ADOGroup` - - Creates a new, empty, ADO group and returns a custom PowerShell `ADO_Group` object -- `Push-GroupMembers` - - Adds user members and group members of a specified, existing, group. ---- -### Migrate Azure DevOps Area Paths -##### MODULE FILE: `Migrate-ADO-AreaPaths.psm1` -This module file provides the following functions: -- `Start-ADOAreaPathsMigration` - - Migrates area paths from one DevOps project to another. Relies on all other functions provided in this file. -- `ConvertTo-AreaPathObject` - - Flattens the area path objects returned from the DevOps REST API call and converts them to a custom PowerShell `ADO_AreaPath` object. -- `Get-AreaPaths` - - Returns all of the area paths for a given project as a list of custom PowerShell `ADO_AreaPath` objects. -- `Push-AreaPaths` - - Pushes a list of area paths in the form of an array of custom PowerShell `ADO_AreaPath` objects to a given project. ---- -### Migrate Azure DevOps Iteration Paths -##### MODULE FILE: `Migrate-ADO-IterationPaths.psm1` -This module file provides the following functions: -- `Start-ADOIterationPathsMigration` - - Migrates iteration paths from one DevOps project to another. Relies on all other functions provided in this file. -- `ConvertTo-IterationPathObject` - - Flattens the iteration path objects returned from the DevOps REST API call and converts them to a custom PowerShell `ADO_IterationPath` object. -- `Get-AreaPaths` - - Returns all of the iteration paths for a given project as a list of custom PowerShell `ADO_IterationPath` objects. -- `Push-AreaPaths` - - Pushes a list of iteration paths in the form of an array of custom PowerShell `ADO_IterationPath` objects to a given project. ---- -### Migrate Azure DevOps Build Queues -##### MODULE FILE: `Migrate-ADO-BuildQueues.psm1` -This module file provides the following functions: -- `Start-ADOBuildQueuesMigration` - - Migrates build queues from one DevOps project to another. Relies on all other functions provided in this file. -- `Get-BuildQueues` - - Returns all of the build queues for a given project as a list of custom PowerShell `ADO_BuildQueue` objects. -- `Push-BuildQueues` - - Checks if any of the provided build queues already exist & pushes the build queues to DevOps using the `New-BuildQueue` function. Requires a list of custom PowerShell `ADO_BuildQueue` objects. -- `New-BuildQueue` - - Creates a new build queue in DevOps with the properties provided by a custom PowerShell `ADO_BuildQueue` object passed to it. ---- -### Migrate Azure DevOps Repos -##### MODULE FILE: `Migrate-ADO-Repos.psm1` -This module file provides the following functions: -- `Start-ADORepoMigration` - - Migrates repos from one DevOps project to another. Relies on all other functions provided in this file. -- `Get-Repos` - - Gets a list of repos from DevOps for a given project -- `Copy-Repos` - - Clones a list of repos from DevOps to the users local machine using a provided path -- `Push-Repos` - - Loops through a list of provided repo objects, creates a new empty repository for each repo using `New-GitRepository` and uses git to push the downloaded repos to DevOps. Requires `Copy-Repos` to be run first. -- `New-GitRepository` - - Creates a new empty git repo in Azure Devops ---- -### Common Files Shared Between the Migration Modules -##### MODULE FILE: `Migrate-ADO-Common.psm1` -This module file provides the following functions: -- `New-HTTPHeaders` - - Generates basic auth headers using a provided personal access token. -- `Get-ADOProjects` - - Gets either a specific Azure DevOps project by name or all projects for a given org -- `Write-Log` - - Handles writing messages to the console and calling `Write-LogAsync` to log those messages to a file path set via an environment variable -- `Write-LogAsync` - - Handles writing messages to log files to a given path. -- `ConvertTo-Object` -- `ConvertTo-HashTable` -- `Get-ProjectFolderPath` - - Returns the formatted folder path based on the migration start date and target/source projects -- `Set-ProjectFolders` - - Creates a directory of projects to store logs and downloaded repo files during the migration diff --git a/migration-tools/module-migrators/supporting-modules/Tests/Get-Test-Context.psm1 b/migration-tools/module-migrators/supporting-modules/Tests/Get-Test-Context.psm1 deleted file mode 100644 index f062f5f..0000000 --- a/migration-tools/module-migrators/supporting-modules/Tests/Get-Test-Context.psm1 +++ /dev/null @@ -1,32 +0,0 @@ -Import-Module .\supporting-modules\Migrate-ADO-Common.psm1 -Force - -function Get-TestContext { - param( - [Parameter (Mandatory = $TRUE)] - [String]$SourceOrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$SourceProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$SourcePAT, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetOrgName, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetProjectName, - - [Parameter (Mandatory = $TRUE)] - [String]$TargetPAT - ) - return @{ - SourceOrgName = $SourceOrgName - SourceProjectName = $SourceProjectName - SourceHeaders = (New-HTTPHeaders -PersonalAccessToken $SourcePAT) - - TargetOrgName = $TargetOrgName - TargetProjectName = $TargetProjectName - TargetHeaders = (New-HTTPHeaders -PersonalAccessToken $TargetPAT) - } -} \ No newline at end of file diff --git a/migration-tools/module-migrators/supporting-modules/Tests/Migrate-ADO-AreaPaths-Tests.ps1 b/migration-tools/module-migrators/supporting-modules/Tests/Migrate-ADO-AreaPaths-Tests.ps1 deleted file mode 100644 index b71205b..0000000 --- a/migration-tools/module-migrators/supporting-modules/Tests/Migrate-ADO-AreaPaths-Tests.ps1 +++ /dev/null @@ -1,21 +0,0 @@ -BeforeAll { - Import-Module .\supporting-modules\Migrate-ADO-AreaPaths.psm1 -Force - - Import-Module .\supporting-modules\Tests\Get-Test-Context.psm1 -Force - $TestContext = Get-TestContext ` - -SourceOrgName "Micron-Testing" ` - -SourceProjectName "SourceTests" ` - -TargetOrgName "Micron-Testing" ` - -TargetProjectName "TargetTests" -} - -Describe 'Get-AreaPaths' { - it 'Should get Area Paths for the given project' { - $areaPaths = Get-AreaPaths ` - -ProjectName $TestContext.SourceProjectName ` - -OrgName $TestContext.SourceOrgName ` - -Headers $TestContext.SourceHeaders - - $areaPaths | Should -Not -Be $null - } -} \ No newline at end of file diff --git a/tools/migrate-external-repos.ps1 b/tools/migrate-external-repos.ps1 index e6a43e1..1c30821 100644 --- a/tools/migrate-external-repos.ps1 +++ b/tools/migrate-external-repos.ps1 @@ -1,3 +1,22 @@ +[CmdletBinding()] +param ( + [Parameter()] + [String] + $SourceRepoUrl, + $SourceProjectName, + $TargetOrganizationName, + $PersonalAccessToken +) + +function Help() { + Write-Host " + + migrate-external-repos ... + Migrate external repos to Azure DevOps + Assumes user is already authenticated against source git host + " +} + $modulePath = "C:\dev\AzureDevOps-Tools\modules" Import-Module "$modulePath\Migrate-ADO-Common.psm1" Import-Module "$modulePath\Migrate-ADO-Repos.psm1" @@ -16,8 +35,22 @@ foreach ($repo in $repos) { Write-Host "Cloning repo $repo" $sourceRepo = [uri]::EscapeUriString("$repo") + + switch ($SourceProvider) { + "AzureDevOps" { + + } + "GitLab" { + + } + } + git clone "$sourceRepoPrefix/$sourceRepo" + git clone --mirror "$sourceRepoPrefix/$sourceRepo.git" "./$repo/.git" + Set-Location ".\$sourceRepo" + + git fetch --all $targetRepo = $repo.Replace(" ", "-") Write-Host "Creating repo $targetRepo"