internal/functions/invoke-adoteammigration.ps1


<#
    .SYNOPSIS
        Migrates teams from source Azure DevOps project to target Azure DevOps project.
         
    .DESCRIPTION
        This function migrates teams from a source Azure DevOps organization/project to a target
        organization/project. It creates teams in the target project if they don't exist,
        preserving team names and descriptions. Optionally migrates team members and settings.
         
    .PARAMETER SourceOrganization
        Source Azure DevOps organization name.
         
    .PARAMETER TargetOrganization
        Target Azure DevOps organization name.
         
    .PARAMETER SourceProjectName
        Source project name or ID.
         
    .PARAMETER TargetProjectName
        Target project name or ID.
         
    .PARAMETER SourceToken
        Source organization Personal Access Token (PAT) with vso.project scope.
         
    .PARAMETER TargetToken
        Target organization Personal Access Token (PAT) with vso.project_manage scope.
         
    .PARAMETER IncludeMembers
        Include team members in migration (requires additional permissions).
         
    .PARAMETER IncludeSettings
        Include team settings like default area path, iteration paths, etc.
         
    .PARAMETER ExcludeDefaultTeam
        Skip migration of the default project team.
         
    .PARAMETER ApiVersion
        API version to use (default: 7.2-preview.3).
         
    .EXAMPLE
        Invoke-ADOTeamMigration -SourceOrganization "contoso" -TargetOrganization "fabrikam" -SourceProjectName "WebApp" -TargetProjectName "WebApp-New" -SourceToken $sourcePat -TargetToken $targetPat
         
        Migrates all teams from source to target project (names and descriptions only).
         
    .EXAMPLE
        Invoke-ADOTeamMigration -SourceOrganization "contoso" -TargetOrganization "fabrikam" -SourceProjectName "WebApp" -TargetProjectName "WebApp-New" -SourceToken $sourcePat -TargetToken $targetPat -IncludeMembers -IncludeSettings
         
        Migrates teams including members and settings.
         
    .EXAMPLE
        Invoke-ADOTeamMigration -SourceOrganization "contoso" -TargetOrganization "fabrikam" -SourceProjectName "WebApp" -TargetProjectName "WebApp-New" -SourceToken $sourcePat -TargetToken $targetPat -ExcludeDefaultTeam
         
        Migrates teams but skips the default project team.
         
    .NOTES
        Author: Oleksandr Nikolaiev (@onikolaiev)
        Requires:
            - Source PAT: vso.project (read)
            - Target PAT: vso.project_manage (write)
            - For members migration: vso.memberentitlementmanagement
         
    .LINK
        https://learn.microsoft.com/azure/devops/organizations/security/about-permissions
#>

