Private/Api/Wit/New-PatchDocument.ps1

function New-PatchDocument {

    <#
        .SYNOPSIS
            Creates a JSON Patch document.

        .DESCRIPTION
            Creates a JSON Patch document.
            The document can be used with New-WorkItem to create and Update-WorkItem to update a work item.

        .PARAMETER SourceWorkItem
            Source work item to update.

        .PARAMETER WorkItemType
            Type of work item to create.
            If specified, overrides the value set by $Properties and $Data.
            Default is 'Task'.

        .PARAMETER Properties
            Properties to copy to update document.
            Default is empty array.

        .PARAMETER Data
            Additional data to add to the patch document.

        .PARAMETER CopyTags
            Flag, whether to copy tags from the source work item.

        .PARAMETER TagsToAdd
            Tags to add to the work item.

        .PARAMETER TagsToRemove
            Tags to remove from the work item.

        .NOTES
            https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/work-items/create?view=azure-devops-rest-5.0&tabs=HTTP
    #>


    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Does not change state, generates a new object.'
    )]
    [CmdletBinding()]
    [OutputType('PSTypeNames.AzureDevOpsApi.ApiWitPatchDocument')]
    param(
        [Alias('Source')]
        $SourceWorkItem,
        $WorkItemType = 'Task',

        [string[]] $Properties = @(),
        [hashtable] $Data = @{},

        [switch] $CopyTags,
        [string[]] $TagsToAdd = @(),
        [string[]] $TagsToRemove = @()
    )

    begin {
        # Set default properties to be copied, if not specified
        if (-not $PSBoundParameters.ContainsKey('Properties')) {
            $Properties = @()
        }
        if ($Properties -notcontains 'System.WorkItemType') {
            $Properties += 'System.WorkItemType'
        }

        # Determine whether tags should be processed;
        # Adjust list of properties to be copied accordingly
        $processTags = -not (-not $TagsToAdd -and -not $TagsToRemove)
        $processTags = ($processTags -and -not $CopyTags.IsPresent) `
            -or ($CopyTags.IsPresent -and $CopyTags -eq $true)
        if ($processTags -and ($Properties -notcontains 'System.Tags')) {
            $Properties += 'System.Tags'
        }
        if (!$processTags -and ($Properties -contains 'System.Tags')) {
            $Properties = $Properties | Where-Object { $_ -ne 'System.Tags' }
        }
    }

    process {

        $document = [PSCustomObject] @{
            PSTypeName   = 'PSTypeNames.AzureDevOpsApi.ApiWitPatchDocument'
            WorkItemUrl  = $null
            WorkItemType = $null
            Operations   = [System.Collections.Generic.List[PSCustomObject]]::new()
        }

        if ($SourceWorkItem) {
            # Get work item URL
            $document.WorkItemUrl = $SourceWorkItem.url

            # Copy properties from source work item
            foreach ($property in $Properties) {

                # Skip properties without values
                if (-not $SourceWorkItem.fields."$($property)") {
                    continue
                }

                # Add property to the patch document
                $document.Operations += [PSCustomObject] @{
                    op    = 'add'
                    path  = "/fields/$($property)"
                    from  = $null
                    value = $SourceWorkItem.fields."$($property)"
                }
            }
        }

        # Add additional data to the patch document;
        # If the key already exists in the patch document,
        # it will be overwritten
        foreach ($key in $Data.Keys) {

            $patchItem = $document.Operations `
            | Where-Object { $_.path -eq "/fields/$($key)" } `
            | Select-Object -First 1

            if (!$patchItem) {
                $patchItem = [PSCustomObject] @{
                    op    = 'add'
                    path  = "/fields/$($key)"
                    from  = $null
                    value = $null
                }
                $document.Operations += $patchItem
            }

            $patchItem.value = $Data[$key]
        }

        # Process tags
        if ($processTags) {
            Update-PatchDocumentTags `
                -Document $document `
                -Add $TagsToAdd `
                -Remove $TagsToRemove `
        }

        # Add work item type to the patch document
        $workItemTypePatchItem = $document.Operations `
        | Where-Object { $_.path -eq '/fields/System.WorkItemType' } `
        | Select-Object -First 1

        if (-not $workItemTypePatchItem) {
            $workItemTypePatchItem = [PSCustomObject] @{
                op    = 'add'
                path  = '/fields/System.WorkItemType'
                from  = $null
                value = $null
            }
            $document.Operations += $workItemTypePatchItem
        }

        if (-not $WorkItemType) {
            $WorkItemType = $workItemTypePatchItem.value
            if (-not $WorkItemType) {
                $WorkItemType = 'Task'
            }
        }

        ## Adjust work item type
        $document.WorkItemType = $WorkItemType
        $workItemTypePatchItem.value = $WorkItemType

        # Return the patch document
        return $document
    }

}