WorkItem/ClassificationNode/WorkItem_ClassificationNode.ps1

Function Get-TfsClassificationNode
{
    [CmdletBinding()]
    [OutputType('Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemClassificationNode')]
    Param
    (
        [Parameter()]
        [SupportsWildcards()]
        [Alias('Area')]
        [Alias('Iteration')]
        [Alias('Path')]
        [object]
        $Node = '\**',

        [Parameter()]
        [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]
        $StructureGroup,

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection
    )

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

        if(-not ($PSBoundParameters.ContainsKey('StructureGroup'))){if ($MyInvocation.InvocationName -like '*Area'){$StructureGroup = [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]::Areas}elseif ($MyInvocation.InvocationName -like '*Iteration'){$StructureGroup = [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]::Iterations}else{throw "Invalid or missing StructureGroup argument"}}
        
        $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.WorkItemTracking.WebApi.WorkItemTrackingHttpClient' -Collection $tpc

        if(_IsWildcard $Node)
        {
            $depth = 2
            $pattern = _NormalizeNodePath -Project $tp.Name -Scope $StructureGroup -Path $Node -IncludeScope -IncludeTeamProject -IncludeLeadingBackslash
            $Node = '/'

            _Log "Preparing to recursively search for pattern '$pattern'"
        }
        else
        {
            $Node = _NormalizeNodePath -Project $tp.Name -Scope $StructureGroup -Path $Node -IncludeLeadingBackslash
            $depth = 0

            _Log "Getting $StructureGroup under path '$Node'"
        }

        $task = $client.GetClassificationNodeAsync($tp.Name,$StructureGroup,$Node,$depth); $result = $task.Result; if($task.IsFaulted) { _throw  "Error retrieving $StructureGroup from path '$Node'" $task.Exception.InnerExceptions }

        if(-not ($pattern))
        {
            return $result
        }

        _GetNodeRecursively -Pattern $pattern -Node $result -StructureGroup $StructureGroup -Project $tp.Name -Client $client
    }
}

Function _GetNodeRecursively($Pattern, $Node, $StructureGroup, $Project, $Client, $Depth = 2)
{
    _Log "Searching for pattern '$Pattern' under $($Node.Path)"

    if($Node.HasChildren -and ($Node.Children.Count -eq 0))
    {
        _Log "Fetching child nodes for node '$($Node.Path)'"

        $task = $client.GetClassificationNodeAsync($Project,$StructureGroup,$Node.RelativePath, $Depth); $result = $task.Result; if($task.IsFaulted) { _throw  "Error retrieving $StructureGroup from path '$($Node.RelativePath)'" $task.Exception.InnerExceptions }
        $Node = $result
    }

    if($Node.Path -like $Pattern)
    {
        _Log "$($Node.Path) matches pattern $Pattern. Returning node."
        Write-Output $Node
    }

    foreach($c in $Node.Children)
    {
        _GetNodeRecursively -Pattern $Pattern -Node $c -StructureGroup $StructureGroup -Project $Project -Client $Client -Depth $Depth
    }
}

