Modules/AzureDevOpsDsc.Common/Api/Functions/Private/Api/WIT/New-WITTags.ps1

Function New-WITTags {
    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSObject[]])]
    param
    (
        # The name of the Azure DevOps organization.
        [Parameter(Mandatory = $true)]
        [string]$Organization,

        # The name of the Azure DevOps project.
        [Parameter(Mandatory = $true)]
        [Alias('Name')]
        [System.String]$ProjectName,

        # The ID of the Tag to delete.
        [Parameter(Mandatory = $true)]
        [Alias('WITTagId')]
        [System.String[]]$WorkItemTrackingNames,

        # Get the latest API version. 7.1 is not supported by the API endpoint.
        [Parameter()]
        [String]
        $ApiVersion = $(Get-AzDevOpsApiVersion | Where-Object { $_ -eq '7.1' } | Select-Object -Last 1)
    )

    # Get the Work Item Type Tag ID
    $witTypes = List-WITTypes -Organization $Organization -ProjectName $ProjectName
    if ($null -eq $witTypes -or $witTypes.Count -eq 0)
    {
        Write-Error "[New-WITTag] Failed to retrieve Work Item Types for project '$ProjectName'. Check authentication token and organization settings."
        return $null
    }
    $WorkItemType = $witTypes[0].name

    # Validate the parameters
    $NewWIPTagParams = @{
        # https://dev.azure.com/{organization}/{project}/_apis/wit/workitems/${type}?api-version=7.1
        Uri              = 'https://dev.azure.com/{0}/{1}/_apis/wit/workitems/${2}?api-version={3}' -f $Organization, $ProjectName, $WorkItemType, $ApiVersion
        HttpContentType  =  "application/json-patch+json"
        Method           =  "POST"
        Body             = @(
            @{
                "op" = "add"
                "path" = "/fields/System.Title"
                "from" = $null
                "value" = 'System - POWERSHELL DSC - Temp Work Item for Tag Creation'
            },
            @{
                "op" = "add"
                "path" = "/fields/System.Tags"
                "value" = $WorkItemTrackingNames -join '; '
            }
        ) | ConvertTo-Json
    }

    #
    # Create the Work Item Tag

    try
    {
        # Invoke the Azure DevOps REST API to create the project]
        $createdWorkItem = Invoke-AzDevOpsApiRestMethod @NewWIPTagParams
    }
    catch
    {
        Write-Error "[New-WITTag] Failed to create Work Item Tags for the Azure DevOps Project $ProjectName. Error: $_"
        return $null
    }

    # Validate the parameters
    $DeleteWIPTagParams = @{
        # https://dev.azure.com/{organization}/{project}/_apis/wit/workitems/{id}?api-version=7.1
        Uri              = 'https://dev.azure.com/{0}/{1}/_apis/wit/workitems/{2}?api-version={3}' -f $Organization, $ProjectName, $createdWorkItem.Id, $ApiVersion
        Method           = "DELETE"
    }

    #
    # Delete the Work Item Tag

    try
    {
        # Invoke the Azure DevOps REST API to delete the temporary work item (the tags it
        # created persist project-wide).
        $deleteResult = Invoke-AzDevOpsApiRestMethod @DeleteWIPTagParams
    }
    catch
    {
        Write-Error "[New-WITTag] Failed to delete Work Item Tags for the Azure DevOps Project $ProjectName. Error: $_"
        return $null
    }

    # Azure DevOps has no create-tag API; tags are created only as a side effect of the
    # work item above, and the /wit/tags collection is eventually consistent. Without
    # waiting for the new tags to become listable, a Set that has returned can be followed
    # immediately by a Test that still reads a stale, partial list and reports
    # not-in-desired-state - which is intermittent for a single tag and reliable for
    # several at once. Confirm the tags are observable before returning, with a small,
    # time-boxed retry so a genuinely failed creation still surfaces quickly.
    $deadline = (Get-Date).AddSeconds(30)
    $missing = @($WorkItemTrackingNames)
    do
    {
        $listedNames = @((List-WITTags -Organization $Organization -ProjectName $ProjectName).name)
        $missing = @($WorkItemTrackingNames | Where-Object { $_ -notin $listedNames })
        if ($missing.Count -eq 0) { break }
        Start-Sleep -Seconds 2
    }
    while ((Get-Date) -lt $deadline)

    if ($missing.Count -ne 0)
    {
        Write-Warning "[New-WITTag] Tags not visible via the tags API within the timeout for project '$ProjectName': $($missing -join ', ')"
    }

    return $deleteResult

}