Team/Team.ps1

<#
 
.SYNOPSIS
    Gets information about one or more teams.
 
.PARAMETER Project
    Specifies either the name of the Team Project or a previously initialized Microsoft.TeamFoundation.WorkItemTracking.Client.Project object to connect to. If omitted, it defaults to the connection opened by Connect-TfsTeamProject (if any).
 
For more details, see the Get-TfsTeamProject cmdlet.
 
.PARAMETER Collection
    Specifies either a URL/name of the Team Project Collection to connect to, or a previously initialized TfsTeamProjectCollection object.
 
When using a URL, it must be fully qualified. The format of this string is as follows:
 
http[s]://<ComputerName>:<Port>/[<TFS-vDir>/]<CollectionName>
 
Valid values for the Transport segment of the URI are HTTP and HTTPS. If you specify a connection URI with a Transport segment, but do not specify a port, the session is created with standards ports: 80 for HTTP and 443 for HTTPS.
 
To connect to a Team Project Collection by using its name, a TfsConfigurationServer object must be supplied either via -Server argument or via a previous call to the Connect-TfsConfigurationServer cmdlet.
 
For more details, see the Get-TfsTeamProjectCollection cmdlet.
 
.INPUTS
    Microsoft.TeamFoundation.WorkItemTracking.Client.Project
    System.String
#>

Function Get-TfsTeam
{
    [CmdletBinding()]
    [OutputType('Microsoft.TeamFoundation.Core.WebApi.WebApiTeam')]
    param
    (
        [Parameter(Position=0)]
        [Alias("Name")]
        [SupportsWildcards()]
        [object]
        $Team = '*',

        [Parameter()]
        [switch]
        $IncludeMembers,

        [Parameter()]
        [switch]
        $IncludeSettings,

        [Parameter(ValueFromPipeline=$true)]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection
    )

    Begin
    {
        #_ImportRequiredAssembly -AssemblyName 'Microsoft.TeamFoundation.Work.WebApi'
    }

    Process
    {
        if ($Team -is [Microsoft.TeamFoundation.Core.WebApi.WebApiTeam]) { _Log "Input item is of type Microsoft.TeamFoundation.Core.WebApi.WebApiTeam; returning input item immediately, without further processing."; return $Team }

        $tp = Get-TfsTeamProject -Project $Project -Collection $Collection; if (-not $tp -or ($tp.Count -ne 1)) {throw "Invalid or non-existent team project $Project."}; $tpc = $tp.Store.TeamProjectCollection

        $client = _GetRestClient 'Microsoft.TeamFoundation.Core.WebApi.TeamHttpClient' -Collection $tpc

        $workClient = _GetRestClient 'Microsoft.TeamFoundation.Work.WebApi.WorkHttpClient'

        if($Team.ToString().Contains('*'))
        {
            _Log "Get all teams matching '$Team'"
            $teams = $client.GetTeamsAsync($tp.Name).Result | Where-Object Name -like $Team
        }
        else
        {
            _Log "Get team named '$Team'"

            if(_TestGuid $Team)
            {
                $Team = [guid]$Team
            }

            $teams = $client.GetTeamAsync($tp.Name, $Team).Result
        }

        foreach($t in $teams)
        {
            if ($IncludeMembers.IsPresent)
            {
                _Log "Retrieving team membership information for team '$($t.Name)'"

                $members = $client.GetTeamMembersWithExtendedPropertiesAsync($tp.Name, $t.Name).Result
                $t | Add-Member -Name 'Members' -MemberType NoteProperty -Value $members

            }
            else
            {
                $t | Add-Member -Name 'Members' -MemberType NoteProperty -Value @()
            }

            if ($IncludeSettings.IsPresent)
            {
                _Log "Retrieving team settings for team '$($t.Name)'"

                $ctx = New-Object 'Microsoft.TeamFoundation.Core.WebApi.Types.TeamContext' -ArgumentList $tp.Name, $t.Name
                $t | Add-Member -Name 'Settings' -MemberType NoteProperty -Value $workClient.GetTeamSettingsAsync($ctx).Result
            }
            else
            {
                $t | Add-Member -Name 'Settings' -MemberType NoteProperty -Value $null
            }
        }

        return $teams
    }
}
<#
 
