functions/add-adoclassificationnode.ps1
<# .SYNOPSIS Creates or updates a classification node (Area or Iteration). .DESCRIPTION Uses the Azure DevOps Work Item Tracking REST API (Classification Nodes - Create Or Update) to create new or update existing classification nodes. Supports: - Creating new Area or Iteration nodes with optional attributes - Moving existing nodes by providing their ID - Setting start/finish dates for Iteration nodes - Specifying parent path for hierarchical organization .OUTPUTS ADO.TOOLS.WorkItem.ClassificationNode .PARAMETER Organization Azure DevOps organization name (e.g. contoso). .PARAMETER Project Project name or id. .PARAMETER Token Personal Access Token (PAT) with vso.work_write scope. .PARAMETER StructureGroup Areas | Iterations - specifies whether this is an Area or Iteration node. .PARAMETER Path Optional path of the classification node. Use for creating nodes under a specific parent (e.g. 'ParentArea\ChildArea') or leave empty to create at root level. .PARAMETER Name Name of the new classification node (required for creating new nodes). .PARAMETER Id ID of an existing node to move (use instead of -Name for move operations). .PARAMETER StartDate Start date for Iteration nodes (ISO 8601 format, e.g. '2024-10-27T00:00:00Z'). .PARAMETER FinishDate Finish date for Iteration nodes (ISO 8601 format, e.g. '2024-10-31T00:00:00Z'). .PARAMETER Attributes Hashtable of additional attributes (alternative to individual date parameters). .PARAMETER ApiVersion API version (default 7.2-preview.2). .EXAMPLE PS> Add-ADOClassificationNode -Organization contoso -Project WebApp -Token $pat -StructureGroup Areas -Name "Frontend Team" Creates a new Area node named "Frontend Team" at the root level. .EXAMPLE PS> Add-ADOClassificationNode -Organization contoso -Project WebApp -Token $pat -StructureGroup Iterations -Name "Sprint 1" -StartDate "2024-01-01T00:00:00Z" -FinishDate "2024-01-15T00:00:00Z" Creates a new Iteration with start and finish dates. .EXAMPLE PS> Add-ADOClassificationNode -Organization contoso -Project WebApp -Token $pat -StructureGroup Areas -Path "Development\Backend" -Name "API Team" Creates "API Team" area under Development\Backend path. .EXAMPLE PS> Add-ADOClassificationNode -Organization contoso -Project WebApp -Token $pat -StructureGroup Areas -Path "NewParent" -Id 126391 Moves existing area node (ID 126391) under "NewParent" path. .LINK https://learn.microsoft.com/azure/devops .NOTES Author: Oleksandr Nikolaiev (@onikolaiev) #> function Add-ADOClassificationNode { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions","")] [CmdletBinding(DefaultParameterSetName='CreateByName')] [OutputType('ADO.TOOLS.WorkItem.ClassificationNode')] param( [Parameter(Mandatory=$true)] [string]$Organization, [Parameter(Mandatory=$true)] [string]$Project, [Parameter(Mandatory=$true)] [string]$Token, [Parameter(Mandatory=$true)] [ValidateSet('Areas','Iterations')] [string]$StructureGroup, [Parameter()] [string]$Path, [Parameter(Mandatory=$true, ParameterSetName='CreateByName')] [string]$Name, [Parameter(Mandatory=$true, ParameterSetName='MoveById')] [int]$Id, [Parameter()] [datetime]$StartDate, [Parameter()] [datetime]$FinishDate, [Parameter()] [hashtable]$Attributes, [Parameter()] [string]$ApiVersion = '7.2-preview.2' ) begin { Write-PSFMessage -Level Verbose -Message "Starting classification node operation (Group: $StructureGroup) (Org: $Organization / Project: $Project)" Invoke-TimeSignal -Start } process { if (Test-PSFFunctionInterrupt) { return } try { # Build API URI $basePath = "$Project/_apis/wit/classificationnodes/$StructureGroup" if ($Path) { # URL encode the path segments $pathSegments = $Path.Split('\', [StringSplitOptions]::RemoveEmptyEntries) $encodedSegments = $pathSegments | ForEach-Object { [System.Uri]::EscapeDataString($_) } $basePath += '/' + ($encodedSegments -join '/') } Write-PSFMessage -Level Verbose -Message "API path: $basePath" # Prepare request body $body = @{} if ($PSCmdlet.ParameterSetName -eq 'CreateByName') { $body['name'] = $Name Write-PSFMessage -Level Verbose -Message "Creating new node: $Name" } else { $body['id'] = $Id Write-PSFMessage -Level Verbose -Message "Moving existing node ID: $Id" } # Handle attributes (dates for iterations) $nodeAttributes = @{} if ($StartDate) { $nodeAttributes['startDate'] = $StartDate.ToString('yyyy-MM-ddTHH:mm:ssZ') } if ($FinishDate) { $nodeAttributes['finishDate'] = $FinishDate.ToString('yyyy-MM-ddTHH:mm:ssZ') } # Merge with provided attributes hashtable if ($Attributes) { foreach ($key in $Attributes.Keys) { $nodeAttributes[$key] = $Attributes[$key] } } if ($nodeAttributes.Count -gt 0) { $body['attributes'] = $nodeAttributes Write-PSFMessage -Level Verbose -Message "Added $($nodeAttributes.Count) attribute(s)" } # Convert to JSON $jsonBody = $body | ConvertTo-Json -Depth 10 -Compress Write-PSFMessage -Level Verbose -Message "Request body: $jsonBody" # Make API call $response = Invoke-ADOApiRequest -Organization $Organization ` -Token $Token ` -ApiUri $basePath ` -Method 'POST' ` -Body $jsonBody ` -Headers @{'Content-Type' = 'application/json'} ` -ApiVersion $ApiVersion Write-PSFMessage -Level Verbose -Message "Successfully processed classification node (ID: $($response.Results.id))" return $response.Results | Select-PSFObject * -TypeName 'ADO.TOOLS.WorkItem.ClassificationNode' } catch { Write-PSFMessage -Level Error -Message "Failed to process classification node: $($_.Exception.Message)" -Exception $PSItem.Exception Stop-PSFFunction -Message "Stopping because of errors" -Target $PSCmdlet.MyInvocation.MyCommand.Name -ErrorRecord $_ } } end { Write-PSFMessage -Level Verbose -Message "Completed classification node operation" Invoke-TimeSignal -End } } |