function Invoke-ADOTeamMigration {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory)][string]$SourceOrganization,
        [Parameter(Mandatory)][string]$TargetOrganization,
        [Parameter(Mandatory)][string]$SourceProjectName,
        [Parameter(Mandatory)][string]$TargetProjectName,
        [Parameter(Mandatory)][string]$SourceToken,
        [Parameter(Mandatory)][string]$TargetToken,
        [Parameter()][switch]$IncludeMembers,
        [Parameter()][switch]$IncludeSettings,
        [Parameter()][switch]$ExcludeDefaultTeam,
        [Parameter()][string]$ApiVersion = '7.2-preview.3'
    )

    begin {
        Write-PSFMessage -Level Host -Message "Starting team migration from '$SourceOrganization/$SourceProjectName' to '$TargetOrganization/$TargetProjectName'..."
        
        # Initialize counters
        $stats = @{
            Migrated = 0
            Skipped = 0
            Errors = 0
        }
    }

    process {
        try {
            # Get source teams
            Write-PSFMessage -Level Verbose -Message "Fetching source teams from '$SourceOrganization/$SourceProjectName'..."
            $sourceTeams = Get-ADOProjectTeamList -Organization $SourceOrganization -ProjectId $SourceProjectName -Token $SourceToken -ApiVersion $ApiVersion

            if (-not $sourceTeams -or $sourceTeams.Count -eq 0) {
                Write-PSFMessage -Level Warning -Message "No teams found in source project '$SourceProjectName'."
                return $stats
            }

            Write-PSFMessage -Level Host -Message "Found $($sourceTeams.Count) teams in source project."

            # Get target teams to avoid duplicates
            Write-PSFMessage -Level Verbose -Message "Fetching existing teams from target project..."
            $targetTeams = Get-ADOProjectTeamList -Organization $TargetOrganization -ProjectId $TargetProjectName -Token $TargetToken -ApiVersion $ApiVersion
            $targetTeamNames = @($targetTeams | ForEach-Object { $_.name })

            # Process each source team
            foreach ($sourceTeam in $sourceTeams) {
                $teamName = $sourceTeam.name

                try {
                    # Skip default team if requested
                    if ($ExcludeDefaultTeam -and $sourceTeam.isDefaultTeam) {
                        Write-PSFMessage -Level Verbose -Message "Skipping default team '$teamName' as requested."
                        $stats.Skipped++
                        continue
                    }

                    # Check if team already exists in target
                    if ($targetTeamNames -contains $teamName) {
                        Write-PSFMessage -Level Verbose -Message "Team '$teamName' already exists in target. Skipping creation."
                        $stats.Skipped++
                        continue
                    }

                    Write-PSFMessage -Level Host -Message "Migrating team '$teamName'..."

                    # Get detailed team info from source
                    $sourceTeamDetail = Get-ADOTeam -Organization $SourceOrganization -ProjectId $SourceProjectName -TeamId $sourceTeam.id -Token $SourceToken -ApiVersion $ApiVersion

                    # Create team in target
                    $teamParams = @{
                        Organization = $TargetOrganization
                        ProjectId = $TargetProjectName
                        Token = $TargetToken
                        Name = $teamName
                        ApiVersion = $ApiVersion
                    }

                    if ($sourceTeamDetail.description) {
                        $teamParams.Description = $sourceTeamDetail.description
                    }

                    $newTeam = Add-ADOTeam @teamParams

                    if ($newTeam) {
                        Write-PSFMessage -Level Verbose -Message "Successfully created team '$teamName' with ID '$($newTeam.id)'."
                        $stats.Migrated++

                        # Migrate members if requested
                        if ($IncludeMembers) {
                            try {
                                $memberResult = Invoke-ADOTeamMemberMigration -SourceOrganization $SourceOrganization -TargetOrganization $TargetOrganization -SourceProjectName $SourceProjectName -TargetProjectName $TargetProjectName -SourceToken $SourceToken -TargetToken $TargetToken -SourceTeamId $sourceTeam.id -TargetTeamId $newTeam.id -ApiVersion $ApiVersion
                                Write-PSFMessage -Level Verbose -Message "Team '$teamName' members migration: $($memberResult.Migrated) migrated, $($memberResult.Errors) errors."
                            } catch {
                                Write-PSFMessage -Level Warning -Message "Failed to migrate members for team '$teamName': $($_.Exception.Message)"
                            }
                        }

                        # Migrate settings if requested
                        if ($IncludeSettings) {
                            try {
                                Invoke-ADOTeamSettingsMigration -SourceOrganization $SourceOrganization -TargetOrganization $TargetOrganization -SourceProjectName $SourceProjectName -TargetProjectName $TargetProjectName -SourceToken $SourceToken -TargetToken $TargetToken -SourceTeamId $sourceTeam.id -TargetTeamId $newTeam.id -ApiVersion $ApiVersion
                                Write-PSFMessage -Level Verbose -Message "Team '$teamName' settings migration completed."
                            } catch {
                                Write-PSFMessage -Level Warning -Message "Failed to migrate settings for team '$teamName': $($_.Exception.Message)"
                            }
                        }
                    }
                    else {
                        Write-PSFMessage -Level Warning -Message "Failed to create team '$teamName' - no team object returned."
                        $stats.Errors++
                    }
                }
                catch {
                    Write-PSFMessage -Level Warning -Message "Failed to migrate team '$teamName': $($_.Exception.Message)"
                    $stats.Errors++
                }
            }
        }
        catch {
            Write-PSFMessage -Level Error -Message "Critical error during team migration: $($_.Exception.Message)"
            throw
        }
    }

    end {
        Write-PSFMessage -Level Host -Message "Team migration completed. Migrated: $($stats.Migrated), Skipped: $($stats.Skipped), Errors: $($stats.Errors)"
        return $stats
    }
}