.SYNOPSIS
    Creates a new team.
 
.PARAMETER Project
    Specifies either the name of the Team Project or a previously initialized Microsoft.TeamFoundation.WorkItemTracking.Client.Project object to connect to. If omitted, it defaults to the connection opened by Connect-TfsTeamProject (if any).
 
For more details, see the Get-TfsTeamProject cmdlet.
 
.PARAMETER Collection
    Specifies either a URL/name of the Team Project Collection to connect to, or a previously initialized TfsTeamProjectCollection object.
 
When using a URL, it must be fully qualified. The format of this string is as follows:
 
http[s]://<ComputerName>:<Port>/[<TFS-vDir>/]<CollectionName>
 
Valid values for the Transport segment of the URI are HTTP and HTTPS. If you specify a connection URI with a Transport segment, but do not specify a port, the session is created with standards ports: 80 for HTTP and 443 for HTTPS.
 
To connect to a Team Project Collection by using its name, a TfsConfigurationServer object must be supplied either via -Server argument or via a previous call to the Connect-TfsConfigurationServer cmdlet.
 
For more details, see the Get-TfsTeamProjectCollection cmdlet.
 
.PARAMETER Passthru
    Returns the results of the command. By default, this cmdlet does not generate any output.
 
.INPUTS
    System.String
#>

Function New-TfsTeam
{
    [CmdletBinding(ConfirmImpact='Medium', SupportsShouldProcess=$true)]
    [OutputType('Microsoft.TeamFoundation.Core.WebApi.WebApiTeam')]
    param
    (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [Alias("Name")]
        [string] 
        $Team,
    
        [Parameter()]
        [string] 
        $Description,
    
        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection,

        [Parameter()]
        [switch]
        $Passthru
    )

    Begin
    {

    }

    Process
    {
        if (-not $PSCmdlet.ShouldProcess($Project, "Create team $Team"))
        {
            return
        }

        $tp = Get-TfsTeamProject -Project $Project -Collection $Collection; if (-not $tp -or ($tp.Count -ne 1)) {throw "Invalid or non-existent team project $Project."}; $tpc = $tp.Store.TeamProjectCollection

        $client = _GetRestClient 'Microsoft.TeamFoundation.Core.WebApi.TeamHttpClient'

        $result = $client.CreateTeamAsync((New-Object 'Microsoft.TeamFoundation.Core.WebApi.WebApiTeam' -Property @{
            Name = $Team
            Description = $Description
        }), $tp.Name).Result

        if ($Passthru)
        {
            return $result
        }
    }
}
<#
.SYNOPSIS
    Deletes a team.
 
.PARAMETER Project
    Specifies either the name of the Team Project or a previously initialized Microsoft.TeamFoundation.WorkItemTracking.Client.Project object to connect to. If omitted, it defaults to the connection opened by Connect-TfsTeamProject (if any).
 
For more details, see the Get-TfsTeamProject cmdlet.
 
.PARAMETER Collection
    Specifies either a URL/name of the Team Project Collection to connect to, or a previously initialized TfsTeamProjectCollection object.
 
When using a URL, it must be fully qualified. The format of this string is as follows:
 
http[s]://<ComputerName>:<Port>/[<TFS-vDir>/]<CollectionName>
 
Valid values for the Transport segment of the URI are HTTP and HTTPS. If you specify a connection URI with a Transport segment, but do not specify a port, the session is created with standards ports: 80 for HTTP and 443 for HTTPS.
 
To connect to a Team Project Collection by using its name, a TfsConfigurationServer object must be supplied either via -Server argument or via a previous call to the Connect-TfsConfigurationServer cmdlet.
 
For more details, see the Get-TfsTeamProjectCollection cmdlet.
 
.INPUTS
    Microsoft.TeamFoundation.Client.TeamFoundationTeam
    System.String
#>