Set-Alias -Name Get-TfsArea -Value Get-TfsClassificationNode
Set-Alias -Name Get-TfsIteration -Value Get-TfsClassificationNode
Function Move-TfsClassificationNode
{
    [CmdletBinding(ConfirmImpact='Medium', SupportsShouldProcess=$true)]
    [OutputType('Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemClassificationNode')]
    Param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [Alias('Area')]
        [Alias('Iteration')]
        [Alias('Path')]
        [object]
        $Node,

        [Parameter(Mandatory=$true, Position=1)]
        [Alias('MoveTo')]
        [object]
        $Destination,

        [Parameter()]
        [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]
        $StructureGroup,

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection,

        [Parameter()]
        [switch]
        $Passthru
    )

    Process
    {
        if(-not ($PSBoundParameters.ContainsKey('StructureGroup'))){if ($MyInvocation.InvocationName -like '*Area'){$StructureGroup = [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]::Areas}elseif ($MyInvocation.InvocationName -like '*Iteration'){$StructureGroup = [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]::Iterations}else{throw "Invalid or missing StructureGroup argument"}}

        $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

        $structureGroup = _GetStructureGroup $structureGroup

        $sourceNode = _GetNode -Node $Node -Project $Project -Collection $Collection

        if(-not $sourceNode)
        {
            throw "Invalid or non-existent $StructureGroup path '$Node'"
        }

        _Log "Source node: '$($sourceNode.FullPath)'"

        $destinationNode = _GetNode -Node $Destination -Project $Project -Collection $Collection
        
        if(-not $destinationNode)
        {
            throw "Invalid or non-existent $StructureGroup path '$Node'"
        }

        _Log "Destination node: '$($destinationNode.FullPath)'"

        $moveTo = "$($destinationNode.Path)\$($sourceNode.Name)"

        if (-not $PSCmdlet.ShouldProcess($sourceNode.FullPath, "Move node to '$moveTo'"))
        {
            return
        }

        $patch = New-Object 'Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemClassificationNode' -Property @{
            Id = $sourceNode.Id
        }

        $client = _GetRestClient 'Microsoft.TeamFoundation.WorkItemTracking.WebApi.WorkItemTrackingHttpClient' -Collection $tpc

        $task = $client.CreateOrUpdateClassificationNodeAsync($patch, $tp.Name, $structureGroup, $destinationNode.RelativePath.SubString(1)); $result = $task.Result; if($task.IsFaulted) { _throw  "Error moving node $($sourceNode.RelativePath) to $($destinationNode.RelativePath)" $task.Exception.InnerExceptions }

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

Set-Alias -Name Move-TfsArea -Value Move-TfsClassificationNode
Set-Alias -Name Move-TfsIteration -Value Move-TfsClassificationNode
<#
.SYNOPSIS
 
.DESCRIPTION
 
.EXAMPLE
 
.INPUTS
 
.OUTPUTS
 
.NOTES
 
#>

Function New-TfsClassificationNode
{
    [CmdletBinding(ConfirmImpact='Medium', SupportsShouldProcess=$true)]
    [OutputType('Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemClassificationNode')]
    Param
    (
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [Alias("Area")]
        [Alias("Iteration")]
        [Alias("Path")]
        [string]
        $Node,

        [Parameter()]
        [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]
        $StructureGroup,

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection,

        [Parameter()]
        [switch]
        $Passthru,

        [Parameter()]
        [switch]
        $Force
    )

    Process
    {
        if(-not ($PSBoundParameters.ContainsKey('StructureGroup'))){if ($MyInvocation.InvocationName -like '*Area'){$StructureGroup = [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]::Areas}elseif ($MyInvocation.InvocationName -like '*Iteration'){$StructureGroup = [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]::Iterations}else{throw "Invalid or missing StructureGroup argument"}}

        $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

        $Node = _NormalizeNodePath $Node -Project $tp.Name -Scope $StructureGroup

        if(-not $PSCmdlet.ShouldProcess($tp.Name, "Create node '$Node'"))
        {
            return
        }

        $client = _GetRestClient 'Microsoft.TeamFoundation.WorkItemTracking.WebApi.WorkItemTrackingHttpClient' -Collection $tpc

        $parentPath = (Split-Path $Node -Parent)
        $nodeName = (Split-Path $Node -Leaf)

        if(-not (Test-TfsClassificationNode -Node $parentPath -StructureGroup $StructureGroup -Project $Project))
        {
            _Log "Parent node '$parentPath' does not exist"

            if(-not $Force.IsPresent)
            {
                _throw "Parent node '$parentPath' does not exist. Check the path or use -Force the create any missing parent nodes."
            }

            _Log "Creating missing parent path '$parentPath'"

            $PSBoundParameters['Node'] = $parentPath
            $PSBoundParameters['StructureGroup'] = $StructureGroup

            New-TfsClassificationNode @PSBoundParameters
        }

        $patch = New-Object 'Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemClassificationNode' -Property @{
            Name = $nodeName
        }
    
        $task = $client.CreateOrUpdateClassificationNodeAsync($patch, $tp.Name, $structureGroup, $parentPath, "Error creating node $node"); $result = $task.Result; if($task.IsFaulted) { _throw  $task.Exception.InnerExceptions }

        if ($Passthru)
        {
            return $node
        }
    }
}

Set-Alias -Name New-TfsArea -Value New-TfsClassificationNode
Set-Alias -Name New-TfsIteration -Value New-TfsClassificationNode
<#
.SYNOPSIS
    Deletes one or more Work Item Areas.
 
.PARAMETER Area
    Specifies the name, URI or path of an Area. Wildcards are permitted. If omitted, all Areas in the given Team Project are returned.
To supply a path, use a backslash ('\') between the path segments. Leading and trailing backslashes are optional.
When supplying a URI, use URIs in the form of 'vstfs:///Classification/Node/<GUID>' (where <GUID> is the unique identifier of the given node)
 
.PARAMETER MoveTo
    Specifies the new area path for the work items currently assigned to the area being deleted, if any. When omitted, defaults to the root area
 
.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 Remove-TfsClassificationNode
{
    [CmdletBinding(ConfirmImpact='High', SupportsShouldProcess=$true)]
    Param
    (
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [Alias("Area")]
        [Alias("Iteration")]
        [Alias("Path")]
        [ValidateScript({($_ -is [string]) -or ($_ -is [type]'Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemClassificationNode')})] 
        [SupportsWildcards()]
        [object]
        $Node,

        [Parameter(Position=1)]
        [Alias("NewPath")]
        [ValidateScript({ ($_ -is [string]) -or ($_ -is [type]'Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemClassificationNode') })] 
        [object]
        $MoveTo = '\',

        [Parameter()]
        [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]
        $StructureGroup,

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection
    )

    Process
    {
        if(-not ($PSBoundParameters.ContainsKey('StructureGroup'))){if ($MyInvocation.InvocationName -like '*Area'){$StructureGroup = [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]::Areas}elseif ($MyInvocation.InvocationName -like '*Iteration'){$StructureGroup = [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]::Iterations}else{throw "Invalid or missing StructureGroup argument"}}

        $nodes = Get-TfsClassificationNode -Node $Node -StructureGroup $StructureGroup -Project $Project -Collection $Collection
        $moveToNode =  Get-TfsClassificationNode -Node $MoveTo -StructureGroup $StructureGroup -Project $Project -Collection $Collection

        if(-not $moveToNode)
        {
            throw "Invalid or non-existent node '$MoveTo'. To remove nodes, supply a valid node in the -MoveTo argument"
        }

        _Log "Remove nodes and move orphaned work items no node '$($moveToNode.FullPath)'"

        $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.WorkItemTracking.WebApi.WorkItemTrackingHttpClient' -Collection $tpc

        foreach($node in $nodes)
        {
            if(-not ($PSCmdlet.ShouldProcess($node.TeamProject, "Remove node $($node.RelativePath)")))
            {
                continue
            }

            $task = $client.DeleteClassificationNodeAsync($node.TeamProject,$StructureGroup,$node.RelativePath,$moveToNode.Id); $result = $task.Result; if($task.IsFaulted) { _throw  "Error removing node '$($node.FullPath)'" $task.Exception.InnerExceptions }
        }
    }
}

Set-Alias -Name Remove-TfsArea -Value Remove-TfsClassificationNode
Set-Alias -Name Remove-TfsIteration -Value Remove-TfsClassificationNode
<#
.SYNOPSIS
    Renames a Work Item Iteration.
 
.PARAMETER Iteration
    Specifies the name, URI or path of an Iteration. Wildcards are permitted. If omitted, all Iterations in the given Team Project are returned.nnTo supply a path, use a backslash ('\') between the path segments. Leading and trailing backslashes are optional.nnWhen supplying a URI, use URIs in the form of 'vstfs:///Classification/Node/<GUID>' (where <GUID> is the unique identifier of the given node).
 
.PARAMETER NewName
    Specifies the new name of the iteration. Enter only a name, not a path and name. If you enter a path that is different from the path that is specified in the Iteration parameter, Rename-TfsIteration generates an error. To rename and move an item, use the Move-TfsIteration cmdlet.
 
.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 Rename-TfsClassificationNode
{
    [CmdletBinding(ConfirmImpact='Medium', SupportsShouldProcess=$true)]
    [OutputType('Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemClassificationNode')]
    Param
    (
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [ValidateScript({($_ -is [string]) -or ($_ -is [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemClassificationNode])})] 
        [Alias("Area")]
        [Alias("Iteration")]
        [Alias("Path")]
        [object]
        $Node,

        [Parameter(Mandatory=$true, Position=1)]
        [string]
        $NewName,

        [Parameter()]
        [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]
        $StructureGroup,

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection,

        [Parameter()]
        [switch]
        $Passthru
    )

    Process
    {
        if(-not ($PSBoundParameters.ContainsKey('StructureGroup'))){if ($MyInvocation.InvocationName -like '*Area'){$StructureGroup = [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]::Areas}elseif ($MyInvocation.InvocationName -like '*Iteration'){$StructureGroup = [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]::Iterations}else{throw "Invalid or missing StructureGroup argument"}}
        
        $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.WorkItemTracking.WebApi.WorkItemTrackingHttpClient' -Collection $tpc

        $nodeToRename = Get-TfsClassificationNode -Node $Node -StructureGroup $StructureGroup -Project $Project -Collection $Collection

        if(-not $PSCmdlet.ShouldProcess($nodeToRename.FullPath, "Rename node to '$NewName'"))
        {
            return
        }

        $patch = New-Object 'Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemClassificationNode' -Property @{
            Name = $NewName
        }
    
        $task = $client.UpdateClassificationNodeAsync($patch, $tp.Name, $structureGroup, $nodeToRename.RelativePath.SubString(1)); $result = $task.Result; if($task.IsFaulted) { _throw  "Error renaming node $node" $task.Exception.InnerExceptions }

        if ($Passthru)
        {
            return $result
        }
    }
}

Set-Alias -Name Rename-TfsArea -Value Rename-TfsClassificationNode
Set-Alias -Name Rename-TfsIteration -Value Rename-TfsClassificationNode
<#
.SYNOPSIS
    Modifies the name, position and/or the dates of a Work Item Iteration.
 
.PARAMETER Iteration
    Specifies the name, URI or path of an Iteration. Wildcards are permitted. If omitted, all Iterations in the given Team Project are returned.nnTo supply a path, use a backslash ('\') between the path segments. Leading and trailing backslashes are optional.nnWhen supplying a URI, use URIs in the form of 'vstfs:///Classification/Node/<GUID>' (where <GUID> is the unique identifier of the given node).
 
.PARAMETER NewName
    Specifies the new name of the iteration. Enter only a name, not a path and name. If you enter a path that is different from the path that is specified in the Iteration parameter, Rename-TfsIteration generates an error. To rename and move an item, use the Move-TfsIteration cmdlet.
 
.PARAMETER MoveBy
    Reorders an iteration by moving it either up or down inside its parent. A positive value moves an iteration down, whereas a negative one moves it up.
 
.PARAMETER StartDate
    Sets the start date of the iteration. To clear the start date, set it to $null. Note that when clearing a date, both must be cleared at the same time (i.e. setting both StartDate and FinishDate to $null)
 
.PARAMETER FinishDate
    Sets the finish date of the iteration. To clear the finish date, set it to $null. Note that when clearing a date, both must be cleared at the same time (i.e. setting both StartDate and FinishDate to $null)
 
.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 Set-TfsClassificationNode
{
    [CmdletBinding(ConfirmImpact='Medium', SupportsShouldProcess=$true)]
    [OutputType('Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemClassificationNode')]
    Param
    (
        [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [Alias("Area")]
        [Alias("Iteration")]
        [Alias("Path")]
        [ValidateScript({($_ -is [string]) -or ($_ -is [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemClassificationNode])})] 
        [SupportsWildcards()]
        [object]
        $Node,

        [Parameter()]
        [string]
        $StructureGroup,

        [Parameter()]
        [int]
        $MoveBy,

        [Parameter()]
        [Nullable[DateTime]]
        $StartDate,
    
        [Parameter()]
        [Nullable[DateTime]]
        $FinishDate,

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection,

        [Parameter()]
        [switch]
        $Passthru
    )

    Process
    {
        if(-not ($PSBoundParameters.ContainsKey('StructureGroup'))){if ($MyInvocation.InvocationName -like '*Area'){$StructureGroup = [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]::Areas}elseif ($MyInvocation.InvocationName -like '*Iteration'){$StructureGroup = [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]::Iterations}else{throw "Invalid or missing StructureGroup argument"}}

        $nodeToSet = Get-TfsClassificationNode -Node $Node -StructureGroup $StructureGroup -Project $Project -Collection $Collection

        if (-not $nodeToSet)
        {
            throw "Invalid or non-existent node $Node"
        }

        $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.WorkItemTracking.WebApi.WorkItemTrackingHttpClient' -Collection $tpc

        if ($PSBoundParameters.ContainsKey('MoveBy') -and $PSCmdlet.ShouldProcess($nodeToSet.RelativePath, "Reorder node by moving it $MoveBy positions (negative is up, positive is down)"))
        {
            _throw "Reorder areas/iterations is currently not supported"
        }

        if ($StructureGroup -eq 'Iterations' -and ($PSBoundParameters.ContainsKey("StartDate") -or $PSBoundParameters.ContainsKey("FinishDate")))
        {
            if (-not ($PSBoundParameters.ContainsKey("StartDate") -and $PSBoundParameters.ContainsKey("FinishDate")))
            {
                throw "When setting iteration dates, both start and finish dates must be supplied."
            }

            if($PSCmdlet.ShouldProcess($nodeToSet.RelativePath, "Set iteration start date to '$StartDate' and finish date to '$FinishDate'"))
            {
                $patch = New-Object 'Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemClassificationNode' -Property @{
                    attributes = _NewDictionary @([string], [object]) @{
                        startDate = $StartDate
                        finishDate = $FinishDate
                    }
                }

                $task = $client.UpdateClassificationNodeAsync($patch, $tp.Name, $structureGroup, $nodeToSet.RelativePath.SubString(1)); $result = $task.Result; if($task.IsFaulted) { _throw  "Error setting dates on iteration '$($nodeToSet.FullPath)'" $task.Exception.InnerExceptions }
            }
        }

        if($Passthru)
        {
            return Get-TfsClassificationNode -Node $Node -StructureGroup $StructureGroup -Project $Project -Collection $Collection
        }
    }
}

Set-Alias -Name Set-TfsArea -Value Set-TfsClassificationNode
Set-Alias -Name Set-TfsIteration -Value Set-TfsClassificationNode
Function Test-TfsClassificationNode
{
    [CmdletBinding()]
    Param
    (
        [Parameter()]
        [Alias('Area')]
        [Alias('Iteration')]
        [Alias('Path')]
        [SupportsWildcards()]
        [object]
        $Node,

        [Parameter()]
        [Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.TreeStructureGroup]
        $StructureGroup,

        [Parameter()]
        [object]
        $Project,

        [Parameter()]
        [object]
        $Collection
    )

    Process
    {
        try
        {
            return (Get-TfsClassificationNode @PSBoundParameters).Count -gt 0
        }
        catch
        {
            _Log "Error testing node '$Node': $($_.Exception)"
            
            return $false
        }
    }
}

Set-Alias -Name Test-TfsArea -Value Test-TfsClassificationNode
Set-Alias -Name Test-TfsIteration -Value Test-TfsClassificationNode