Work/Work.ps1

<#
.SYNOPSIS
Gets information about one or more backlogs of the given 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 Get-TfsTeamBacklog
{
    [CmdletBinding()]
    [OutputType('Microsoft.TeamFoundation.Work.WebApi.BacklogLevelConfiguration')]
    param
    (
        [Parameter(Position=0)]
        [Alias("Name")]
        [ValidateScript({($_ -is [string]) -or ($_ -is [Microsoft.TeamFoundation.Work.WebApi.BacklogLevelConfiguration])})] 
        [SupportsWildcards()]
        [object]
        $Backlog = '*',

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

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection
    )

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

    Process
    {
        if ($Backlog -is [Microsoft.TeamFoundation.Work.WebApi.BacklogLevelConfiguration]) { _Log "Input item is of type Microsoft.TeamFoundation.Work.WebApi.BacklogLevelConfiguration; returning input item immediately, without further processing."; return $Backlog }
        $t = Get-TfsTeam -Team $Team -Project $Project -Collection $Collection
        if($t.ProjectName) {$Project = $t.ProjectName}; $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.Work.WebApi.WorkHttpClient' -Collection $tpc
        $ctx = New-Object 'Microsoft.TeamFoundation.Core.WebApi.Types.TeamContext' -ArgumentList @($tp.Name, $t.Name)

        if (-not $Backlog.ToString().Contains('*'))
        {
            _Log "Get backlog '$Backlog'"
            $task = $client.GetBacklogAsync($ctx, $Backlog)

            $result = $task.Result; if($task.IsFaulted) { _throw "Error getting backlog '$Backlog'" $task.Exception.InnerExceptions }
        }
        else
        {
            _Log "Get all backlogs matching '$Backlog'"
            $task = $client.GetBacklogsAsync($ctx)
            $result = $task.Result; if($task.IsFaulted) { _throw 'Error enumerating backlogs' $task.Exception.InnerExceptions }
            
            $result = $result | Where-Object Name -like $Backlog
        }

        return $result
    }
}
Function Get-TfsTeamBoard
{
    [CmdletBinding()]
    [OutputType('Microsoft.TeamFoundation.Work.WebApi.Board')]
    Param
    (
        # Specifies the board name(s). Wildcards accepted
        [Parameter(Position=0)]
        [SupportsWildcards()]
        [Alias('Name')]
        [object]
        $Board = '*',

        [Parameter()]
        [switch]
        $SkipDetails,

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

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection
    )

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

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

        $t = Get-TfsTeam -Team $Team -Project $Project -Collection $Collection; if ($t.Count -ne 1) {throw "Invalid or non-existent team '$Team'."}; if($t.ProjectName) {$Project = $t.ProjectName}; $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.Work.WebApi.WorkHttpClient' -Collection $tpc

        $ctx = New-Object 'Microsoft.TeamFoundation.Core.WebApi.Types.TeamContext' -ArgumentList $tp.Name, $t.Name

        _Log "Getting boards matching '$Board' in team '$($t.Name)'"

        $task = $client.GetBoardsAsync($ctx); $result = $task.Result; if($task.IsFaulted) { _throw 'Error retrieving team boards' $task.Exception.InnerExceptions }

        $boardRefs = $result | Where-Object Name -like $Board

        _Log "Found $($boardRefs.Count) boards matching '$Board' in team '$($t.Name)'"

        if($SkipDetails.IsPresent)
        {
            _Log "SkipDetails switch is present. Returning board references without details"
            return $boardRefs
        }

        foreach($b in $boardRefs)
        {
            _Log "Fetching details for board '$($b.Name)'"

            $task = $client.GetBoardAsync($ctx, $b.Id); $result = $task.Result; if($task.IsFaulted) { _throw "Error fetching board data" $task.Exception.InnerExceptions }
            Write-Output $result
        }
    }
}
Function Get-TfsTeamBoardCardRuleSettings
{
    [CmdletBinding()]
    [OutputType('Microsoft.TeamFoundation.Work.WebApi.BoardCardRuleSettings')]
    Param
    (
        [Parameter()]
        [SupportsWildcards()]
        [object]
        $Board = '*',

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

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection
    )

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

    Process
    {
        if($Board -is [Microsoft.TeamFoundation.Work.WebApi.Board])
        {
            $boards = @($Board.Name)
            $Team = ([uri] $b.Links.Links.team.Href).Segments[-1]
            $Project = ([uri] $b.Links.Links.project.Href).Segments[-1]

            _Log "Getting card rules for board $($Board.Name) in team $Team"
        }
        elseif ($Board.ToString().Contains('*'))
        {
            _Log "Getting card rules for boards matching '$Board' in team $Team"

            $boards = (Get-TfsTeamBoard -Board $Board -SkipDetails -Team $Team -Project $Project -Collection $Collection).Name

            _Log "$($boards.Count) board(s) found matching '$Board'"
        }
        else
        {
            _Log "Getting card rules for board $($Board.Name) in team $Team"

            $boards = @($Board)
        }

        $t = Get-TfsTeam -Team $Team -Project $Project -Collection $Collection; if ($t.Count -ne 1) {throw "Invalid or non-existent team '$Team'."}; if($t.ProjectName) {$Project = $t.ProjectName}; $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.Work.WebApi.WorkHttpClient' -Collection $tpc

        foreach($boardName in $boards)
        {
            $ctx = New-Object 'Microsoft.TeamFoundation.Core.WebApi.Types.TeamContext' -ArgumentList $tp.Name, $t.Name

            _Log "Fetching card rule settings for board $boardName"
            
            $task = $client.GetBoardCardRuleSettingsAsync($ctx,$boardName); $result = $task.Result; if($task.IsFaulted) { _throw "Error retrieving card rule settings for board '$Board'" $task.Exception.InnerExceptions }

            Write-Output $result `
                | Add-Member -Name 'Team' -MemberType NoteProperty -Value $t.Name -PassThru `
                | Add-Member -Name 'Project' -MemberType NoteProperty -Value $tp.Name -PassThru
        }
    }
}
Function Set-TfsTeamBoardCardRuleSettings
{
    [CmdletBinding()]
    [OutputType('Microsoft.TeamFoundation.Work.WebApi.BoardCardRuleSettings')]
    Param
    (
        [Parameter()]
        [object]
        $Board,

        [Parameter(ParameterSetName="Bulk set")]
        [Microsoft.TeamFoundation.Work.WebApi.BoardCardRuleSettings]
        $Rules,

        [Parameter(ParameterSetName="Set individual rules")]
        [string]
        $CardStyleRuleName,

        [Parameter(ParameterSetName="Set individual rules")]
        [string]
        $CardStyleRuleFilter,

        [Parameter(ParameterSetName="Set individual rules")]
        [hashtable]
        $CardStyleRuleSettings,

        [Parameter(ParameterSetName="Set individual rules")]
        [string]
        $TagStyleRuleName,

        [Parameter(ParameterSetName="Set individual rules")]
        [string]
        $TagStyleRuleFilter,

        [Parameter(ParameterSetName="Set individual rules")]
        [hashtable]
        $TagStyleRuleSettings,

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

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection
    )

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

    Process
    {
        Write-Verbose "Getting card rules for team $Team"

        if($Board -is [Microsoft.TeamFoundation.Work.WebApi.Board])
        {
            $boards = @($Board.Name)
            $Team = ([uri] $b.Links.Links.team.Href).Segments[-1]
            $Project = ([uri] $b.Links.Links.project.Href).Segments[-1]
        }
        elseif ($Board.ToString().Contains('*'))
        {
            $boards = (Get-TfsTeamBoard -Board $Board -Team $Team -Project $Project -Collection $Collection).Name
        }
        else
        {
            $boards = @($Board)
        }

        $t = Get-TfsTeam -Team $Team -Project $Project -Collection $Collection; if ($t.Count -ne 1) {throw "Invalid or non-existent team '$Team'."}; if($t.ProjectName) {$Project = $t.ProjectName}; $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.Work.WebApi.WorkHttpClient' -Collection $tpc

        foreach($boardName in $boards)
        {
            $ctx = New-Object 'Microsoft.TeamFoundation.Core.WebApi.Types.TeamContext' -ArgumentList $tp.Name, $t.Name

            $task = $client.GetBoardCardRuleSettingsAsync($ctx,$boardName); $result = $task.Result; if($task.IsFaulted) { _throw "Error retrieving card rule settings for board '$Board'" $task.Exception.InnerExceptions }

            Write-Output $result `
                | Add-Member -Name 'Team' -MemberType NoteProperty -Value $t.Name -PassThru `
                | Add-Member -Name 'Project' -MemberType NoteProperty -Value $tp.Name -PassThru
        }
    }
}
Function Set-TfsWorkItemBoardStatus
{
    [CmdletBinding(ConfirmImpact='Medium', SupportsShouldProcess=$true)]
    [OutputType('Microsoft.TeamFoundation.WorkItemTracking.WebApi.WorkItem')]
    Param
    (
        [Parameter(ValueFromPipeline=$true, Position=0)]
        [Alias("id")]
        [ValidateNotNull()]
        [object]
        $WorkItem,

        [Parameter()]
        [object]
        $Board,

        [Parameter()]
        [object]
        $Column,

        [Parameter()]
        [object]
        $Lane,

        [Parameter()]
        [ValidateSet('Doing', 'Done')]
        [string]
        $ColumnStage,

        [Parameter()]
        [object]
        $Team,

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection
    )

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

    Process
    {
        if ((-not $Column) -and (-not $ColumnStage) -and (-not $Lane))
        {
            throw 'Supply a value to at least one of the following arguments: Column, ColumnStage, Lane'
        }

        if ($WorkItem -is [Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem])
        {
            $tp = $WorkItem.Project
            $tpc = $WorkItem.Store.TeamProjectCollection
        }
        else
        {
            $tp = Get-TfsTeamProject -Project $Project -Collection $Collection
            $tpc = $tp.Store.TeamProjectCollection
            $WorkItem = Get-TfsWorkItem -WorkItem $WorkItem -Collection $Collection
        }

        $t = Get-TfsTeam -Team $Team -Project $tp -Collection $tpc
        $id = [int] $WorkItem.Id
        $rev = $WorkItem.Revision

        # Get the Kanban board column/lane field info

        $b = Get-TfsBoard -Board $Board -Team $t -Project $tp -Collection $tpc

        if (-not $b)
        {
            throw "Invalid or non-existent board '$Board' in team '$Team'"
        }

        $processMessages = @()
        
        $ops = @(
            @{
                Operation = 'Test';
                Path = '/rev';
                Value = $rev.ToString()
            }
        )

        if ($Column)
        {
            $ops += @{
                Operation = 'Add';
                Path = "/fields/$($b.Fields.ColumnField.ReferenceName)";
                Value = $Column
            }

            $processMessages += "Board Column='$Column'"
        }

        if ($Lane)
        {
            $ops += @{
                Operation = 'Add';
                Path = "/fields/$($b.Fields.RowField.ReferenceName)";
                Value = $Lane
            }

            $processMessages += "Board Lane='$Lane'"
        }

        if ($ColumnStage)
        {
            $ops += @{
                Operation = 'Add';
                Path = "/fields/$($b.Fields.DoneField.ReferenceName)";
                Value = ($ColumnStage -eq 'Done') 
            }

            $processMessages += "Board Stage (Doing/Done)='$ColumnStage'"
        }

        if ($PSCmdlet.ShouldProcess("$($WorkItem.WorkItemType) $id ('$($WorkItem.Title)')", "Set work item board status: $($processMessages -join ', ')"))
        {
            $patch = _GetJsonPatchDocument $ops
            $client = _GetRestClient 'Microsoft.TeamFoundation.WorkItemTracking.WebApi.WorkItemTrackingHttpClient' -Collection $tpc
            $wi = $client.UpdateWorkItemAsync($patch, $id).Result
            return $wi
        }
    }
}