Function Remove-TfsTeam
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')]
    [OutputType('Microsoft.TeamFoundation.Core.WebApi.WebApiTeam')]
    param
    (
        [Parameter(Position=0, ValueFromPipeline=$true)]
        [Alias("Name")]
        [ValidateScript({($_ -is [string]) -or ($_ -is [Microsoft.TeamFoundation.Core.WebApi.WebApiTeam])})] 
        [SupportsWildcards()]
        [object]
        $Team = '*',

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection
    )

    Process
    {
        if($Team.ProjectName) {$Project = $Team.ProjectName}; $tpc = Get-TfsTeamProject -Project $Project -Collection $Collection; if (-not $tpc -or ($tpc.Count -ne 1)) {throw "Invalid or non-existent team project $Project."}; $tp = $tpc.Store.TeamProjectCollection
        $t = Get-TfsTeam -Team $Team -Project $Project -Collection $Collection

        if (-not $PSCmdlet.ShouldProcess($t.Name, 'Delete team'))
        {
            return
        }

        $client = _GetRestClient 'Microsoft.TeamFoundation.Core.WebApi.TeamHttpClient'
        $task = $client.DeleteTeamAsync($tp.Name, $t.Name)

        $result = $task.Result; if($task.IsFaulted) { throw 'Error deleting team' + ": $($task.Exception.InnerExceptions | ForEach-Object {$_.ToString()})" }
    }
}
<#
.SYNOPSIS
Renames a team.
 
.PARAMETER Project
Specifies either the name of the Team Project or a previously initialized Microsoft.TeamFoundation.WorkItemTracking.Client.Project object to connect to. If omitted, it defaults to the connection opened by Connect-TfsTeamProject (if any).
 
For more details, see the Get-TfsTeamProject cmdlet.
 
.PARAMETER Collection
Specifies either a URL/name of the Team Project Collection to connect to, or a previously initialized TfsTeamProjectCollection object.
 
When using a URL, it must be fully qualified. The format of this string is as follows:
 
http[s]://<ComputerName>:<Port>/[<TFS-vDir>/]<CollectionName>
 
Valid values for the Transport segment of the URI are HTTP and HTTPS. If you specify a connection URI with a Transport segment, but do not specify a port, the session is created with standards ports: 80 for HTTP and 443 for HTTPS.
 
To connect to a Team Project Collection by using its name, a TfsConfigurationServer object must be supplied either via -Server argument or via a previous call to the Connect-TfsConfigurationServer cmdlet.
 
For more details, see the Get-TfsTeamProjectCollection cmdlet.
 
.INPUTS
Microsoft.TeamFoundation.Client.TeamFoundationTeam
System.String
#>

Function Rename-TfsTeam
{
    [CmdletBinding(ConfirmImpact='Medium')]
    [OutputType('Microsoft.TeamFoundation.Client.TeamFoundationTeam')]
    param
    (
        [Parameter(Position=0, ValueFromPipeline=$true)]
        [Alias("Name")]
        [ValidateScript({($_ -is [string]) -or ($_ -is [Microsoft.TeamFoundation.Client.TeamFoundationTeam])})] 
        [SupportsWildcards()]
        [object]
        $Team = '*',

        [Parameter()]
        [string]
        $NewName,

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection,

        [Parameter()]
        [switch]
        $Passthru
    )

    Process
    {
        $result = Set-TfsTeam -Team $Team -NewName $NewName -Project $Project -Collection $Collection

        if ($Passthru)
        {
            return $result
        }
    }
}
<#
.SYNOPSIS
Changes the details of a team.
 
.PARAMETER Project
Specifies either the name of the Team Project or a previously initialized Microsoft.TeamFoundation.WorkItemTracking.Client.Project object to connect to. If omitted, it defaults to the connection opened by Connect-TfsTeamProject (if any).
 
For more details, see the Get-TfsTeamProject cmdlet.
 
.PARAMETER Collection
Specifies either a URL/name of the Team Project Collection to connect to, or a previously initialized TfsTeamProjectCollection object.
 
When using a URL, it must be fully qualified. The format of this string is as follows:
 
http[s]://<ComputerName>:<Port>/[<TFS-vDir>/]<CollectionName>
 
Valid values for the Transport segment of the URI are HTTP and HTTPS. If you specify a connection URI with a Transport segment, but do not specify a port, the session is created with standards ports: 80 for HTTP and 443 for HTTPS.
 
