JiraAgilePS.psm1

#region Dependencies
# Load the ConfluencePS namespace from C#
if (!("AtlassianPS.JiraAgilePS.Board" -as [Type])) {
    Add-Type -Path (Join-Path $PSScriptRoot JiraAgilePS.Types.cs) -ReferencedAssemblies Microsoft.CSharp, Microsoft.PowerShell.Commands.Utility, System.Management.Automation, System.Text.RegularExpressions, System.Runtime.Extensions, System.Collections
}

if (!("ArgumentCompleter" -as [Type])) {
    Add-Type -Path (Join-Path $PSScriptRoot JiraAgilePS.Attributes.cs) -ReferencedAssemblies Microsoft.CSharp, Microsoft.PowerShell.Commands.Utility, System.Management.Automation
}
#endregion Dependencies
#region Configuration
[UInt32]$script:DefaultPageSize = 25
$PSDefaultParameterValues = @{
    Disabled                        = $false
    'ConvertTo-Json:Compress'       = $true
    'ConvertTo-Json:EnumsAsStrings' = $true
}
#endregion Configuration
function ConvertTo-Board {
    <#
    .SYNOPSIS
        Converts Jira Agile board payloads to Board objects.
 
    .DESCRIPTION
        Selects the board properties used by JiraAgilePS and casts each
        pipeline input object to [AtlassianPS.JiraAgilePS.Board].
    #>

    [CmdletBinding()]
    [OutputType( [AtlassianPS.JiraAgilePS.Board] )]
    param(
        [Parameter( ValueFromPipeline )]
        [PSObject[]]
        $InputObject
    )

    process {
        foreach ($object in $InputObject) {
            Write-Debug "[$($MyInvocation.MyCommand.Name)] Converting `$object to custom object"

            [AtlassianPS.JiraAgilePS.Board](ConvertTo-Hashtable -InputObject ( $object | Select-Object `
                        Id,
                    Name,
                    Type,
                    Self
                )
            )
        }
    }
}


function ConvertTo-BoardConfiguration {
    <#
    .SYNOPSIS
        Converts board configuration responses to typed objects.
 
    .DESCRIPTION
        Clones each response object into a PSCustomObject and applies the
        AtlassianPS.JiraAgilePS.BoardConfiguration typename for formatting
        and downstream processing.
    #>

    [CmdletBinding()]
    [OutputType([PSObject])]
    param(
        [Parameter(ValueFromPipeline)]
        [PSObject[]]
        $InputObject
    )

    process {
        foreach ($object in $InputObject) {
            if ($null -eq $object) {
                continue
            }

            Write-Debug "[$($MyInvocation.MyCommand.Name)] Converting `$InputObject to AtlassianPS.JiraAgilePS.BoardConfiguration"

            $configuration = [PSCustomObject](ConvertTo-Hashtable -InputObject ($object | Select-Object -Property *))
            $configuration.PSObject.TypeNames.Insert(0, "AtlassianPS.JiraAgilePS.BoardConfiguration")

            $configuration
        }
    }
}
function ConvertTo-Epic {
    <#
    .SYNOPSIS
        Converts Jira Agile epic payloads to Epic objects.
 
    .DESCRIPTION
        Maps API response fields to [AtlassianPS.JiraAgilePS.Epic], including
        normalization of color values returned as either strings or objects.
    #>

    [CmdletBinding()]
    [OutputType([AtlassianPS.JiraAgilePS.Epic])]
    param(
        [Parameter(ValueFromPipeline)]
        [PSObject[]]
        $InputObject
    )

    process {
        foreach ($object in $InputObject) {
            if ($null -eq $object) {
                continue
            }

            Write-Debug "[$($MyInvocation.MyCommand.Name)] Converting `$InputObject to AtlassianPS.JiraAgilePS.Epic"

            [AtlassianPS.JiraAgilePS.Epic](ConvertTo-Hashtable -InputObject ($object | Select-Object `
                        Id,
                    Key,
                    Name,
                    Summary,
                    @{
                        Name       = 'Color'
                        Expression = {
                            if ($null -eq $object.color) {
                                $null
                            }
                            elseif ($object.color -is [String]) {
                                $object.color
                            }
                            elseif ($object.color.PSObject.Properties['key']) {
                                $object.color.key
                            }
                            elseif ($object.color.PSObject.Properties['name']) {
                                $object.color.name
                            }
                            else {
                                [String]$object.color
                            }
                        }
                    },
                    @{ Name = 'Done'; Expression = { [bool]$object.done } },
                    Self
                )
            )
        }
    }
}
function ConvertTo-HashTable {
    <#
    .SYNOPSIS
        Converts a PSCustomObject to Hashtable
 
    .DESCRIPTION
        PowerShell v4 on Windows 8.1 seems to have trouble casting [PSCustomObject] to custom classes.
        This function is a workaround, as casting from [Hashtable] is no problem.
    #>

    [CmdletBinding()]
    [OutputType( [Hashtable] )]
    param(
        # Object to convert
        [Parameter( Mandatory )]
        [PSCustomObject]
        $InputObject
    )

    begin {
        $hash = @{ }
        $InputObject.PSObject.Properties | Foreach-Object {
            $hash[$_.Name] = $_.Value
        }
        $hash
    }
}
function ConvertTo-Issue {
    <#
    .SYNOPSIS
        Converts Jira issue payloads to typed Issue objects.
 
    .DESCRIPTION
        Copies all properties from each issue response object and applies the
        AtlassianPS.JiraAgilePS.Issue typename to preserve rich output typing.
    #>

    [CmdletBinding()]
    [OutputType([PSObject])]
    param(
        [Parameter(ValueFromPipeline)]
        [PSObject[]]
        $InputObject
    )

    process {
        foreach ($object in $InputObject) {
            if ($null -eq $object) {
                continue
            }

            Write-Debug "[$($MyInvocation.MyCommand.Name)] Converting `$InputObject to AtlassianPS.JiraAgilePS.Issue"

            $issue = [PSCustomObject](ConvertTo-Hashtable -InputObject ($object | Select-Object -Property *))
            $issue.PSObject.TypeNames.Insert(0, "AtlassianPS.JiraAgilePS.Issue")

            $issue
        }
    }
}
function ConvertTo-JiraAgileDateString {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [DateTime]
        $Date
    )

    process {
        $Date.ToString("yyyy-MM-ddTHH:mm:ss.fffzzz", [Globalization.CultureInfo]::InvariantCulture)
    }
}
function ConvertTo-Sprint {
    <#
    .SYNOPSIS
        Converts Jira Agile sprint payloads to Sprint objects.
 
    .DESCRIPTION
        Projects sprint fields from API responses and normalizes date fields
        before casting to [AtlassianPS.JiraAgilePS.Sprint].
    #>

    [CmdletBinding()]
    [OutputType( [AtlassianPS.JiraAgilePS.Sprint] )]
    param(
        [Parameter( ValueFromPipeline )]
        [PSObject[]]
        $InputObject
    )

    process {
        foreach ($object in $InputObject) {
            Write-Debug "[$($MyInvocation.MyCommand.Name)] Converting `$object to custom object"

            [AtlassianPS.JiraAgilePS.Sprint](ConvertTo-Hashtable -InputObject ( $object | Select-Object `
                        Id,
                    Name,
                    State,
                    @{ Name = 'StartDate'; Expression = { Get-Date -Date ($object.startDate) } },
                    @{ Name = 'EndDate'; Expression = { Get-Date -Date ($object.endDate) } },
                    @{ Name = 'CompleteDate'; Expression = { Get-Date -Date ($object.completeDate) } },
                    OriginBoardId,
                    Goal,
                    Self
                )
            )
        }
    }
}


function Get-AgilePageItem {
    <#
    .SYNOPSIS
        Expands paged Jira Agile API responses to item objects.
 
    .DESCRIPTION
        Unwraps common paged response shapes ('issues' and 'values') and emits
        contained items. If no known paging property exists, returns the input
        object unchanged.
    #>

    [CmdletBinding()]
    [OutputType([PSObject])]
    param(
        [Parameter(ValueFromPipeline)]
        [PSObject[]]
        $InputObject
    )

    process {
        foreach ($object in $InputObject) {
            if ($null -eq $object) {
                continue
            }

            $issuesProperty = $object.PSObject.Properties['issues']
            if ($issuesProperty) {
                Write-Debug "[$($MyInvocation.MyCommand.Name)] Expanding 'issues' property from paged response"
                foreach ($issue in @($issuesProperty.Value)) {
                    $issue
                }
                continue
            }

            $valuesProperty = $object.PSObject.Properties['values']
            if ($valuesProperty) {
                Write-Debug "[$($MyInvocation.MyCommand.Name)] Expanding 'values' property from paged response"
                foreach ($value in @($valuesProperty.Value)) {
                    $value
                }
                continue
            }

            Write-Debug "[$($MyInvocation.MyCommand.Name)] Returning input object without page expansion"
            $object
        }
    }
}
function ThrowError {
    <#
    .SYNOPSIS
        Utility to throw a terminating errorrecord
    .NOTES
        Thanks to Jaykul:
        https://github.com/PoshCode/Configuration/blob/master/Source/Metadata.psm1
    #>

    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCmdlet]
        $Cmdlet = $((Get-Variable -Scope 1 PSCmdlet).Value),

        [Parameter( Position = 1, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = "ExistingException" )]
        [Parameter( ParameterSetName = "NewException" )]
        [ValidateNotNullOrEmpty()]
        [System.Exception]
        $Exception,

        [Parameter( Position = 2, ParameterSetName = "NewException" )]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ExceptionType = "System.Management.Automation.RuntimeException",

        [Parameter( Position = 3, Mandatory, ParameterSetName = "NewException" )]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Message,

        [Parameter()]
        [System.Object]
        $TargetObject,

        [Parameter( Position = 10, Mandatory, ParameterSetName = "ExistingException" )]
        [Parameter( Position = 10, Mandatory, ParameterSetName = "NewException" )]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ErrorId,

        [Parameter( Position = 11, Mandatory, ParameterSetName = "ExistingException" )]
        [Parameter( Position = 11, Mandatory, ParameterSetName = "NewException" )]
        [ValidateNotNull()]
        [System.Management.Automation.ErrorCategory]
        $Category,

        [Parameter( Position = 1, Mandatory, ParameterSetName = "Rethrow" )]
        [System.Management.Automation.ErrorRecord]$ErrorRecord
    )

    process {
        if (-not $ErrorRecord) {
            if ($PSCmdlet.ParameterSetName -eq "NewException") {
                if ($Exception) {
                    $Exception = New-Object $ExceptionType $Message, $Exception
                }
                else {
                    $Exception = New-Object $ExceptionType $Message
                }
            }
            $errorRecord = New-Object System.Management.Automation.ErrorRecord $Exception, $ErrorId, $Category, $TargetObject
        }
        $Cmdlet.ThrowTerminatingError($errorRecord)
    }
}
function Write-DebugMessage {
    <#
    .SYNOPSIS
        Writes debug messages even when debug output is not enabled globally.
 
    .DESCRIPTION
        Temporarily sets DebugPreference to Continue for the pipeline scope so
        helper/debug tracing is emitted consistently, then restores the
        original preference in the end block.
    #>

    [CmdletBinding()]
    param(
        [Parameter( ValueFromPipeline )]
        [String]
        $Message
    )

    begin {
        $oldDebugPreference = $DebugPreference
        if (-not ($DebugPreference -eq "SilentlyContinue")) {
            $DebugPreference = 'Continue'
        }
    }

    process {
        Write-Debug $Message
    }

    end {
        $DebugPreference = $oldDebugPreference
    }
}
function WriteError {
    <#
    .SYNOPSIS
        Utility to write an errorrecord to the errstd
    .NOTES
        Thanks to Jaykul:
        https://github.com/PoshCode/Configuration/blob/master/Source/Metadata.psm1
    #>

    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCmdlet]
        $Cmdlet = $((Get-Variable -Scope 1 PSCmdlet).Value),

        [Parameter( Position = 1, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = "ExistingException" )]
        [Parameter( ParameterSetName = "NewException" )]
        [ValidateNotNullOrEmpty()]
        [System.Exception]
        $Exception,

        [Parameter( Position = 2, ParameterSetName = "NewException" )]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ExceptionType = "System.Management.Automation.RuntimeException",

        [Parameter( Position = 3, Mandatory, ParameterSetName = "NewException" )]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Message,

        [Parameter()]
        [System.Object]
        $TargetObject,

        [Parameter( Position = 10, Mandatory )]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ErrorId,

        [Parameter( Position = 11, Mandatory )]
        [ValidateNotNull()]
        [System.Management.Automation.ErrorCategory]
        $Category,

        [Parameter( Position = 1, Mandatory, ParameterSetName = "Rethrow" )]
        [System.Management.Automation.ErrorRecord]$ErrorRecord
    )

    process {
        if (-not $ErrorRecord) {
            if ($PSCmdlet.ParameterSetName -eq "NewException") {
                if ($Exception) {
                    $Exception = New-Object $ExceptionType $Message, $Exception
                }
                else {
                    $Exception = New-Object $ExceptionType $Message
                }
            }
            $errorRecord = New-Object System.Management.Automation.ErrorRecord $Exception, $ErrorId, $Category, $TargetObject
        }
        $Cmdlet.WriteError($errorRecord)
    }
}
function Add-IssueToSprint {
    # .ExternalHelp ..\JiraAgilePS-help.xml
    [CmdletBinding( SupportsPaging )]
    [OutputType( [void] )]
    param(
        [Parameter( Position = 0, Mandatory, ValueFromPipeline )]
        <# Waiting on JiraPS v3.0 : [AtlassianPS.JiraPS.Issue[]] #>
        $Issue,

        [Parameter( Mandatory )]
        [AtlassianPS.JiraAgilePS.Sprint]
        $Sprint,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"

        if (-not $Sprint.Self) {
            $Sprint = Get-Sprint -Sprint $Sprint -Credential $Credential -ErrorAction Stop
        }

        $issuesToProcess = New-Object -TypeName System.Collections.ArrayList
    }

    process {
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)"

        foreach ($_issue in $Issue) {
            $null = $issuesToProcess.Add($_issue)
        }
    }

    end {
        while ($issuesToProcess.Count -gt 0) {
            $thisPageSize = if ($issuesToProcess.Count -lt 50) { $issuesToProcess.Count } else { 50 }
            $thisIssuePage = @($issuesToProcess | Select-Object -First $thisPageSize)

            $requestParameter = @{
                Uri        = "$($Sprint.Self)/issue"
                Method     = "POST"
                Body       = ConvertTo-Json @{
                    issues = @($thisIssuePage.Key) # TODO: pass Issue object with JiraPS v3.0
                    # "rankBeforeIssue": "<string>",
                    # "rankAfterIssue": "<string>",
                    # "rankCustomFieldId": 2154
                }
                Credential = $Credential
                Cmdlet     = $PSCmdlet
                Verbose    = $VerbosePreference
                Debug      = $DebugPreference
            }
            Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
            Invoke-JiraMethod @requestParameter

            $issuesToProcess.RemoveRange(0, $thisPageSize)
        }

        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete"
    }
}
function Get-Board {
    # .ExternalHelp ..\JiraAgilePS-help.xml
    [CmdletBinding( SupportsPaging, DefaultParameterSetName = '_All' )]
    [OutputType( [AtlassianPS.JiraAgilePS.Board] )]
    param(
        [Parameter( Position = 0, Mandatory, ValueFromPipeline, ParameterSetName = '_Search' )]
        [UInt64[]]
        $BoardId,

        [Parameter()]
        [ValidateRange(1, 4294967295)]
        [UInt32]$PageSize = $script:DefaultPageSize,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"

        $server = Get-JiraConfigServer -ErrorAction Stop

        $resourceUrl = "$server/rest/agile/1.0/board"
    }

    process {
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)"

        $requestParameter = @{
            Method       = "GET"
            GetParameter = @{ maxResults = $PageSize }
            Credential   = $Credential
            Cmdlet       = $PSCmdlet
            Verbose      = $VerbosePreference
            Debug        = $DebugPreference
        }

        switch ($PSCmdlet.ParameterSetName) {
            '_All' {
                $requestParameter['Uri'] = $resourceUrl
                $requestParameter['Paging'] = $true
                # Paging
                ($PSCmdlet.PagingParameters | Get-Member -MemberType Property).Name | ForEach-Object {
                    $requestParameter[$_] = $PSCmdlet.PagingParameters.$_
                }

                Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
                Invoke-JiraMethod @requestParameter | ConvertTo-Board
            }
            '_Search' {
                foreach ($_boardId in $BoardId) {
                    Write-Verbose "[$($MyInvocation.MyCommand.Name)] Processing [$_boardId]"
                    Write-Debug "[$($MyInvocation.MyCommand.Name)] Processing `$_boardId [$_boardId]"

                    $requestParameter['Uri'] = "$resourceUrl/$_boardId"
                    $requestParameter['Paging'] = $false

                    Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
                    Invoke-JiraMethod @requestParameter | ConvertTo-Board
                }
            }
        }
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete"
    }
}
function Get-BoardConfiguration {
    # .ExternalHelp ..\JiraAgilePS-help.xml
    [CmdletBinding()]
    [OutputType([PSObject])]
    param(
        [Parameter(Position = 0, Mandatory, ValueFromPipeline)]
        [AtlassianPS.JiraAgilePS.Board]
        $Board,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"

        $server = Get-JiraConfigServer -ErrorAction Stop
        $resourceUrl = "$server/rest/agile/1.0/board/{0}/configuration"
    }

    process {
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)"
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Processing [$($Board.Id)]"
        Write-Debug "[$($MyInvocation.MyCommand.Name)] Processing `$Board [$($Board.Id)]"

        $requestParameter = @{
            Uri        = $resourceUrl -f $Board.Id
            Method     = "GET"
            Credential = $Credential
            Cmdlet     = $PSCmdlet
            Verbose    = $VerbosePreference
            Debug      = $DebugPreference
        }

        Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
        Invoke-JiraMethod @requestParameter | ConvertTo-BoardConfiguration
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete"
    }
}
function Get-Epic {
    # .ExternalHelp ..\JiraAgilePS-help.xml
    [CmdletBinding(SupportsPaging, DefaultParameterSetName = '_ById')]
    [OutputType([AtlassianPS.JiraAgilePS.Epic])]
    param(
        [Parameter(Position = 0, Mandatory, ValueFromPipeline, ParameterSetName = '_ById')]
        [AtlassianPS.JiraAgilePS.Epic[]]
        $Epic,

        [Parameter(Position = 0, Mandatory, ValueFromPipeline, ParameterSetName = '_ByBoard')]
        [AtlassianPS.JiraAgilePS.Board]
        $Board,

        [Parameter(ParameterSetName = '_ByBoard')]
        [ValidateRange(1, 4294967295)]
        [UInt32]$PageSize = $script:DefaultPageSize,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"

        $server = Get-JiraConfigServer -ErrorAction Stop
        $resourceUrl_ById = "$server/rest/agile/1.0/epic/{0}"
        $resourceUrl_ByBoard = "$server/rest/agile/1.0/board/{0}/epic"
    }

    process {
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)"

        switch ($PSCmdlet.ParameterSetName) {
            '_ById' {
                foreach ($_epic in $Epic) {
                    if ($_epic.Id -eq 0) {
                        throw "[$($MyInvocation.MyCommand.Name)] Epic input must contain a non-zero Id."
                    }

                    Write-Verbose "[$($MyInvocation.MyCommand.Name)] Processing [$($_epic.Id)]"
                    Write-Debug "[$($MyInvocation.MyCommand.Name)] Processing `$_epic [$($_epic.Id)]"

                    $requestParameter = @{
                        Uri        = $resourceUrl_ById -f $_epic.Id
                        Method     = "GET"
                        Credential = $Credential
                        Cmdlet     = $PSCmdlet
                        Verbose    = $VerbosePreference
                        Debug      = $DebugPreference
                    }

                    Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
                    Invoke-JiraMethod @requestParameter | ConvertTo-Epic
                }
            }
            '_ByBoard' {
                if ($Board.Id -eq 0) {
                    throw "[$($MyInvocation.MyCommand.Name)] Board input must contain a non-zero Id."
                }

                Write-Verbose "[$($MyInvocation.MyCommand.Name)] Processing Board ID [$($Board.Id)]"
                Write-Debug "[$($MyInvocation.MyCommand.Name)] Processing `$Board [$($Board.Id)]"

                $requestParameter = @{
                    Uri          = $resourceUrl_ByBoard -f $Board.Id
                    Method       = "GET"
                    GetParameter = @{
                        maxResults = $PageSize
                    }
                    Paging       = $true
                    Credential   = $Credential
                    Cmdlet       = $PSCmdlet
                    Verbose      = $VerbosePreference
                    Debug        = $DebugPreference
                }

                # Paging
                ($PSCmdlet.PagingParameters | Get-Member -MemberType Property).Name | ForEach-Object {
                    $requestParameter[$_] = $PSCmdlet.PagingParameters.$_
                }

                Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
                Invoke-JiraMethod @requestParameter | Get-AgilePageItem | ConvertTo-Epic
            }
        }
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete"
    }
}
function Get-Issue {
    # .ExternalHelp ..\JiraAgilePS-help.xml
    [CmdletBinding(SupportsPaging, DefaultParameterSetName = '_Board')]
    [OutputType([PSObject])]
    param(
        [Parameter(Position = 0, Mandatory, ValueFromPipeline, ParameterSetName = '_Board')]
        [Parameter(Position = 0, Mandatory, ValueFromPipeline, ParameterSetName = '_Backlog')]
        [Parameter(Position = 0, Mandatory, ValueFromPipeline, ParameterSetName = '_Sprint')]
        [Parameter(Position = 0, Mandatory, ValueFromPipeline, ParameterSetName = '_BoardEpic')]
        [Parameter(Position = 0, Mandatory, ValueFromPipeline, ParameterSetName = '_BoardWithoutEpic')]
        [AtlassianPS.JiraAgilePS.Board]
        $Board,

        [Parameter(Mandatory, ParameterSetName = '_Backlog')]
        [switch]
        $Backlog,

        [Parameter(Position = 1, Mandatory, ValueFromPipeline, ParameterSetName = '_Sprint')]
        [AtlassianPS.JiraAgilePS.Sprint[]]
        $Sprint,

        [Parameter(Position = 0, Mandatory, ValueFromPipeline, ParameterSetName = '_Epic')]
        [Parameter(Position = 1, Mandatory, ValueFromPipeline, ParameterSetName = '_BoardEpic')]
        [AtlassianPS.JiraAgilePS.Epic[]]
        $Epic,

        [Parameter(Mandatory, ParameterSetName = '_BoardWithoutEpic')]
        [switch]
        $WithoutEpic,

        [Parameter()]
        [ValidateRange(1, 4294967295)]
        [UInt32]$PageSize = $script:DefaultPageSize,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"

        $server = Get-JiraConfigServer -ErrorAction Stop
        $resourceUrl_Board = "$server/rest/agile/1.0/board/{0}/issue"
        $resourceUrl_Backlog = "$server/rest/agile/1.0/board/{0}/backlog"
        $resourceUrl_Sprint = "$server/rest/agile/1.0/board/{0}/sprint/{1}/issue"
        $resourceUrl_Epic = "$server/rest/agile/1.0/epic/{0}/issue"
        $resourceUrl_BoardEpic = "$server/rest/agile/1.0/board/{0}/epic/{1}/issue"
        $resourceUrl_BoardWithoutEpic = "$server/rest/agile/1.0/board/{0}/epic/none/issue"
    }

    process {
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)"

        $requestParameter = @{
            Method       = "GET"
            GetParameter = @{
                maxResults = $PageSize
            }
            Paging       = $true
            Credential   = $Credential
            Cmdlet       = $PSCmdlet
            Verbose      = $VerbosePreference
            Debug        = $DebugPreference
        }

        # Paging
        ($PSCmdlet.PagingParameters | Get-Member -MemberType Property).Name | ForEach-Object {
            $requestParameter[$_] = $PSCmdlet.PagingParameters.$_
        }

        switch ($PSCmdlet.ParameterSetName) {
            '_Board' {
                if ($Board.Id -eq 0) {
                    throw "[$($MyInvocation.MyCommand.Name)] Board input must contain a non-zero Id."
                }

                Write-Verbose "[$($MyInvocation.MyCommand.Name)] Processing Board ID [$($Board.Id)]"
                Write-Debug "[$($MyInvocation.MyCommand.Name)] Processing `$Board [$($Board.Id)]"

                $requestParameter["Uri"] = $resourceUrl_Board -f $Board.Id
                Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
                Invoke-JiraMethod @requestParameter | Get-AgilePageItem | ConvertTo-Issue
            }
            '_Backlog' {
                if ($Board.Id -eq 0) {
                    throw "[$($MyInvocation.MyCommand.Name)] Board input must contain a non-zero Id."
                }

                Write-Verbose "[$($MyInvocation.MyCommand.Name)] Processing Board ID [$($Board.Id)] backlog"
                Write-Debug "[$($MyInvocation.MyCommand.Name)] Processing `$Board [$($Board.Id)]"

                $requestParameter["Uri"] = $resourceUrl_Backlog -f $Board.Id
                Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
                Invoke-JiraMethod @requestParameter | Get-AgilePageItem | ConvertTo-Issue
            }
            '_Sprint' {
                foreach ($_sprint in $Sprint) {
                    if ($Board.Id -eq 0) {
                        throw "[$($MyInvocation.MyCommand.Name)] Board input must contain a non-zero Id."
                    }
                    if ($_sprint.Id -eq 0) {
                        throw "[$($MyInvocation.MyCommand.Name)] Sprint input must contain a non-zero Id."
                    }

                    Write-Verbose "[$($MyInvocation.MyCommand.Name)] Processing Sprint ID [$($_sprint.Id)] for Board ID [$($Board.Id)]"
                    Write-Debug "[$($MyInvocation.MyCommand.Name)] Processing `$_sprint [$($_sprint.Id)]"

                    $requestParameter["Uri"] = $resourceUrl_Sprint -f $Board.Id, $_sprint.Id
                    Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
                    Invoke-JiraMethod @requestParameter | Get-AgilePageItem | ConvertTo-Issue
                }
            }
            '_Epic' {
                foreach ($_epic in $Epic) {
                    if ($_epic.Id -eq 0) {
                        throw "[$($MyInvocation.MyCommand.Name)] Epic input must contain a non-zero Id."
                    }

                    Write-Verbose "[$($MyInvocation.MyCommand.Name)] Processing Epic ID [$($_epic.Id)]"
                    Write-Debug "[$($MyInvocation.MyCommand.Name)] Processing `$_epic [$($_epic.Id)]"

                    $requestParameter["Uri"] = $resourceUrl_Epic -f $_epic.Id
                    Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
                    Invoke-JiraMethod @requestParameter | Get-AgilePageItem | ConvertTo-Issue
                }
            }
            '_BoardEpic' {
                foreach ($_epic in $Epic) {
                    if ($Board.Id -eq 0) {
                        throw "[$($MyInvocation.MyCommand.Name)] Board input must contain a non-zero Id."
                    }
                    if ($_epic.Id -eq 0) {
                        throw "[$($MyInvocation.MyCommand.Name)] Epic input must contain a non-zero Id."
                    }

                    Write-Verbose "[$($MyInvocation.MyCommand.Name)] Processing Epic ID [$($_epic.Id)] for Board ID [$($Board.Id)]"
                    Write-Debug "[$($MyInvocation.MyCommand.Name)] Processing `$_epic [$($_epic.Id)]"

                    $requestParameter["Uri"] = $resourceUrl_BoardEpic -f $Board.Id, $_epic.Id
                    Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
                    Invoke-JiraMethod @requestParameter | Get-AgilePageItem | ConvertTo-Issue
                }
            }
            '_BoardWithoutEpic' {
                if ($Board.Id -eq 0) {
                    throw "[$($MyInvocation.MyCommand.Name)] Board input must contain a non-zero Id."
                }

                Write-Verbose "[$($MyInvocation.MyCommand.Name)] Processing Board ID [$($Board.Id)] with no epic"
                Write-Debug "[$($MyInvocation.MyCommand.Name)] Processing `$Board [$($Board.Id)]"

                $requestParameter["Uri"] = $resourceUrl_BoardWithoutEpic -f $Board.Id
                Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
                Invoke-JiraMethod @requestParameter | Get-AgilePageItem | ConvertTo-Issue
            }
        }
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete"
    }
}
function Get-Sprint {
    # .ExternalHelp ..\JiraAgilePS-help.xml
    [CmdletBinding( SupportsPaging, DefaultParameterSetName = '_All' )]
    [OutputType( [AtlassianPS.JiraAgilePS.Sprint] )]
    param(
        [Parameter( Position = 0, Mandatory, ValueFromPipeline, ParameterSetName = '_ById' )]
        [AtlassianPS.JiraAgilePS.Sprint[]]
        $Sprint,


        [Parameter( Position, Mandatory, ValueFromPipeline, ParameterSetName = '_All' )]
        [AtlassianPS.JiraAgilePS.Board]
        $Board,

        [Parameter( ParameterSetName = '_All' )]
        [AtlassianPS.JiraAgilePS.SprintState]
        $State,

        [Parameter( ParameterSetName = '_All' )]
        [ValidateRange(1, 4294967295)]
        [UInt32]$PageSize = $script:DefaultPageSize,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"

        $server = Get-JiraConfigServer -ErrorAction Stop

        $resourceUrl_All = "$server/rest/agile/1.0/board/{0}/sprint"
        $resourceUrl_ById = "$server/rest/agile/1.0/sprint/{0}"
    }

    process {
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)"

        $requestParameter = @{
            Method     = "GET"
            Credential = $Credential
            Cmdlet     = $PSCmdlet
            Verbose    = $VerbosePreference
            Debug      = $DebugPreference
        }

        switch ($PSCmdlet.ParameterSetName) {
            '_All' {
                $requestParameter["Uri"] = $resourceUrl_All -f $Board.Id
                $requestParameter["GetParameter"] = @{
                    maxResults = $PageSize
                    state      = $State
                }
                $requestParameter["Paging"] = $true

                # Paging
                ($PSCmdlet.PagingParameters | Get-Member -MemberType Property).Name | ForEach-Object {
                    $requestParameter[$_] = $PSCmdlet.PagingParameters.$_
                }

                Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
                Invoke-JiraMethod @requestParameter | ConvertTo-Sprint
            }
            '_ById' {
                foreach ($_sprint in $Sprint) {
                    $requestParameter["Uri"] = $resourceUrl_ById -f $_sprint.Id
                    $requestParameter["GetParameter"] = @{ }
                    $requestParameter["Paging"] = $false

                    Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
                    Invoke-JiraMethod @requestParameter | ConvertTo-Sprint
                }
            }
        }

    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete"
    }
}
function Move-IssueToBacklog {
    # .ExternalHelp ..\JiraAgilePS-help.xml
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([void])]
    param(
        [Parameter(Position = 0, Mandatory, ValueFromPipeline)]
        $Issue,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"

        $server = Get-JiraConfigServer -ErrorAction Stop
        $resourceUrl = "$server/rest/agile/1.0/backlog/issue"
        $issuesToProcess = New-Object -TypeName System.Collections.ArrayList
    }

    process {
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)"

        foreach ($_issue in $Issue) {
            $null = $issuesToProcess.Add($_issue)
        }
    }

    end {
        while ($issuesToProcess.Count -gt 0) {
            $thisPageSize = if ($issuesToProcess.Count -lt 50) { $issuesToProcess.Count } else { 50 }
            $thisIssuePage = @($issuesToProcess | Select-Object -First $thisPageSize)
            $issueKeys = @(
                foreach ($_issue in $thisIssuePage) {
                    if ($_issue.PSObject.Properties.Name -contains 'Key') {
                        $_issue.Key
                    }
                    else {
                        [string]$_issue
                    }
                }
            )

            if ($PSCmdlet.ShouldProcess(($issueKeys -join ', '), 'Move issues to Jira Agile backlog')) {
                $requestParameter = @{
                    Uri        = $resourceUrl
                    Method     = "POST"
                    Body       = ConvertTo-Json @{ issues = @($issueKeys) }
                    Credential = $Credential
                    Cmdlet     = $PSCmdlet
                    Verbose    = $VerbosePreference
                    Debug      = $DebugPreference
                }
                Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
                Invoke-JiraMethod @requestParameter
            }

            $issuesToProcess.RemoveRange(0, $thisPageSize)
        }

        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete"
    }
}
function New-Sprint {
    # .ExternalHelp ..\JiraAgilePS-help.xml
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([AtlassianPS.JiraAgilePS.Sprint])]
    param(
        [Parameter(Mandatory)]
        [string]
        $Name,

        [Parameter(Position = 0, Mandatory, ValueFromPipeline)]
        [AtlassianPS.JiraAgilePS.Board]
        $Board,

        [Parameter()]
        [DateTime]
        $StartDate,

        [Parameter()]
        [DateTime]
        $EndDate,

        [Parameter()]
        [string]
        $Goal,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"

        $server = Get-JiraConfigServer -ErrorAction Stop
        $resourceUrl = "$server/rest/agile/1.0/sprint"
    }

    process {
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)"

        if ($Board.Id -eq 0) {
            throw "[$($MyInvocation.MyCommand.Name)] Board input must contain a non-zero Id."
        }

        $body = @{
            name          = $Name
            originBoardId = $Board.Id
        }
        if ($PSBoundParameters.ContainsKey('StartDate')) { $body['startDate'] = ConvertTo-JiraAgileDateString $StartDate }
        if ($PSBoundParameters.ContainsKey('EndDate')) { $body['endDate'] = ConvertTo-JiraAgileDateString $EndDate }
        if ($PSBoundParameters.ContainsKey('Goal')) { $body['goal'] = $Goal }

        if ($PSCmdlet.ShouldProcess($Name, "Create Jira Agile sprint on board $($Board.Id)")) {
            $requestParameter = @{
                Uri        = $resourceUrl
                Method     = "POST"
                Body       = ConvertTo-Json $body
                Credential = $Credential
                Cmdlet     = $PSCmdlet
                Verbose    = $VerbosePreference
                Debug      = $DebugPreference
            }

            Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
            Invoke-JiraMethod @requestParameter | ConvertTo-Sprint
        }
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete"
    }
}
function Remove-Sprint {
    # .ExternalHelp ..\JiraAgilePS-help.xml
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    [OutputType([void])]
    param(
        [Parameter(Position = 0, Mandatory, ValueFromPipeline)]
        [AtlassianPS.JiraAgilePS.Sprint[]]
        $Sprint,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"

        $server = Get-JiraConfigServer -ErrorAction Stop
        $resourceUrl = "$server/rest/agile/1.0/sprint/{0}"
    }

    process {
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)"

        foreach ($_sprint in $Sprint) {
            if ($_sprint.Id -eq 0) {
                throw "[$($MyInvocation.MyCommand.Name)] Sprint input must contain a non-zero Id."
            }

            if ($PSCmdlet.ShouldProcess("Sprint $($_sprint.Id)", 'Delete Jira Agile sprint')) {
                $requestParameter = @{
                    Uri        = $resourceUrl -f $_sprint.Id
                    Method     = "DELETE"
                    Credential = $Credential
                    Cmdlet     = $PSCmdlet
                    Verbose    = $VerbosePreference
                    Debug      = $DebugPreference
                }

                Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
                Invoke-JiraMethod @requestParameter
            }
        }
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete"
    }
}
function Set-Sprint {
    # .ExternalHelp ..\JiraAgilePS-help.xml
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([AtlassianPS.JiraAgilePS.Sprint])]
    param(
        [Parameter(Position = 0, Mandatory, ValueFromPipeline)]
        [AtlassianPS.JiraAgilePS.Sprint[]]
        $Sprint,

        [Parameter()]
        [string]
        $Name,

        [Parameter()]
        [AtlassianPS.JiraAgilePS.SprintState]
        $State,

        [Parameter()]
        [DateTime]
        $StartDate,

        [Parameter()]
        [DateTime]
        $EndDate,

        [Parameter()]
        [string]
        $Goal,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"

        $server = Get-JiraConfigServer -ErrorAction Stop
        $resourceUrl = "$server/rest/agile/1.0/sprint/{0}"
    }

    process {
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)"
        Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)"

        foreach ($_sprint in $Sprint) {
            if ($_sprint.Id -eq 0) {
                throw "[$($MyInvocation.MyCommand.Name)] Sprint input must contain a non-zero Id."
            }

            $body = @{ }
            if ($PSBoundParameters.ContainsKey('Name')) { $body['name'] = $Name }
            if ($PSBoundParameters.ContainsKey('State')) { $body['state'] = $State.ToString() }
            if ($PSBoundParameters.ContainsKey('StartDate')) { $body['startDate'] = ConvertTo-JiraAgileDateString $StartDate }
            if ($PSBoundParameters.ContainsKey('EndDate')) { $body['endDate'] = ConvertTo-JiraAgileDateString $EndDate }
            if ($PSBoundParameters.ContainsKey('Goal')) { $body['goal'] = $Goal }

            if ($PSCmdlet.ShouldProcess("Sprint $($_sprint.Id)", 'Update Jira Agile sprint')) {
                $requestParameter = @{
                    Uri        = $resourceUrl -f $_sprint.Id
                    Method     = "POST"
                    Body       = ConvertTo-Json $body
                    Credential = $Credential
                    Cmdlet     = $PSCmdlet
                    Verbose    = $VerbosePreference
                    Debug      = $DebugPreference
                }

                Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking JiraMethod with `$requestParameter"
                Invoke-JiraMethod @requestParameter | ConvertTo-Sprint
            }
        }
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete"
    }
}