functions/invoke-adoprojectmigration.ps1
<# .SYNOPSIS Migrates a project from a source Azure DevOps organization to a target Azure DevOps organization. .DESCRIPTION This function facilitates the migration of a project from one Azure DevOps organization to another. It retrieves the source project details, validates its existence, and prepares for migration to the target organization. .PARAMETER SourceOrganization The name of the source Azure DevOps organization. .PARAMETER TargetOrganization The name of the target Azure DevOps organization. .PARAMETER SourceProjectName The name of the project in the source organization to be migrated. .PARAMETER TargetProjectName The name of the project in the target organization where the source project will be migrated. .PARAMETER SourceOrganizationToken The authentication token for accessing the source Azure DevOps organization. .PARAMETER TargetOrganizationToken The authentication token for accessing the target Azure DevOps organization. .PARAMETER ApiVersion The version of the Azure DevOps REST API to use. Default is "7.1". .EXAMPLE $sourceOrg = "sourceOrg" $targetOrg = "targetOrg" $sourceProjectName = "sourceProject" $targetProjectName = "targetProject" $sourceOrgToken = "sourceOrgToken" $targetOrgToken = "targetOrgToken" Invoke-ADOProjectMigration -SourceOrganization $sourceOrg ` -TargetOrganization $targetOrg ` -SourceProjectName $sourceProjectName ` -TargetProjectName $targetProjectName ` -SourceOrganizationToken $sourceOrgToken ` -TargetOrganizationToken $targetOrgToken This example migrates the project "sourceProject" from the organization "sourceOrg" to the organization "targetOrg". .NOTES This function uses PSFramework for logging and exception handling. Author: Oleksandr Nikolaiev (@onikolaiev) #> function Invoke-ADOProjectMigration { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$SourceOrganization, [Parameter(Mandatory = $true)] [string]$TargetOrganization, [Parameter(Mandatory = $true)] [string]$SourceProjectName, [Parameter(Mandatory = $true)] [string]$TargetProjectName, [Parameter(Mandatory = $true)] [string]$SourceOrganizationToken, [Parameter(Mandatory = $true)] [string]$TargetOrganizationToken, [Parameter(Mandatory = $false)] [string]$ApiVersion = $Script:ADOApiVersion ) begin{ Invoke-TimeSignal -Start $ErrorActionPreference = "Stop" } process{ if (Test-PSFFunctionInterrupt) { return } # Log start of migration Write-PSFMessage -Level Host -Message "Starting migration from source organization '$sourceOrganization' to target organization '$targetOrganization'." Convert-FSCPSTextToAscii -Text "Start migration" -Font "Standard" ## GETTING THE SOURCE PROJECT INFORMATION Write-PSFMessage -Level Host -Message "Fetching source project '$sourceProjectName' from organization '$sourceOrganization'." $sourceProjecttmp = (Get-ADOProjectList -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion -StateFilter All).Where({$_.name -eq $sourceProjectName}) if (-not $sourceProjecttmp) { Write-PSFMessage -Level Error -Message "Source project '$sourceProjectName' not found in organization '$sourceOrganization'. Exiting." return } Write-PSFMessage -Level Host -Message "Source project '$sourceProjectName' found. Fetching detailed information." $sourceProject = Get-ADOProject -Organization $sourceOrganization -Token $sourceOrganizationtoken -ProjectId "$($sourceProjecttmp.id)" -IncludeCapabilities -ApiVersion $ApiVersion $sourceProjectVersionControl = $sourceProject.capabilities.versioncontrol $sourceProjectProcess = Get-ADOProcess -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion -ProcessTypeId "$($sourceProject.capabilities.processTemplate.templateTypeId)" $sourceProjectProcessParentProcess = Get-ADOProcess -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion -ProcessTypeId "$($sourceProjectProcess.parentProcessTypeId)" Write-PSFMessage -Level Host -Message "Source project process: '$($sourceProjectProcess.name)' (ID: $($sourceProjectProcess.typeId))." Convert-FSCPSTextToAscii -Text "Migrate processes.." -Font "Standard" ### PROCESSING PROCESS Write-PSFMessage -Level Host -Message "Checking if target process '$($sourceProjectProcess.name)' exists in target organization '$targetOrganization'." $targetProjectProcess = (Get-ADOProcessList -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion).Where({$_.name -eq $sourceProjectProcess.name}) ## Check if the target process already exists. If not, create it. if (-not $targetProjectProcess) { Write-PSFMessage -Level Host -Message "Target process '$($sourceProjectProcess.name)' does not exist. Creating it in target organization '$targetOrganization'." $body = @{ name = $sourceProjectProcess.name parentProcessTypeId = $sourceProjectProcessParentProcess.typeId description = $sourceProjectProcess.description customizationType = $sourceProjectProcess.customizationType isEnabled = "true" } $body = $body | ConvertTo-Json -Depth 10 Write-PSFMessage -Level Verbose -Message "Adding process '$($sourceProjectProcess.name)' to target organization '$($targetOrganization)' with the following details: $($body)" $targetProjectProcess = Add-ADOProcess -Organization $targetOrganization -Token $targetOrganizationtoken -Body $body -ApiVersion $ApiVersion } else { Write-PSFMessage -Level Host -Message "Target process '$($sourceProjectProcess.name)' already exists in target organization '$targetOrganization'." } Convert-FSCPSTextToAscii -Text "Migrate wit fields.." -Font "Standard" ## PROCESSING WIT FIELDS Write-PSFMessage -Level Host -Message "Fetching custom work item fields from source organization '$sourceOrganization'." $sourceWitFields = (Get-ADOWitFieldList -Organization $sourceOrganization -Token $sourceOrganizationtoken -Expand "extensionFields" -ApiVersion $ApiVersion ).Where({$_.referenceName.startswith("Custom.")}) Write-PSFMessage -Level Host -Message "Found $($sourceWitFields.Count) custom fields in source organization." Write-PSFMessage -Level Host -Message "Fetching custom work item fields from target organization '$targetOrganization'." $targetWitFields = (Get-ADOWitFieldList -Organization $targetOrganization -Token $targetOrganizationtoken -Expand "extensionFields" -ApiVersion $ApiVersion).Where({$_.referenceName.startswith("Custom.")}) Write-PSFMessage -Level Host -Message "Found $($targetWitFields.Count) custom fields in target organization." $sourceWitFields | ForEach-Object { $witField = $_ $targetWitField = $targetWitFields.Where({$_.name -eq $witField.name}) if (-not $targetWitField) { Write-PSFMessage -Level Host -Message "Custom field '$($witField.name)' does not exist in target organization. Adding it." $sourceWitField = Get-ADOWitField -Organization $sourceOrganization -Token $sourceOrganizationtoken -FieldNameOrRefName "$($witField.referenceName)" -ApiVersion $ApiVersion $body = @{ name = $sourceWitField.name referenceName = $sourceWitField.referenceName description = $sourceWitField.description type = $sourceWitField.type usage = $sourceWitField.usage readOnly = $sourceWitField.readOnly isPicklist = $sourceWitField.isPicklist isPicklistSuggested = $sourceWitField.isPicklistSuggested isIdentity = $sourceWitField.isIdentity isQueryable = $sourceWitField.isQueryable isLocked = $sourceWitField.isLocked canSortBy = $sourceWitField.canSortBy supportedOperations = $sourceWitField.supportedOperations } $body = $body | ConvertTo-Json -Depth 10 Write-PSFMessage -Level Verbose -Message "Adding custom field '$($sourceWitField.name)' to target process '$($targetProjectProcess.name)' with the following details: $($body)" $targetWitField = Add-ADOWitField -Organization $targetOrganization -Token $targetOrganizationtoken -Body $body -ApiVersion $ApiVersion } else { Write-PSFMessage -Level Host -Message "Custom field '$($witField.name)' already exists in target organization. Skipping." } } ### Creating SourceWorkitemId field for the target organization $sourceWorkitemIdFieldName = "SourceWorkitemId" $sourceWorkitemIdReferenceName = "Custom.$sourceWorkitemIdFieldName" $body = @{ name = $sourceWorkitemIdFieldName referenceName = "$sourceWorkitemIdReferenceName" description = "" type = "string" usage = "workItem" readOnly = $false isQueryable = $true isLocked = $false canSortBy = $true } $body = $body | ConvertTo-Json -Depth 10 $null = Add-ADOWitField -Organization $targetOrganization -Token $targetOrganizationtoken -Body $body -ApiVersion $ApiVersion -ErrorAction SilentlyContinue -WarningAction SilentlyContinue Convert-FSCPSTextToAscii -Text "Migrate work item types.." -Font "Standard" ## PROCESSING WORK ITEM TYPES Write-PSFMessage -Level Host -Message "Fetching custom work item types from source process '$($sourceProjectProcess.name)'." $sourceWitList = (Get-ADOWorkItemTypeList -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($sourceProjectProcess.typeId)" -Expand layout).Where({$_.customization -eq 'inherited'}) Write-PSFMessage -Level Host -Message "Found $($sourceWitList.Count) custom work item types in source process." Write-PSFMessage -Level Host -Message "Fetching custom work item types from target process '$($targetProjectProcess.name)'." $targetWitList = (Get-ADOWorkItemTypeList -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -Expand layout).Where({$_.customization -eq 'inherited'}) Write-PSFMessage -Level Host -Message "Found $($targetWitList.Count) custom work item types in target process." $sourceWitList | ForEach-Object { $wit = $_ $targetWit = $targetWitList.Where({$_.name -eq $wit.name}) if (-not $targetWit) { Write-PSFMessage -Level Host -Message "Work item type '$($wit.name)' does not exist in target process. Adding it." $sourceWit = Get-ADOWorkItemType -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($sourceProjectProcess.typeId)" -WitRefName "$($wit.referenceName)" $body = @{ name = $sourceWit.name description = $sourceWit.description color = $sourceWit.color icon = $sourceWit.icon isDisabled = $sourceWit.isDisabled inheritsFrom = $sourceWit.inherits } $body = $body | ConvertTo-Json -Depth 10 Write-PSFMessage -Level Verbose -Message "Adding work item type '$($sourceWit.name)' to target process '$($targetProjectProcess.name)' with the following details: $($body)" $targetWit = Add-ADOWorkItemType -Organization $targetOrganization -Token $targetOrganizationtoken -ProcessId "$($targetProjectProcess.typeId)" -Body $body } else { Write-PSFMessage -Level Host -Message "Work item type '$($wit.name)' already exists in target process. Skipping." } } $targetWitList = (Get-ADOWorkItemTypeList -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -Expand layout).Where({$_.customization -eq 'inherited'}) Convert-FSCPSTextToAscii -Text "Migrate fields.." -Font "Standard" ## Process Fields Write-PSFMessage -Level Host -Message "Starting to process custom fields for work item types." $sourceWitList | ForEach-Object { $wit = $_ Write-PSFMessage -Level Host -Message "Processing fields for work item type '$($wit.name)'." $targetWit = $targetWitList.Where({$_.name -eq $wit.name}) $customFields = (Get-ADOWorkItemTypeFieldList -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($sourceProjectProcess.typeId)" -WitRefName $wit.referenceName).Where({$_.customization -ne "system"}) $customFields | ForEach-Object { $field = $_ Write-PSFMessage -Level Host -Message "Checking field '$($field.name)' in target process." $targetField = (Get-ADOWorkItemTypeFieldList -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -WitRefName $targetWit.referenceName).Where({$_.name -eq $field.name}) if (-not $targetField) { Write-PSFMessage -Level Host -Message "Field '$($field.name)' does not exist in target process. Adding it." $sourceField = Get-ADOWorkItemTypeField -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($sourceProjectProcess.typeId)" -WitRefName "$($wit.referenceName)" -FieldRefName "$($field.referenceName)" -Expand all $body = @{ allowGroups = $sourceField.allowGroups allowedValues = $sourceField.allowedValues description = $sourceField.description defaultValue = $sourceField.defaultValue readOnly = $sourceField.readOnly referenceName = $sourceField.referenceName required = $sourceField.required } $body = $body | ConvertTo-Json -Depth 10 Write-PSFMessage -Level Verbose -Message "Adding field '$($sourceField.name)' to target process '$($targetProjectProcess.name)' with the following details: $($body)" $targetField = Add-ADOWorkItemTypeField -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -WitRefName $targetWit.referenceName -Body $body } else { Write-PSFMessage -Level Host -Message "Field '$($field.name)' already exists in target process. Skipping." } } } Convert-FSCPSTextToAscii -Text "Migrate behaviors.." -Font "Standard" ## Process Behaviors Write-PSFMessage -Level Host -Message "Starting to process behaviors for work item types." $sourceWitList | ForEach-Object { $wit = $_ Write-PSFMessage -Level Host -Message "Processing behaviors for work item type '$($wit.name)'." #$targetWit = $targetWitList.Where({$_.name -eq $wit.name}) $sourceBehaviors = (Get-ADOProcessBehaviorList -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($sourceProjectProcess.typeId)" -Expand "fields") $targetBehaviors = (Get-ADOProcessBehaviorList -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -Expand "fields") $sourceBehaviors | ForEach-Object { $behavior = $_ Write-PSFMessage -Level Host -Message "Checking behavior '$($behavior.name)' in target process." $targetBehavior = $targetBehaviors.Where({$_.name -eq $behavior.name}) if (-not $targetBehavior) { Write-PSFMessage -Level Verbose -Message "Behavior '$($behavior.name)' does not exist in target process. Adding it." $sourceBehavior = Get-ADOProcessBehavior -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($sourceProjectProcess.typeId)" -BehaviorRefName "$($behavior.referenceName)" -Expand "fields" $body = @{ color = $sourceBehavior.color inherits = $sourceBehavior.inherits name = $sourceBehavior.name referenceName = $sourceBehavior.referenceName } $body = $body | ConvertTo-Json -Depth 10 Write-PSFMessage -Level Host -Message "Adding behavior '$($sourceBehavior.name)' to target process '$($targetProjectProcess.name)' with the following details: $($body)" $targetBehavior = Add-ADOProcessBehavior -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -Body $body } else { Write-PSFMessage -Level Host -Message "Behavior '$($behavior.name)' already exists in target process. Skipping." } } } Convert-FSCPSTextToAscii -Text "Migrate picklists.." -Font "Standard" ## Process Picklists Write-PSFMessage -Level Host -Message "Starting to process picklists." $sourcePicklists = (Get-ADOPickListList -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion) $targetPicklists = (Get-ADOPickListList -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion) $sourcePicklists | ForEach-Object { $picklist = $_ Write-PSFMessage -Level Host -Message "Checking picklist '$($picklist.name)' in target process." $targetPicklist = $targetPicklists.Where({$_.name -eq $picklist.name}) if (-not $targetPicklist) { Write-PSFMessage -Level Verbose -Message "Picklist '$($picklist.name)' does not exist in target process. Adding it." $sourcePicklist = Get-ADOPickList -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion -ListId "$($picklist.id)" $body = @{ name = $sourcePicklist.name type = $sourcePicklist.type isSuggested = $sourcePicklist.isSuggested items = $sourcePicklist.items } $body = $body | ConvertTo-Json -Depth 10 Write-PSFMessage -Level Host -Message "Adding picklist '$($sourcePicklist.name)' to target process '$($targetProjectProcess.name)' with the following details: $($body)" $targetPicklist = Add-ADOPickList -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -Body $body } else { Write-PSFMessage -Level Host -Message "Picklist '$($picklist.name)' already exists in target process. Skipping." } } Convert-FSCPSTextToAscii -Text "Migrate states.." -Font "Standard" ## Process States Write-PSFMessage -Level Host -Message "Starting to process states for work item types." $sourceWitList | ForEach-Object { $wit = $_ Write-PSFMessage -Level Host -Message "Processing states for work item type '$($wit.name)'." $targetWit = $targetWitList.Where({$_.name -eq $wit.name}) $sourceStates = (Get-ADOWorkItemTypeStateList -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($sourceProjectProcess.typeId)" -WitRefName "$($wit.referenceName)") $targetStates = (Get-ADOWorkItemTypeStateList -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -WitRefName "$($targetWit.referenceName)") $sourceStates | ForEach-Object { $state = $_ Write-PSFMessage -Level Host -Message "Checking state '$($state.name)' in target process." $targetState = $targetStates.Where({$_.name -eq $state.name}) $sourceState = Get-ADOWorkItemTypeState -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($sourceProjectProcess.typeId)" -WitRefName "$($wit.referenceName)" -StateId "$($state.id)" if (-not $targetState) { Write-PSFMessage -Level Host -Message "State '$($state.name)' does not exist in target process. Adding it." $body = @{ name = $sourceState.name color = $sourceState.color stateCategory = $sourceState.stateCategory order = $sourceState.order } $body = $body | ConvertTo-Json -Depth 10 Write-PSFMessage -Level Verbose -Message "Adding state '$($sourceState.name)' to target process '$($targetProjectProcess.name)' with the following details: $($body)" $targetState = Add-ADOWorkItemTypeState -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -WitRefName "$($targetWit.referenceName)" -Body $body } else { Write-PSFMessage -Level Host -Message "State '$($state.name)' already exists in target process. Skipping." } if ($sourceState.hidden) { try { Write-PSFMessage -Level Verbose -Message "Hiding state '$($sourceState.name)' in target process." $targetState = Hide-ADOWorkItemTypeState -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -WitRefName "$($targetWit.referenceName)" -StateId "$($targetState.id)" -Hidden "true" -WarningAction SilentlyContinue -ErrorAction SilentlyContinue } catch { Write-PSFMessage -Level Warning -Message "Failed to hide state '$($sourceState.name)' in target process. The state is already hidden" } } } } Convert-FSCPSTextToAscii -Text "Migrate rules.." -Font "Standard" ## Process Rules Write-PSFMessage -Level Host -Message "Starting to process rules for work item types." $sourceWitList | ForEach-Object { $wit = $_ Write-PSFMessage -Level Host -Message "Processing rules for work item type '$($wit.name)'." $targetWit = $targetWitList.Where({$_.name -eq $wit.name}) $sourceRules = (Get-ADOWorkItemTypeRuleList -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($sourceProjectProcess.typeId)" -WitRefName "$($wit.referenceName)").Where({$_.customizationType -ne 'system'}) $targetRules = (Get-ADOWorkItemTypeRuleList -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -WitRefName "$($targetWit.referenceName)").Where({$_.customizationType -ne 'system'}) $sourceRules | ForEach-Object { $rule = $_ Write-PSFMessage -Level Host -Message "Checking rule '$($rule.name)' in target process." $targetRule = $targetRules.Where({$_.name -eq $rule.name}) if (-not $targetRule) { Write-PSFMessage -Level Host -Message "Rule '$($rule.name)' does not exist in target process. Adding it." $sourceRule = Get-ADOWorkItemTypeRule -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($sourceProjectProcess.typeId)" -WitRefName "$($wit.referenceName)" -RuleRefName "$($rule.referenceName)" $body = @{ name = $sourceRule.name conditions = $sourceRule.conditions actions = $sourceRule.actions isDisabled = $sourceRule.isDisabled } $body = $body | ConvertTo-Json -Depth 10 Write-PSFMessage -Level Host -Message "Adding rule '$($sourceRule.name)' to target process '$($targetProjectProcess.name)' with the following details: $($body)" $targetRule = Add-ADOWorkItemTypeRule -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -WitRefName "$($targetWit.referenceName)" -Body $body } else { Write-PSFMessage -Level Host -Message "Rule '$($rule.name)' already exists in target process. Skipping." } } } Convert-FSCPSTextToAscii -Text "Migrate layouts.." -Font "Standard" ## Process Layouts Write-PSFMessage -Level Host -Message "Starting to process layouts for work item types." $sourceWitList | ForEach-Object { $wit = $_ Write-PSFMessage -Level Host -Message "Processing layouts for work item type '$($wit.name)'." $targetWit = $targetWitList.Where({$_.name -eq $wit.name}) $sourceLayout = (Get-ADOWorkItemTypeLayout -Organization $sourceOrganization -Token $sourceOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($sourceProjectProcess.typeId)" -WitRefName "$($wit.referenceName)") $targetLayout = (Get-ADOWorkItemTypeLayout -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -WitRefName "$($targetWit.referenceName)") $sourceLayout.pages | Where-Object pageType -EQ "custom" | ForEach-Object { $sourcePage = $_ Write-PSFMessage -Level Host -Message "Processing page '$($sourcePage.label)' for work item type '$($wit.name)'." $targetPage = $targetLayout.pages.Where({$_.label -eq $sourcePage.label}) if (-not $targetPage) { Write-PSFMessage -Level Host -Message "Page '$($sourcePage.label)' does not exist in target process. Adding it." $body = @{ id = $sourcePage.id label = $sourcePage.label pageType = $sourcePage.pageType visible = $sourcePage.visible } $body = $body | ConvertTo-Json -Depth 10 Write-PSFMessage -Level Verbose -Message "Adding page '$($sourcePage.label)' to target process '$($targetProjectProcess.name)' with the following details: $($body)" $targetPage = Add-ADOWorkItemTypePage -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -WitRefName "$($targetWit.referenceName)" -Body $body } else { Write-PSFMessage -Level Host -Message "Page '$($sourcePage.label)' already exists in target process. Updating it." $body = @{ id = $targetPage.id label = $sourcePage.label pageType = $sourcePage.pageType visible = $sourcePage.visible } $body = $body | ConvertTo-Json -Depth 10 Write-PSFMessage -Level Verbose -Message "Updating page '$($sourcePage.label)' in target process '$($targetProjectProcess.name)' with the following details: $($body)" $null = Update-ADOWorkItemTypePage -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -WitRefName "$($targetWit.referenceName)" -Body $body } # Process Sections $sourcePageSections = $sourcePage.sections $targetPageSections = $targetPage.sections $sourcePageSections | Where-Object groups -NE $NULL | ForEach-Object { $sourceSection = $_ Write-PSFMessage -Level Host -Message "Processing section ''$(if($sourceSection.label){$sourceSection.label}else{$sourceSection.id})' on page '$($sourcePage.label)'." $targetSection = $targetPageSections.Where({$_.id -eq $sourceSection.id}) if (-not $targetSection) { Write-PSFMessage -Level Host -Message "Section '$(if($sourceSection.label){$sourceSection.label}else{$sourceSection.id})' does not exist in target process. Adding it." $body = @{ id = $sourceSection.id label = $sourceSection.label visible = $sourceSection.visible } $body = $body | ConvertTo-Json -Depth 10 Write-PSFMessage -Level Verbose -Message "Adding section '$(if($sourceSection.label){$sourceSection.label}else{$sourceSection.id})' to page '$($targetPage.label)' in target process '$($targetProjectProcess.name)' with the following details: $($body)" $targetSection = Add-ADOWorkItemTypeSection -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -WitRefName "$($targetWit.referenceName)" -PageId "$($targetPage.id)" -Body $body } else { Write-PSFMessage -Level Host -Message "Section '$(if($sourceSection.label){$sourceSection.label}else{$sourceSection.id})' already exists in target process. Skipping." } # Process Groups $sourceSection.groups | ForEach-Object { $sourceGroup = $_ Write-PSFMessage -Level Host -Message "Processing group '$($sourceGroup.label)' in section '$($sourceSection.label)'." $targetGroup = $targetSection.groups.Where({$_.label -eq $sourceGroup.label}) if (-not $targetGroup) { Write-PSFMessage -Level Host -Message "Group '$($sourceGroup.label)' does not exist in target process. Adding it." $body = @{ id = $sourceGroup.id label = $sourceGroup.label visible = $sourceGroup.visible controls = $sourceGroup.controls } $body = $body | ConvertTo-Json -Depth 10 Write-PSFMessage -Level Verbose -Message "Adding group '$($sourceGroup.label)' to section '$($sourceSection.label)' on page '$($targetPage.label)' in target process '$($targetProjectProcess.name)' with the following details: $($body)" $targetGroup = Add-ADOWorkItemTypeGroup -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -WitRefName "$($targetWit.referenceName)" -PageId "$($targetPage.id)" -SectionId "$($sourceSection.id)" -Body $body } else { Write-PSFMessage -Level Host -Message "Group '$($sourceGroup.label)' already exists in target process. Skipping." } # Process Controls $sourceGroup.controls | ForEach-Object { $sourceControl = $_ Write-PSFMessage -Level Host -Message "Processing control '$(if($sourceControl.label){$sourceControl.label}else{$sourceControl.id})' in group '$($sourceGroup.label)'." $targetControl = $targetGroup.controls.Where({$_.id -eq $sourceControl.id}) if (-not $targetControl) { Write-PSFMessage -Level Host -Message "Control '$(if($sourceControl.label){$sourceControl.label}else{$sourceControl.id})' does not exist in target process. Adding it." $body = @{ id = $sourceControl.id label = $sourceControl.label controlType = $sourceControl.controlType contribution = $sourceControl.contribution visible = $sourceControl.visible height = $sourceControl.height readOnly = $sourceControl.readOnly } $body = $body | ConvertTo-Json -Depth 10 Write-PSFMessage -Level Verbose -Message "Adding control '$(if($sourceControl.label){$sourceControl.label}else{$sourceControl.id})' to group '$($sourceGroup.label)' in target process '$($targetProjectProcess.name)' with the following details: $($body)" $targetControl = Add-ADOWorkItemTypeGroupControl -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $ApiVersion -ProcessId "$($targetProjectProcess.typeId)" -WitRefName "$($targetWit.referenceName)" -GroupId "$($targetGroup.id)" -Body $body } else { Write-PSFMessage -Level Host -Message "Control '$(if($sourceControl.label){$sourceControl.label}else{$sourceControl.id})' already exists in target process. Skipping." } } } } } } Convert-FSCPSTextToAscii -Text "Migrate project.." -Font "Standard" ### PROCESSING PROJECT Write-PSFMessage -Level Host -Message "Fetching source project '$($sourceProjecttmp.name)' from organization '$($sourceOrganization)'." $sourceProject = Get-ADOProject -Organization $sourceOrganization -Token $sourceOrganizationtoken -ProjectId "$($sourceProjecttmp.id)" -IncludeCapabilities -ApiVersion $ApiVersion Write-PSFMessage -Level Host -Message "Checking if target project '$($sourceProjectName)' exists in organization '$($targetOrganization)'." $targetProject = (Get-ADOProjectList -Organization $targetOrganization -Token $targetOrganizationtoken -ApiVersion $apiVersion -StateFilter All).Where({$_.name -eq $sourceProjectName}) if (-not $targetProject) { Write-PSFMessage -Level Verbose -Message "Target project '$($targetProjectName)' does not exist in organization '$($targetOrganization)'. Creating a new project." $body = @{ name = $targetProjectName description = $sourceProject.description visibility = $sourceProject.visibility capabilities = @{ versioncontrol = @{ sourceControlType = $sourceProjectVersionControl.sourceControlType } processTemplate = @{ templateTypeId = $targetProjectProcess.typeId } } } $body = $body | ConvertTo-Json -Depth 10 Write-PSFMessage -Level Host -Message "Adding project '$($targetProjectName)' to target organization '$($targetOrganization)' with the following details: $($body)" $targetProject = Add-ADOProject -Organization $targetOrganization -Token $targetOrganizationtoken -Body $body -ApiVersion $ApiVersion Write-PSFMessage -Level Host -Message "Project '$($targetProjectName)' successfully created in target organization '$($targetOrganization)'." } else { Write-PSFMessage -Level Host -Message "Target project '$($targetProjectName)' already exists in organization '$($targetOrganization)'. Updating the project." $body = @{ name = $targetProjectName description = $sourceProject.description visibility = $sourceProject.visibility capabilities = @{ versioncontrol = @{ sourceControlType = $sourceProjectVersionControl.sourceControlType } processTemplate = @{ templateTypeId = $targetProjectProcess.typeId } } } $body = $body | ConvertTo-Json -Depth 10 Write-PSFMessage -Level Host -Message "Updating project '$($targetProjectName)' in target organization '$($targetOrganization)' with the following details: $($body)" $targetProject = Update-ADOProject -Organization $targetOrganization -Token $targetOrganizationtoken -Body $body -ProjectId "$($targetProject.id)" -ApiVersion $ApiVersion Write-PSFMessage -Level Host -Message "Project '$($targetProjectName)' successfully updated in target organization '$($targetOrganization)'." } #PROCESSING WORK ITEM $sourceWorkItemsList = (Get-ADOSourceWorkItemsList -SourceOrganization $sourceOrganization -SourceProjectName $sourceProjectName -SourceToken $sourceOrganizationtoken -ApiVersion $ApiVersion) $targetWorkItemList = @{} $sourceWorkItemsList | ForEach-Object { $sourceWorkItem = $_ $targetWorkItemsList = (Get-ADOSourceWorkItemsList -SourceOrganization $targetOrganization -SourceProjectName $targetProjectName -SourceToken $targetOrganizationtoken -Fields @("System.Id", "System.Title", "System.Description", "System.WorkItemType", "System.State", "System.Parent", "Custom.SourceWorkitemId")) $workItemExists = $targetWorkItemsList | Where-Object 'Custom.SourceWorkitemId' -EQ $sourceWorkItem.'System.Id' if ($workItemExists) { continue; } Invoke-ADOWorkItemsProcessing -SourceWorkItem $sourceWorkItem -SourceOrganization $sourceOrganization -SourceProjectName $sourceProjectName -SourceToken $sourceOrganizationtoken -TargetOrganization $targetOrganization ` -TargetProjectName $targetProjectName -TargetToken $targetOrganizationtoken ` -TargetWorkItemList ([ref]$targetWorkItemList) -ApiVersion $ApiVersion } # Log the completion of the migration process Write-PSFMessage -Level Host -Message "Completed migration of work items from project '$sourceProjectName' in organization '$sourceOrganization' to project '$targetProjectName' in organization '$targetOrganization'." } end{ # Log the end of the operation Write-PSFMessage -Level Host -Message "Migration from source organization '$sourceOrganization' to target organization '$targetOrganization' completed successfully." Invoke-TimeSignal -End } } |