To connect to a Team Project Collection by using its name, a TfsConfigurationServer object must be supplied either via -Server argument or via a previous call to the Connect-TfsConfigurationServer cmdlet.
 
For more details, see the Get-TfsTeamProjectCollection cmdlet.
 
.INPUTS
Microsoft.TeamFoundation.Core.WebApi.WebApiTeam
System.String
#>

Function Set-TfsTeam
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]
    [OutputType('Microsoft.TeamFoundation.Client.TeamFoundationTeam')]
    param
    (
        [Parameter(Position=0, ValueFromPipeline=$true)]
        [Alias("Name")]
        [ValidateScript({($_ -is [string]) -or ($_ -is [Microsoft.TeamFoundation.Core.WebApi.WebApiTeam])})] 
        [SupportsWildcards()]
        [object]
        $Team = '*',

        [Parameter()]
        [switch]
        $Default,

        [Parameter()]
        [string]
        $NewName,

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        [Alias('TeamFieldValue')]
        [string]
        $DefaultAreaPath,

        [Parameter()]
        [hashtable]
        $AreaPaths,

        [Parameter()]
        [string]
        $BacklogIteration,

        [Parameter()]
        [object]
        $IterationPaths,

        # Default iteration macro
        [Parameter()]
        [string]
        $DefaultIterationMacro, #= '@CurrentIteration'
    
        # Working Days. Defaults to Monday thru Friday
        [Parameter()]
        [string[]]
        $WorkingDays, #= @("monday", "tuesday", "wednesday", "thursday", "friday"),

        # Bugs behavior
        [Parameter()]
        [ValidateSet('AsTasks', 'AsRequirements', 'Off')]
        [string]
        $BugsBehavior,

        [Parameter()]
        [hashtable]
        $BacklogVisibilities,

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection,

        [Parameter()]
        [switch]
        $Passthru
    )

    Begin
    {
        #_ImportRequiredAssembly -AssemblyName 'Microsoft.TeamFoundation.Work.WebApi'
    }

    Process
    {
        $t = Get-TfsTeam -Team $Team -Project $Project -Collection $Collection
        
        if ($Project)
        {
            $tp = Get-TfsTeamProject -Project $Project -Collection $Collection
            $tpc = $tp.Store.TeamProjectCollection
        }
        else
        {
            $tpc = Get-TfsTeamProjectCollection -Collection $Collection
        }

        $teamService = $tpc.GetService([type]'Microsoft.TeamFoundation.Client.TfsTeamService')

        if ($NewName -and $PSCmdlet.ShouldProcess($Team, "Rename team to '$NewName'"))
        {
            $isDirty = $true
            $t.Name = $NewName
        }

        if ($PSBoundParameters.ContainsKey('Description') -and $PSCmdlet.ShouldProcess($Team, "Set team's description to '$Description'"))
        {
            $isDirty = $true
            $t.Description = $Description
        }

        if ($Default -and $PSCmdlet.ShouldProcess($Team, "Set team to project's default team"))
        {
            $teamService.SetDefaultTeam($t)
        }

        if($isDirty)
        {
            $teamService.UpdateTeam($t)
        }

        # Prepare for the second stage

        $client = _GetRestClient 'Microsoft.TeamFoundation.Work.WebApi.WorkHttpClient' -Collection $tpc
        $ctx = New-Object 'Microsoft.TeamFoundation.Core.WebApi.Types.TeamContext' -ArgumentList @($tp.Name, $t.Name)

        # Set Team Field and Area Path settings

        $patch = New-Object 'Microsoft.TeamFoundation.Work.WebApi.TeamFieldValuesPatch'

        if($DefaultAreaPath -and $PSCmdlet.ShouldProcess($Team, "Set the team's default area path (team field value in TFS) to $DefaultAreaPath"))
        {
            if($tpc.IsHostedServer)
            {
                _Log "Conected to Azure DevOps Server. Treating Team Field Value as Area Path"

                $DefaultAreaPath = _NormalizeCssNodePath -Project $tp.Name -Path $DefaultAreaPath -IncludeTeamProject
            }

            if(-not $AreaPaths)
            {
                _Log "AreaPaths is empty. Adding DefaultAreaPath (TeamFieldValue) to AreaPaths as default value."

                $AreaPaths = @{ $DefaultAreaPath = $true }
            }

            _Log "Setting default area path (team field) to $DefaultAreaPath"

            $patch = New-Object 'Microsoft.TeamFoundation.Work.WebApi.TeamFieldValuesPatch' -Property @{
                DefaultValue = $DefaultAreaPath
            }

            $values = @()

            foreach($a in $AreaPaths.GetEnumerator())
            {
                $values += New-Object 'Microsoft.TeamFoundation.Work.WebApi.TeamFieldValue' -Property @{
                    Value = _NormalizeCssNodePath -Project $tp.Name -Path $a.Key -IncludeTeamProject
                    IncludeChildren = $a.Value
                }
            }

            $patch.Values = [Microsoft.TeamFoundation.Work.WebApi.TeamFieldValue[]] $values

            $task = $client.UpdateTeamFieldValuesAsync($patch, $ctx)

            $result = $task.Result; if($task.IsFaulted) { throw 'Error applying team field value and/or area path settings' + ": $($task.Exception.InnerExceptions | ForEach-Object {$_.ToString()})" }
        }

        # Set backlog and iteration path settings

        $patch = New-Object 'Microsoft.TeamFoundation.Work.WebApi.TeamSettingsPatch'
        $isDirty = $false

        if ($BacklogIteration -and $PSCmdlet.ShouldProcess($Team, "Set the team's backlog iteration to $BacklogIteration"))
        {
            _Log "Setting backlog iteration to $BacklogIteration"
            $iteration = Get-TfsIteration -Iteration $BacklogIteration -Project $Project -Collection $Collection
            $patch.BacklogIteration = [guid] $iteration.Id
            $patch.DefaultIteration = [guid] $iteration.Id

            $isDirty = $true
        }

        if ($DefaultIteration -and $PSCmdlet.ShouldProcess($Team, "Set the team's default iteration to $DefaultIteration"))
        {
            _Log "Setting default iteration to $DefaultIteration"
            $iteration = Get-TfsIteration -Iteration $BacklogIteration -Project $Project -Collection $Collection
            $patch.DefaultIteration = [guid] $iteration.Id

            $isDirty = $true
        }

        if ($BacklogVisibilities -and $PSCmdlet.ShouldProcess($Team, "Set the team's backlog visibilities to $(_DumpObj $BacklogVisibilities)"))
        {
            _Log "Setting backlog iteration to $BacklogVisibilities"
            $patch.BacklogVisibilities = _NewDictionary @([string], [bool]) $BacklogVisibilities

            $isDirty = $true
        }

        if ($DefaultIterationMacro -and $PSCmdlet.ShouldProcess($Team, "Set the team's default iteration macro to $DefaultIterationMacro"))
        {
            _Log "Setting default iteration macro to $DefaultIterationMacro"
            $patch.DefaultIterationMacro = $DefaultIterationMacro

            $isDirty = $true
        }

        if ($WorkingDays -and $PSCmdlet.ShouldProcess($Team, "Set the team's working days to $(_DumpObj $WorkingDays)"))
        {
            _Log "Setting working days to $($WorkingDays|ConvertTo=-Json -Compress)"
            $patch.WorkingDays = $WorkingDays

            $isDirty = $true
        }

        if($BugsBehavior -and $PSCmdlet.ShouldProcess($Team, "Set the team's bugs behavior to $(_DumpObj $BugsBehavior)"))
        {
            _Log "Setting bugs behavior to $(_DumpObj $BugsBehavior)"
            $patch.BugsBehavior = $BugsBehavior

            $isDirty = $true
        }

        if($isDirty)
        {
            $task = $client.UpdateTeamSettingsAsync($patch, $ctx)
            $result = $task.Result; if($task.IsFaulted) { throw 'Error applying iteration settings' + ": $($task.Exception.InnerExceptions | ForEach-Object {$_.ToString()})" }
        }

        if($Passthru.IsPresent)
        {
            return $t
        }
    }
}