Add-ADOIterationPath.ps1
function Add-ADOIterationPath { <# .Synopsis Adds an Azure DevOps IterationPath .Description Adds an Azure DevOps IterationPath. IterationPaths are used to logically group work items within a project. .Example Add-ADOIterationPath -Organization MyOrg -Project MyProject -IterationPath MyIterationPath .Example Add-ADOIterationPath -Organization MyOrg -Project MyProject -IterationPath MyIterationPath\MyNestedPath .Link Get-ADOIterationPath .Link Remove-ADOIterationPath #> [CmdletBinding(SupportsShouldProcess)] [OutputType('PSDevOps.IterationPath')] param( # The Organization. [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [Alias('Org')] [string] $Organization, # The Project. [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $Project, # The IterationPath. [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $IterationPath, # The start date of the iteration. [Parameter(ValueFromPipelineByPropertyName)] [DateTime] $StartDate, # The end date of the iteration. [Parameter(ValueFromPipelineByPropertyName)] [DateTime] $EndDate, # The server. By default https://dev.azure.com/. # To use against TFS, provide the tfs server URL (e.g. http://tfsserver:8080/tfs). [Parameter(ValueFromPipelineByPropertyName)] [uri] $Server = "https://dev.azure.com/", # The api version. By default, 5.1. # If targeting TFS, this will need to change to match your server version. # See: https://docs.microsoft.com/en-us/azure/devops/integrate/concepts/rest-api-versioning?view=azure-devops [string] $ApiVersion = "5.1-preview") dynamicParam { . $GetInvokeParameters -DynamicParameter } begin { #region Copy Invoke-ADORestAPI parameters $invokeParams = . $getInvokeParameters $PSBoundParameters #endregion Copy Invoke-ADORestAPI parameters $q = [Collections.Queue]::new() # Collect input in a queue } process { $q.Enqueue(@{} + $psboundParameters) # enqueue new input. } end { $c,$t, $id = 0, $q.Count, [Random]::new().Next() $allIterationPaths = @{} #region Until the Queue is Empty while ($q.Count) { $qi = $q.Dequeue() # Dequeue each item. $getIterationPathParams = @{} + $qi # Prepare input for Get-ADOIterationPath $getIterationPathParams.Remove('IterationPath') $getIterationPathParams.Remove('StartDate') $getIterationPathParams.Remove('EndDate') $getIterationPathParams.Remove('WhatIf') $getIterationPathParams.Remove('Confirm') foreach ($kv in $qi.GetEnumerator()) { # Repopulate the input parameters. $ExecutionContext.SessionState.PSVariable.Set($kv.Key, $kv.Value) } if ($t -gt 1) { $c++ Write-Progress "Adding Iteration Paths" "$IterationPath " -PercentComplete ($c * 100 / $t) -Id $id } $IterationPathParts= @($IterationPath -split '\\') if (-not $allIterationPaths["$Organization/$Project"]) { # Cache ADO Iteration Paths $allIterationPaths["$Organization/$Project"] = @(Get-ADOIterationPath @getIterationPathParams) } $existingIterationPath = $null $closestIterationPath = $null $IterationPathCache = $allIterationPaths["$Organization/$Project"] foreach ($path in $IterationPathCache) { # See If the path already exists, or where we can put it in the hierarchy. if ($path.Path -eq "\$project\Iteration\$IterationPath") { $existingIterationPath = $path } elseif ($IterationPathParts.Count -gt 1) { for ($i = ($IterationPathParts.Count - 2); $i -ge 0; $i--) { if ($path.Path -like "\$Project\Iteration\$($IterationPathParts[0..$i] -join '\')") { $closestIterationPath = $path.Path break } } } } if ($existingIterationPath) { # If it already existed, write it to Verbose. Write-Verbose "IterationPath: '\$Project\Iteration\$IterationPath' already exists" continue } $body = @{name = $IterationPathParts[-1]} # Prepare the input body $base = "/{Organization}/{Project}/_apis/wit/classificationNodes/Iterations" if ($closestIterationPath) { # If we had a closest path, $firstSlash, $projectPart, $IterationPart, $closestPart = # split up the IterationPath $closestIterationPath -split '\\', 4 $base += "/$closestPart" # and add the subpath to the base. } $uri = $Server.ToString().TrimEnd('/') + (. $ReplaceRouteParameter $base) + '?' + $( if ($Server -ne 'https://dev.azure.com' -and -not $psBoundParameters['apiVersion']) { $apiVersion = "2.0" } if ($ApiVersion) { "api-version=$ApiVersion" } ) if ($StartDate -or $EndDate) { $body.attributes = @{} } if ($StartDate) { $body.Attributes.startDate = $StartDate.ToUniversalTime().ToString('o') } if ($EndDate) { $body.Attributes.finishDate = $EndDate.ToUniversalTime().ToString('o') } $invokeParams.Uri = $uri $invokeParams.Method = 'POST' $invokeParams.Body = $body if ($WhatIfPreference) { # If we passed -WhatIf $invokeParams.Remove('PersonalAccessToken') # remove the PersonalAccessToken $invokeParams # and return other parameters. continue } if (-not $PSCmdlet.ShouldProcess("Add IterationPath $IterationPath")) { continue } # Check ShouldProcess $typeName = "$Organization.IterationPath", "$Organization.$project.IterationPath", "PSDevOps.IterationPath" # Invoke the REST API. Invoke-ADORestAPI @invokeParams -PSTypeName $typeName -Property @{ Organization = $organization Project = $Project Server = $server } } #endregion Until the Queue is Empty if ($t -gt 1) { Write-Progress "Adding Iteration Paths" ' ' -Completed -Id $id } } } |