commands.ps1
<# .SYNOPSIS Retrieves and processes work items from a source Azure DevOps project. .DESCRIPTION This function retrieves work items from a source Azure DevOps project using a WIQL query, splits them into batches of 200, and processes them to extract detailed information. .PARAMETER SourceOrganization The name of the source Azure DevOps organization. .PARAMETER SourceProjectName The name of the source Azure DevOps project. .PARAMETER SourceToken The personal access token (PAT) for the source Azure DevOps organization. .PARAMETER Fields (Optional) The fields to retrieve for each work item. Default is a set of common fields including ID, Title, Description, WorkItemType, State, and Parent. .PARAMETER ApiVersion (Optional) The API version to use. Default is `7.1`. .EXAMPLE # Example: Retrieve and process work items from a source project Get-ADOSourceWorkItemsList -SourceOrganization "source-org" -SourceProjectName "source-project" -SourceToken "source-token" .NOTES This function is part of the ADO Tools module and adheres to the conventions used in the module for logging, error handling, and API interaction. Author: Oleksandr Nikolaiev (@onikolaiev) #> function Get-ADOSourceWorkItemsList { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$SourceOrganization, [Parameter(Mandatory = $true)] [string]$SourceProjectName, [Parameter(Mandatory = $true)] [string]$SourceToken, [Parameter(Mandatory = $false)] [Array]$Fields = @("System.Id", "System.Title", "System.Description", "System.WorkItemType", "System.State", "System.Parent"), [Parameter(Mandatory = $false)] [string]$ApiVersion = "7.1" ) begin { # Log the start of the operation Write-PSFMessage -Level Verbose -Message "Starting retrieval of work items from project '$SourceProjectName' in organization '$SourceOrganization'." Invoke-TimeSignal -Start } process { try { # Execute WIQL query to retrieve work items Write-PSFMessage -Level Verbose -Message "Executing WIQL query to retrieve work items from project '$SourceProjectName' in organization '$SourceOrganization'." $query = "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = '$SourceProjectName' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] asc" $result = Invoke-ADOWiqlQueryByWiql -Organization $SourceOrganization -Token $SourceToken -Project $SourceProjectName -Query $query -ApiVersion $ApiVersion # Log the number of work items retrieved Write-PSFMessage -Level Verbose -Message "Retrieved $($result.workItems.Count) work items from the WIQL query." # Split the work item IDs into batches of 200 Write-PSFMessage -Level Verbose -Message "Splitting work item IDs into batches of 200." $witListBatches = [System.Collections.ArrayList]::new() $batch = @() $result.workItems.id | ForEach-Object -Process { $batch += $_ if ($batch.Count -eq 200) { Write-PSFMessage -Level Verbose -Message "Adding a batch of 200 work item IDs." $null = $witListBatches.Add($batch) $batch = @() } } -End { if ($batch.Count -gt 0) { Write-PSFMessage -Level Verbose -Message "Adding the final batch of $($batch.Count) work item IDs." $null = $witListBatches.Add($batch) } } # Log the number of batches created Write-PSFMessage -Level Verbose -Message "Created $($witListBatches.Count) batches of work item IDs." $wiResult = @() # Process each batch foreach ($witBatch in $witListBatches) { if($witBatch.Count -eq 0) { continue } Write-PSFMessage -Level Verbose -Message "Processing a batch of $($witBatch.Count) work item IDs." $wiResult += Get-ADOWorkItemsBatch -Organization $SourceOrganization -Token $SourceToken -Project $SourceProjectName -Ids $witBatch -Fields $Fields -ApiVersion $ApiVersion } # Log the number of work items retrieved in detail Write-PSFMessage -Level Verbose -Message "Retrieved detailed information for $($wiResult.Count) work items." # Format work items into a list $sourceWorkItemsList = $wiResult.fields | ForEach-Object { [PSCustomObject]@{ "System.Id" = $_."System.Id" "System.WorkItemType" = $_."System.WorkItemType" "System.Description" = $_."System.Description" "System.State" = $_."System.State" "System.Title" = $_."System.Title" "Custom.SourceWorkitemId" = $_."Custom.SourceWorkitemId" "System.Parent" = if ($_.PSObject.Properties["System.Parent"] -and $_."System.Parent") { $_."System.Parent" } else { 0 } } } # Log the work items retrieved Write-PSFMessage -Level Verbose -Message "Formatted work items into a list. Total items: $($sourceWorkItemsList.Count)." #$sourceWorkItemsList | Format-Table -AutoSize # Return the formatted work items list return $sourceWorkItemsList } catch { # Log the error Write-PSFMessage -Level Error -Message "An error occurred: $($_.Exception.Message)" Stop-PSFFunction -Message "Stopping because of errors." } } end { # Log the end of the operation Write-PSFMessage -Level Verbose -Message "Completed retrieval of work items from project '$SourceProjectName'." Invoke-TimeSignal -End } } <# .SYNOPSIS Processes a source work item from Azure DevOps and creates or updates a corresponding work item in the target Azure DevOps project, maintaining parent-child relationships. .DESCRIPTION This function processes a source work item retrieved from Azure DevOps, builds the necessary JSON payload, and creates or updates a corresponding work item in the target Azure DevOps project. It also handles parent-child relationships by linking the work item to its parent if applicable. If the parent work item does not exist in the target project, it is created first. .PARAMETER SourceWorkItem The source work item object containing the fields to process. .PARAMETER SourceOrganization The name of the source Azure DevOps organization. .PARAMETER SourceProjectName The name of the source Azure DevOps project. .PARAMETER SourceToken The personal access token (PAT) for the source Azure DevOps organization. .PARAMETER TargetOrganization The name of the target Azure DevOps organization. .PARAMETER TargetProjectName The name of the target Azure DevOps project. .PARAMETER TargetToken The personal access token (PAT) for the target Azure DevOps organization. .PARAMETER TargetWorkItemList A hashtable containing mappings of source work item IDs to target work item URLs for parent-child relationships. Passed by reference. .PARAMETER ApiVersion (Optional) The API version to use. Default is `7.1`. .EXAMPLE # Example 1: Process a single work item and create it in the target project Invoke-ADOWorkItemsProcessing -SourceWorkItem $sourceWorkItem -SourceOrganization "source-org" ` -SourceProjectName "source-project" -SourceToken "source-token" ` -TargetOrganization "target-org" -TargetProjectName "target-project" ` -TargetToken "target-token" -TargetWorkItemList ([ref]$targetWorkItemList) .NOTES This function is part of the ADO Tools module and adheres to the conventions used in the module for logging, error handling, and API interaction. Author: Oleksandr Nikolaiev (@onikolaiev) #> function Invoke-ADOWorkItemsProcessing { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [PSCustomObject]$SourceWorkItem, [Parameter(Mandatory = $true)] [string]$SourceOrganization, [Parameter(Mandatory = $true)] [string]$SourceProjectName, [Parameter(Mandatory = $true)] [string]$SourceToken, [Parameter(Mandatory = $true)] [string]$TargetOrganization, [Parameter(Mandatory = $true)] [string]$TargetProjectName, [Parameter(Mandatory = $true)] [string]$TargetToken, [Parameter(Mandatory = $true)] [ref]$TargetWorkItemList, [Parameter(Mandatory = $false)] [string]$ApiVersion = $Script:ADOApiVersion ) begin { # Log the start of the operation Write-PSFMessage -Level Host -Message "Processing work item ID: $($SourceWorkItem.'System.Id'). Title: $($SourceWorkItem.'System.Title')." } process { try { # Build the JSON payload for the new work item $body = @( @{ op = "add" path = "/fields/System.Title" value = "$($SourceWorkItem."System.Title")" } @{ op = "add" path = "/fields/System.Description" value = "$($SourceWorkItem."System.Description")" } @{ op = "add" path = "/fields/Custom.SourceWorkitemId" value = "$($SourceWorkItem."System.ID")" } @{ op = "add" path = "/fields/System.State" value = "$($SourceWorkItem."System.State")" } ) # Handle parent-child relationships if ($SourceWorkItem."System.Parent") { if (-not $TargetWorkItemList.Value[$SourceWorkItem."System.Parent"]) { Write-PSFMessage -Level Verbose -Message "Parent work item ID $($SourceWorkItem.'System.Parent') not found in target work item list. Creating it..." $SourceWorkItemsList = (Get-ADOSourceWorkItemsList -SourceOrganization $sourceOrganization -SourceProjectName $SourceProjectName -SourceToken $SourceToken) $parentWorkItem = $SourceWorkItemsList | Where-Object { $_."System.Id" -eq $SourceWorkItem.'System.Parent' } # Create the parent work item first Invoke-ADOWorkItemsProcessing -SourceWorkItem $parentWorkItem -SourceOrganization $SourceOrganization -SourceProjectName $SourceProjectName -SourceToken $SourceToken -TargetOrganization $TargetOrganization ` -TargetProjectName $TargetProjectName -TargetToken $TargetToken ` -TargetWorkItemList ($TargetWorkItemList) -ApiVersion $ApiVersion } $body += @{ op = "add" path = "/relations/-" value = @{ rel = "System.LinkTypes.Hierarchy-Reverse" url = "$(($TargetWorkItemList).Value[$SourceWorkItem."System.Parent"])" attributes = @{ comment = "Making a new link for the dependency" } } } } # Convert the payload to JSON $body = $body | ConvertTo-Json -Depth 10 # Log the creation of the target work item Write-PSFMessage -Level Verbose -Message "Creating target work item for source work item ID: $($SourceWorkItem.'System.Id')." # Call the Add-ADOWorkItem function to create the work item $targetWorkItem = Add-ADOWorkItem -Organization $TargetOrganization ` -Token $TargetToken ` -Project $TargetProjectName ` -Type "`$$($SourceWorkItem."System.WorkItemType")" ` -Body $body ` -ApiVersion $ApiVersion if(-not $targetWorkItem.url) { # Add the target work item URL to the TargetWorkItemList Write-PSFMessage -Level Error -Message "Error: $($targetWorkItem.url) for source work item ID: $($SourceWorkItem.'System.Id')." } # Log the successful creation of the target work items list $TargetWorkItemList.Value[$SourceWorkItem.'System.Id'] = $targetWorkItem.url } catch { # Log the error Write-PSFMessage -Level Error -Message "Failed to process work item ID: $($SourceWorkItem.'System.Id'). Error: $($_)" } } end { # Log the end of the operation Write-PSFMessage -Level Host -Message "Completed processing of work item ID: $($SourceWorkItem.'System.Id')." } } <# .SYNOPSIS Handle time measurement .DESCRIPTION Handle time measurement from when a cmdlet / function starts and ends Will write the output to the verbose stream (Write-PSFMessage -Level Verbose) .PARAMETER Start Switch to instruct the cmdlet that a start time registration needs to take place .PARAMETER End Switch to instruct the cmdlet that a time registration has come to its end and it needs to do the calculation .EXAMPLE PS C:\> Invoke-TimeSignal -Start This will start the time measurement for any given cmdlet / function .EXAMPLE PS C:\> Invoke-TimeSignal -End This will end the time measurement for any given cmdlet / function. The output will go into the verbose stream. .NOTES This is refactored function from d365fo.tools Original Author: Mötz Jensen (@Splaxi) Author: Oleksandr Nikolaiev (@onikolaiev) #> function Invoke-TimeSignal { [CmdletBinding(DefaultParameterSetName = 'Start')] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Start', Position = 1 )] [switch] $Start, [Parameter(Mandatory = $True, ParameterSetName = 'End', Position = 2 )] [switch] $End ) $Time = (Get-Date) $Command = (Get-PSCallStack)[1].Command if ($Start) { if ($Script:TimeSignals.ContainsKey($Command)) { Write-PSFMessage -Level Verbose -Message "The command '$Command' was already taking part in time measurement. The entry has been update with current date and time." $Script:TimeSignals[$Command] = $Time } else { $Script:TimeSignals.Add($Command, $Time) } } else { if ($Script:TimeSignals.ContainsKey($Command)) { $TimeSpan = New-TimeSpan -End $Time -Start (($Script:TimeSignals)[$Command]) Write-PSFMessage -Level Verbose -Message "Total time spent inside the function was $TimeSpan" -Target $TimeSpan -FunctionName $Command -Tag "TimeSignal" $null = $Script:TimeSignals.Remove($Command) } else { Write-PSFMessage -Level Verbose -Message "The command '$Command' was never started to take part in time measurement." } } } <# .SYNOPSIS Performs a code review of a Microsoft Dynamics 365 Business Central AL codebase using Azure OpenAI. .DESCRIPTION This function indexes a codebase, searches for relevant files, and queries Azure OpenAI to perform a detailed code review. It generates a report based on the provided user query and context extracted from the codebase. .PARAMETER OpenAIEndpoint The Azure OpenAI endpoint URL. .PARAMETER OpenAIApiKey The API key for authenticating with Azure OpenAI. .PARAMETER CodebasePath The path to the codebase to be indexed and reviewed. .PARAMETER Prompt The prompt to be sent to Azure OpenAI for code review. .PARAMETER Files (Optional) A list of specific file to search for in the codebase. Provide a paths to the files. .PARAMETER ExcludedFolders (Optional) A list of folder names to exclude from indexing. .PARAMETER FileExtensions (Optional) A list of file extensions to include in the indexing process. .EXAMPLE # Define the required parameters $openaiEndpoint = "https://YourAzureOpenApiEndpoint" $openaiApiKey = "your-api-key" $codebasePath = "C:\Projects\MyCodebase" $prompt = "Analyze the code for bugs and improvements." $filenames = @("example1.al", "example2.al") # Call the function Invoke-ADOAzureOpenAI -OpenAIEndpoint $openaiEndpoint ` -OpenAIApiKey $openaiApiKey ` -CodebasePath $codebasePath ` -Prompt $prompt ` -Files $filenames .NOTES This function uses PSFramework for logging and exception handling. Author: Oleksandr Nikolaiev (@onikolaiev) #> function Invoke-ADOAICodeReview { param ( [Parameter(Mandatory = $true)] [string]$OpenAIEndpoint, [Parameter(Mandatory = $true)] [string]$OpenAIApiKey, [Parameter(Mandatory = $true)] [string]$CodebasePath, [Alias("UserQuery")] [string]$Prompt, [Parameter(Mandatory = $true)] [Alias("Filenames")] [array]$Files = @(), [array]$ExcludedFolders = @(".git", "node_modules", ".vscode"), [array]$FileExtensions = @(".al", ".json", ".xml", ".txt") ) begin{ $IndexPath = "c:\temp\codebase_index.json" $ErrorActionPreference = "Stop" # Validate parameters if (-not (Test-Path $CodebasePath)) { throw "The specified codebase path does not exist: $CodebasePath" } #Invoke-TimeSignal -Start } process{ if (Test-PSFFunctionInterrupt) { return } try { # Step 1: Index the codebase Write-PSFMessage -Level Host -Message "Indexing the codebase at path: $CodebasePath" $index = @() If(-not (Test-Path $IndexPath)) { $null = New-Item -Path $IndexPath -ItemType File -Force } function CheckPath { param ( $filePath ) (($ExcludedFolders | ForEach-Object { IF($filePath -match $_) { return $true } } )) return $false } Get-ChildItem -Path $codebasePath -Recurse -File | Where-Object { ($FileExtensions -contains $_.Extension) -and (-not (CheckPath $_.FullName)) } | ForEach-Object { $filePath = $_.FullName try { $content = Get-Content -Path $filePath -Raw # Extract only the content inside CDATA tags $cleanedContent = [regex]::Matches($content, "<!\[CDATA\[(.*?)\]\]>", [System.Text.RegularExpressions.RegexOptions]::Singleline) | ForEach-Object { $_.Groups[1].Value } # Combine all extracted CDATA content into a single string (if there are multiple CDATA sections) $cleanedContent = $cleanedContent -join "`n" # Add the cleaned content to the index if it's not empty if (-not [string]::IsNullOrWhiteSpace($cleanedContent)) { $index += @{ FilePath = "$filePath" Content = $cleanedContent } } } catch { Write-PSFMessage -Level Error -Message "Failed to process file: $filePath. Error: $_" } } $index | ConvertTo-Json -Depth 10 | Set-Content -Path $IndexPath Write-PSFMessage -Level Host -Message "Indexing completed. Output saved to: $IndexPath" # Step 2: Search the codebase Write-PSFMessage -Level Host -Message "Searching the codebase for relevant files." $index = Get-Content -Path $IndexPath | ConvertFrom-Json $context = @() foreach ($file in $index) { if ($file.Content -match [regex]::Escape("")) { $context += @{ FilePath = $file.FilePath Snippet = $file.Content -replace "(?s).{0,50}" + [regex]::Escape("") + ".{0,50}", "...$&..." } } } Write-PSFMessage -Level Host -Message "Filtering results based on provided filenames." $context = $context | Where-Object { $filePath = $_.FilePath $Files | ForEach-Object { $filePath -like "*$_" } | Where-Object { $_ } | Measure-Object | Select-Object -ExpandProperty Count } Write-PSFMessage -Level Host -Message "Search completed. Found $($context.Count) matching files." # Step 3: Query Azure OpenAI Write-PSFMessage -Level Host -Message "Sending request to Azure OpenAI for code review." $fullPrompt = "You are an assistant that helps with code suggestions. Here is the context:\n" foreach ($snippet in $context) { $fullPrompt += "File: $($snippet.FilePath)\nCode:\n$($snippet.Snippet)\n\n" } $fullPrompt += "User Prompt: $Prompt" $messages = @( @{ role = "system"; content = "You are a helpful assistant for code suggestions." }, @{ role = "user"; content = $fullPrompt } ) $response = Invoke-ADOAzureOpenAI -OpenAIEndpoint $openaiEndpoint -OpenAIApiKey $openaiApiKey -Messages $messages Write-PSFMessage -Level Verbose -Message "Azure OpenAI response received." # Output the response return $response.choices[0].message.content } catch { Write-PSFMessage -Level Error -Message "An error occurred: $($_.Exception.Message)" throw } } end{ # Log the end of the operation Write-PSFMessage -Level Host -Message "Request completed." Invoke-TimeSignal -End } } <# .SYNOPSIS Performs a code review of a Microsoft Dynamics 365 Business Central AL codebase using Azure OpenAI. .DESCRIPTION This function indexes a codebase, searches for relevant files, and queries Azure OpenAI to perform a detailed code review. It generates a report based on the provided user query and context extracted from the codebase. .PARAMETER OpenAIEndpoint The Azure OpenAI endpoint URL. .PARAMETER OpenAIApiKey The API key for authenticating with Azure OpenAI. .PARAMETER Messages The messages to be sent to Azure OpenAI. .EXAMPLE # Define the required parameters $openaiEndpoint = "https://YourAzureOpenApiEndpoint" $openaiApiKey = "your-api-key" $prompt = "Who are you?" $messages = @( @{ role = "system"; content = "You are a helpful assistant." }, @{ role = "user"; content = $prompt } ) # Call the function Invoke-ADOAzureOpenAI -OpenAIEndpoint $openaiEndpoint ` -OpenAIApiKey $openaiApiKey ` -CodebasePath $codebasePath ` -Messages $messages .OUTPUTS Returns a hashtable containing the response from Azure OpenAI. The hashtable includes the following keys: - id - object - created - model - usage - choices #Example response @" { "model": "o1-2024-12-17", "created": 1745923901, "object": "chat.completion", "id": "chatcmpl-BRcqr2gTdJH2EeL63R3jEM5ZVOpUD", "choices": [ { "content_filter_results": { "hate": { "filtered": false, "severity": "safe" }, "self_harm": { "filtered": false, "severity": "safe" }, "sexual": { "filtered": false, "severity": "safe" }, "violence": { "filtered": false, "severity": "safe" } }, "finish_reason": "stop", "index": 0, "logprobs": null, "message": { "content": "I’m ChatGPT, a large language model trained by OpenAI. I’m here to help you with your questions, provide information, and engage in conversation. How can I assist you today?", "refusal": null, "role": "assistant" } } ], "usage": { "completion_tokens": 178, "completion_tokens_details": { "accepted_prediction_tokens": 0, "audio_tokens": 0, "reasoning_tokens": 128, "rejected_prediction_tokens": 0 }, "prompt_tokens": 20, "prompt_tokens_details": { "audio_tokens": 0, "cached_tokens": 0 }, "total_tokens": 198 } } "@ .NOTES This function uses PSFramework for logging and exception handling. Author: Oleksandr Nikolaiev (@onikolaiev) #> function Invoke-ADOAzureOpenAI { param ( [Parameter(Mandatory = $true)] [string]$OpenAIEndpoint, [Parameter(Mandatory = $true)] [string]$OpenAIApiKey, [Array]$Messages ) begin{ $ErrorActionPreference = "Stop" Invoke-TimeSignal -Start Write-PSFMessage -Level Verbose -Message "Starting Azure OpenAI request." # Validate messages array if (-not $Messages -or $Messages.Count -eq 0) { throw "The Messages parameter cannot be null or empty." } $body = @{ messages = $Messages } | ConvertTo-Json -Depth 10 $headers = @{ "Content-Type" = "application/json" "api-key" = $OpenAIApiKey } } process{ if (Test-PSFFunctionInterrupt) { return } try { $response = Invoke-RestMethod -Uri $OpenAIEndpoint -Method Post -Headers $headers -Body $body Write-PSFMessage -Level Verbose -Message "Azure OpenAI response received." # Output the response return $response | Select-PSFObject * } catch { Write-PSFMessage -Level Error -Message "An error occurred: $($_.Exception.Message)" throw } } end{ # Log the end of the operation Write-PSFMessage -Level Verbose -Message "Request completed." Invoke-TimeSignal -End } } <# .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 } } |