AzureDevOpsIngest.psm1

#Requires -Module SimplySQL

# Module Constants

# GUID used to identify the connection names by SimplySQL
Set-Variable ConnectionNames -option Constant -value (@{
    Automatic = (New-Guid)
    Manual = (New-Guid)
})
Function ConvertTo-AuthorizationHeader {
<#
.SYNOPSIS
    Convert a PSCredential object, containing the user's Personal Access Token, to an Authorization header suitable for Azure DevOps REST API
 
.PARAMETER Credential
    The PSCredential object to convert
    Note: The username is ignored
 
.EXAMPLE
    ConvertTo-AuthorizationHeader -Credential 'Personal Access Token'
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory, Position=0)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential
)
Process {
    @{
        Authorization = "Basic $([Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($Credential.GetNetworkCredential().Password)")))"
    } | Write-Output
}
}
Function Join-Hashtable {
<#
    .SYNOPSIS
        Join two hashtables
 
    .PARAMETER LeftHandSide
        The first hashtable
 
    .PARAMETER RightHandSide
        The second hashtable
 
    .PARAMETER Force
        If set, the keys in the RightHandSide will overwrite the keys in the LeftHandSide
        Otherwise an exception is raised in duplicated keys are found
 
    .EXAMPLE
        $LeftHandSide = @{
            Key1 = 'Value1'
            Key2 = 'Value2'
        }
        $RightHandSide = @{
            Key2 = 'Value3'
            Key3 = 'Value4'
        }
        Join-Hashtable -LeftHandSide $LeftHandSide -RightHandSide $RightHandSide
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory, Position=0)]
    [hashtable]
    $LeftHandSide,

    [Parameter(Mandatory = $false, Position=1)]
    [AllowNull()]
    [hashtable]
    $RightHandSide,

    [Parameter(Mandatory = $false)]
    [switch]
    $Force
)
Process {
    $Output = @{}
    foreach($k in $LeftHandSide.Keys) {
        $Output += @{$k = $LeftHandSide.$k}
    }
    if($null -ne $RightHandSide) {
        foreach($k in $RightHandSide.Keys) {
            if($Output.Keys -icontains $k) {
                if($Force) {
                    $Output.$k = $RightHandSide.$k
                    Write-Debug "Hashtable already contained the key '$k' with value '$($LeftHandSide.$k)', it was overwritten by '$($RightHandSide.$k)'."
                } else {
                    throw "Hashtable already contains the key '$k', the value '$($RightHandSide.$k)' would be ignored."
                }
            } else {
                $Output += @{$k = $RightHandSide.$k}
            }
        }
    }
    $Output | Write-Output
}
}
Function Join-Uri {
<#
    .SYNOPSIS
        Join two URIs and validate the result is within the scope of the Base URI
 
    .PARAMETER BaseUri
        The Base URI. Generated URI are guaranteed to be within this scope
 
    .PARAMETER RelativeUri
        An URI relative to the BaseUri
 
    .PARAMETER AbsoluteUri
        An absolute URI
 
    .PARAMETER QueryParameters
        The hashtable containing the query parameters to append to the URI
 
    .EXAMPLE
        Join-Uri -BaseUri 'https://dev.azure.com/EESC-CoR/' -RelativeUri 'MyProject/_apis/wit/workitems' -QueryParameters @{'ids' = '1,2,3'}
 
    .EXAMPLE
        Join-Uri -BaseUri 'https://dev.azure.com/EESC-CoR/' -AbsoluteUri 'https://dev.azure.com/EESC-CoR/MyProject/_apis/wit/workitems' -QueryParameters @{'ids' = '1,2,3'}
#>

[CmdletBinding(DefaultParameterSetName='relative')]
[OutputType([System.Uri])]
param(
    [Parameter(Mandatory)]
    [System.Uri]
    $BaseUri,

    [Parameter(Mandatory,ParameterSetName='relative')]
    [string]
    [ValidatePattern("^[^/]")]
    $RelativeUri,

    [Parameter(Mandatory,ParameterSetName='absolute')]
    [System.Uri]
    $AbsoluteUri,

    [Parameter(Mandatory=$false)]
    [AllowNull()]
    [hashtable]
    $QueryParameters = @{}
)
Process {
    $UserUri = "http://test.com#Uri_TryCreate"

    # Get the target's Absolute Uri, append Parameters, and Validate the generated URI
    $TempUserAbsoluteUri = $UserUri
    if($AbsoluteUri) {
        $TempUserAbsoluteUri = $AbsoluteUri.AbsoluteUri
    } else {
        $TempUserAbsoluteUri = "$($BaseUri.AbsoluteUri)$($RelativeUri)"
    }
    # Craft the query string
    $ExtraQueryString = ($QueryParameters.GetEnumerator() | Sort-Object -Property Key | Foreach-Object {
        "$([uri]::EscapeDataString($_.Key))=$([uri]::EscapeDataString($_.Value))"
    }) -join '&'
    if($ExtraQueryString) {
        if($TempUserAbsoluteUri -match '\?') {
            $TempUserAbsoluteUri = "$($TempUserAbsoluteUri)&$($ExtraQueryString)"
        } else {
            $TempUserAbsoluteUri = "$($TempUserAbsoluteUri)?$($ExtraQueryString)"
        }
    }
    if( -not ([System.Uri]::TryCreate($BaseUri, $TempUserAbsoluteUri, [ref]$UserUri))) {
        throw "URI $UserUri is invalid."
    }
    if( -not $BaseUri.IsBaseOf($UserUri)) {
        throw "URI $UserUri is out of scope."
    }
    $UserUri | Write-Output
}
}
Function Get-Project {
<#
    .SYNOPSIS
        Get the list of projects from Azure DevOps.
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER Organization (optional)
        The Azure DevOps organization
        Default: EESC-CoR
 
    .PARAMETER ApiVersion (optional)
        The Azure DevOps API version
        Default: 7.1
 
    .PARAMETER Expand (optional)
        If set, the cmdlet will return the details of each project by calling Invoke-Api on each project's url
 
    .PARAMETER ProjectId
        The project's ID
        If set, the cmdlet will return the details of the project with the given ID
        If omitted, the cmdlet will return the list of all projects
 
    .EXAMPLE
        # Get all the projects of the organization
        Get-Project -Credential $AzureCredential
 
    .EXAMPLE
        # Get all the details of all the projects (by calling Invoke-Api on each project's url)
        Get-Project -Credential $AzureCredential | Invoke-Api -Credential $AzureCredential
#>

[CmdletBinding(DefaultParameterSetName = 'List')]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory = $false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1',

    [Parameter(Mandatory,ParameterSetName = 'Single',ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [ValidateScript({
        [guid]::TryParse($_, $([ref][guid]::Empty))
    })]
    [Alias('id')]
    [string]
    $ProjectId,

    [Parameter(Mandatory=$false,ParameterSetName = 'List')]
    [switch]
    $Expand
)
Process {
    if ($PSCmdlet.ParameterSetName -eq 'Single') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -Project -ProjectId $ProjectId
    } elseif ($PSCmdlet.ParameterSetName -eq 'List') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -Project
    } else {
        throw 'Invalid parameter set'
    }

    Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -AbsoluteUri $AbsoluteUrl |
        Foreach-Object {
            if($PSCmdlet.ParameterSetName -eq 'Single' ) {
                $_ | Write-Output
            } elseif($PSCmdlet.ParameterSetName -eq 'List' ) {
                $_.Value | ForEach-Object {
                    if($Expand) {
                        Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -AbsoluteUri $_.url | Write-Output
                    } else {
                        $_ | Write-Output
                    }
                }
            } else {
                throw 'Invalid parameter set'
            }
        }
}
}
Function Get-ProjectBuild {
<#
    .SYNOPSIS
        Get the Builds for a project.
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER ProjectId
        The project's ID
 
    .PARAMETER BuildId
        The Build's ID
 
    .PARAMETER Organization (optional)
        The Azure DevOps organization
        Default: EESC-CoR
 
    .PARAMETER ApiVersion (optional)
        The Azure DevOps API version
        Default: 7.1
#>

[CmdletBinding(DefaultParameterSetName = 'List')]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory = $false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1',

    [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [ValidateScript({
        [guid]::TryParse($_, $([ref][guid]::Empty))
    })]
    [Alias('id')]
    [string]
    $ProjectId,

    [Parameter(Mandatory,ParameterSetName = 'Single')]
    [ValidateScript({
        [int]::TryParse($_, $([ref][int]::Empty))
    })]
    [string]
    $BuildId

)
Process {
    if ($PSCmdlet.ParameterSetName -eq 'Single') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -Build -BuildId $BuildId
    } elseif ($PSCmdlet.ParameterSetName -eq 'List') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -Build
    } else {
        throw 'Invalid parameter set'
    }
    Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -AbsoluteUri $AbsoluteUrl |
        ForEach-Object {
            if($PSCmdlet.ParameterSetName -eq 'Single') {
                $_ | Write-Output
            } else {
                $_.Value | ForEach-Object {
                    $_ | Write-Output
                }
            }
        }
}
}
Function Get-ProjectBuildArtifact {
<#
    .SYNOPSIS
        Get the Artifacts for a Build for a project.
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER ProjectId
        The project's ID
 
    .PARAMETER BuildId
        The Build's ID
 
    .PARAMETER ArtifactName
        The Artifact's ID
 
    .PARAMETER Fast
        If set, the cmdlet will download the artifact in zip format directly
        If not set, the cmdlet will download the artifact's description in json format, then use the contained url to download the zip file
 
    .PARAMETER Organization (optional)
        The Azure DevOps organization
        Default: EESC-CoR
 
    .PARAMETER ApiVersion (optional)
        The Azure DevOps API version
        Default: 7.1
#>

[CmdletBinding(DefaultParameterSetName = 'List')]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory = $false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1',

    [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [ValidateScript({
        [guid]::TryParse($_, $([ref][guid]::Empty))
    })]
    [Alias('id')]
    [string]
    $ProjectId,

    [Parameter(Mandatory)]
    [ValidateScript({
        [int]::TryParse($_, $([ref][int]::Empty))
    })]
    [string]
    $BuildId,

    [Parameter(Mandatory,ParameterSetName = 'Single')]
    [string]
    $ArtifactName,

    [Parameter(Mandatory=$false,ParameterSetName = 'Single')]
    [switch]
    $Fast,

    [Parameter(Mandatory=$false,ParameterSetName = 'Single')]
    [switch]
    $ReturnContent

)
Process {
    $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -BuildId $BuildId -Artifact

    if ($PSCmdlet.ParameterSetName -eq 'Single') {
        $Target = New-TemporaryFile -ErrorAction Stop
        if($Fast) {
            <# # Option 1 (seems to be faswter)
                # Requests the CONTENT in zip format. And save to disk
            #>

            $QueryParameters = @{ 
                artifactName = $ArtifactName
                '$format' = 'zip'
            }
            Invoke-ApiRaw -Credential $Credential -AbsoluteUri $AbsoluteUrl -QueryParameters $QueryParameters -OutFile ($Target)
        } else {
            <# # Option 2 (the default)
                # Requests a DESCRIPTION of the artifact in JSON, then download it
                #>

                $QueryParameters = @{ 
                    artifactName = $ArtifactName
                    '$format' = 'json'      # Optional: $format can be omitted in this case
                }
                $Descriptor = Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -AbsoluteUri $AbsoluteUrl -QueryParameters $QueryParameters
                Invoke-ApiRaw -Credential $Credential -AbsoluteUri $Descriptor.resource.downloadUrl -Method Get -OutFile ($Target)
        }
        if($ReturnContent) {
            $TargetExpand = "$($env:TEMP)\$($Target.BaseName)"
            $TargetExpandExists = Test-Path $TargetExpand
            if($TargetExpandExists) {
                "The target directory already exists: $TargetExpand" | Write-Warning
                $Target | Write-Output
            } else {
                try {
                    # Attempt to expand the archive and to read the content of the single file
                    Expand-Archive -Path $Target.FullName -DestinationPath $TargetExpand -ErrorAction Stop
                    $TargetFiles = Get-ChildItem -Path $TargetExpand -Recurse -File -ErrorAction Stop
                    if($TargetFiles.Count -eq 1) {
                        $TargetFiles | Get-Content -ErrorAction Stop | Write-Output
                        # Remove the downloaded file
                        Remove-Item -Path $Target.FullName -ErrorAction Continue
                    } else {
                        "The archive contains several files!" | Write-Warning
                        Get-Content -Path $Target | Write-Output
                    }
                } catch {
                    if(Test-Path $TargetExpand) {
                        # Remove the expanded directory, if exists
                        Remove-Item -Path $TargetExpand -Recurse -ErrorAction SilentlyContinue
                    }
                    $Target | Write-Output
                }
            }
        } else {
            $Target | Write-Output
        }
    } elseif ($PSCmdlet.ParameterSetName -eq 'List') {
        Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -AbsoluteUri $AbsoluteUrl |
            Select-Object -ExpandProperty value | Write-Output
    } else {
        throw 'Invalid parameter set'
    }
}
}
Function Get-ProjectBuildDefinition {
<#
    .SYNOPSIS
        Get the Build Definitions for a project.
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER ProjectId
        The project's ID
 
    .PARAMETER BuildDefinitionId
        The Build Definition's ID
 
    .PARAMETER Organization (optional)
        The Azure DevOps organization
        Default: EESC-CoR
 
    .PARAMETER ApiVersion (optional)
        The Azure DevOps API version
        Default: 7.1
#>

[CmdletBinding(DefaultParameterSetName = 'List')]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory = $false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1',

    [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [ValidateScript({
        [guid]::TryParse($_, $([ref][guid]::Empty))
    })]
    [Alias('id')]
    [string]
    $ProjectId,

    [Parameter(Mandatory,ParameterSetName = 'Single')]
    [ValidateScript({
        [int]::TryParse($_, $([ref][int]::Empty))
    })]
    [string]
    $BuildDefinitionId

)
Process {
    if ($PSCmdlet.ParameterSetName -eq 'Single') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -BuildDefinition -BuildDefinitionId $BuildDefinitionId
    } elseif ($PSCmdlet.ParameterSetName -eq 'List') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -BuildDefinition
    } else {
        throw 'Invalid parameter set'
    }
    Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -AbsoluteUri $AbsoluteUrl |
        ForEach-Object {
            if($PSCmdlet.ParameterSetName -eq 'Single') {
                $_ | Write-Output
            } else {
                $_.Value | ForEach-Object {
                    $_ | Write-Output
                }
            }
        }
}
}
Function Get-ProjectBuildLog {
<#
    .SYNOPSIS
        Get the Logs of a Build for a project.
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER ProjectId
        The project's ID
 
    .PARAMETER BuildId
        The Build's ID
 
    .PARAMETER LogId
        The Log's ID
 
    .PARAMETER Organization (optional)
        The Azure DevOps organization
        Default: EESC-CoR
 
    .PARAMETER ApiVersion (optional)
        The Azure DevOps API version
        Default: 7.1
#>

[CmdletBinding(DefaultParameterSetName = 'List')]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory = $false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1',

    [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [ValidateScript({
        [guid]::TryParse($_, $([ref][guid]::Empty))
    })]
    [Alias('id')]
    [string]
    $ProjectId,

    [Parameter(Mandatory)]
    [ValidateScript({
        [int]::TryParse($_, $([ref][int]::Empty))
    })]
    [string]
    $BuildId,

    [Parameter(Mandatory,ParameterSetName = 'Single')]
    [ValidateScript({
        [int]::TryParse($_, $([ref][int]::Empty))
    })]
    [string]
    $LogId

)
Process {
    if ($PSCmdlet.ParameterSetName -eq 'Single') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -BuildId $BuildId -Build -Log -LogId $LogId
    } elseif ($PSCmdlet.ParameterSetName -eq 'List') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -BuildId $BuildId -Build -Log
    } else {
        throw 'Invalid parameter set'
    }
    Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -AbsoluteUri $AbsoluteUrl |
        ForEach-Object {
            if($PSCmdlet.ParameterSetName -eq 'Single') {
                $_ | Write-Output
            } else {
                $_.Value | ForEach-Object {
                    $_ | Write-Output
                }
            }
        }
}
}
Function Get-ProjectMetrics {
<#
    .SYNOPSIS
        Get the Build Metrics for a project.
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER ProjectId
        The project's ID
 
    .PARAMETER Organization (optional)
        The Azure DevOps organization
        Default: EESC-CoR
 
    .PARAMETER ApiVersion (optional)
        The Azure DevOps API version
        Default: 7.1-preview
#>

[CmdletBinding(DefaultParameterSetName = 'List')]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory = $false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1-preview',

    [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [ValidateScript({
        [guid]::TryParse($_, $([ref][guid]::Empty))
    })]
    [Alias('id')]
    [string]
    $ProjectId

)
Process {
    if ($PSCmdlet.ParameterSetName -eq 'List') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -BuildMetrics
    } else {
        throw 'Invalid parameter set'
    }
    Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -AbsoluteUri $AbsoluteUrl |
        Select-Object -ExpandProperty Value |
        ForEach-Object {
            $_ | Write-Output
        }
}
}
Function Get-ProjectPipeline {
<#
    .SYNOPSIS
        Get the pipelines for a project.
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER Organization (optional)
        The Azure DevOps organization
        Default: EESC-CoR
 
    .PARAMETER ApiVersion (optional)
        The Azure DevOps API version
        Default: 7.1
 
    .PARAMETER ProjectId
        The project's ID
 
    .PARAMETER Expand (optional)
        If set, the cmdlet will return the details of each pipeline by calling Invoke-Api on each pipeline's url
 
    .PARAMETER PipelineId
        The pipeline's ID
 
    .EXAMPLE
        # Get a specific project's pipelines
        Get-ProjectPipeline -Credential 'Personal Access Token' -id 'MyProject'
 
    .EXAMPLE
        # Get all the pipelines for the first project
        Get-Project -Credential $AzureCredential | Select-Object -First 1 & Get-ProjectPipeline -Credential $AzureCredential
#>

[CmdletBinding(DefaultParameterSetName = 'List')]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory = $false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1',

    [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [ValidateScript({
        [guid]::TryParse($_, $([ref][guid]::Empty))
    })]
    [Alias('id')]
    [string]
    $ProjectId,

    [Parameter(Mandatory,ParameterSetName = 'Single')]
    [ValidateScript({
        [int]::TryParse($_, $([ref][int]::Empty))
    })]
    [string]
    $PipelineId,

    [Parameter(Mandatory=$false,ParameterSetName = 'List')]
    [switch]
    $Expand,

    [Parameter(Mandatory=$false)]
    [switch]
    $GetYaml

)
Process {
    if ($PSCmdlet.ParameterSetName -eq 'Single') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -Pipeline -PipelineId $PipelineId
    } elseif ($PSCmdlet.ParameterSetName -eq 'List') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -Pipeline
    } else {
        throw 'Invalid parameter set'
    }
    Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -AbsoluteUri $AbsoluteUrl |
        ForEach-Object {
            if($PSCmdlet.ParameterSetName -eq 'Single') {
                $_ | Write-Output
            } else {
                $_.Value | ForEach-Object {
                    if($Expand) {
                        Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -AbsoluteUri $_.url | Write-Output
                    } else {
                        $_ | Write-Output
                    }
                }
            }
        }
}
}
Function Get-ProjectPipelineRun {
<#
    .SYNOPSIS
        Get the Runs for a project pipeline.
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER ProjectId
        The project's ID
 
    .PARAMETER PipelineId
        The pipeline's ID
 
    .PARAMETER RunId
        The run ID
 
    .PARAMETER Organization (optional)
        The Azure DevOps organization
        Default: EESC-CoR
 
    .PARAMETER ApiVersion (optional)
        The Azure DevOps API version
        Default: 7.1
#>

[CmdletBinding(DefaultParameterSetName = 'List')]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory = $false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1',

    [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [ValidateScript({
        [guid]::TryParse($_, $([ref][guid]::Empty))
    })]
    [Alias('id')]
    [string]
    $ProjectId,

    [Parameter(Mandatory)]
    [ValidateScript({
        [int]::TryParse($_, $([ref][int]::Empty))
    })]
    [string]
    $PipelineId,

    [Parameter(Mandatory,ParameterSetName = 'Single')]
    [ValidateScript({
        [int]::TryParse($_, $([ref][int]::Empty))
    })]
    [string]
    $RunId
)
Process {
    if ($PSCmdlet.ParameterSetName -eq 'Single') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -PipelineId $PipelineId -Run -RunId $RunId
    } elseif ($PSCmdlet.ParameterSetName -eq 'List') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -PipelineId $PipelineId -Run
    } else {
        throw 'Invalid parameter set'
    }
    Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -AbsoluteUri $AbsoluteUrl |
        ForEach-Object {
            if($PSCmdlet.ParameterSetName -eq 'Single') {
                $_ | Write-Output
            } else {
                $_.Value | ForEach-Object {
                    $_ | Write-Output
                }
            }
        }
}
}
Function Get-ProjectPipelineRunLog {
<#
    .SYNOPSIS
        Get the Runs for a project pipeline.
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER ProjectId
        The project's ID
 
    .PARAMETER PipelineId
        The pipeline's ID
 
    .PARAMETER RunId
        The run ID
 
    .PARAMETER LogId
        The Log ID
 
    .PARAMETER GetContent
        If set, the cmdlet will return the content of the log
 
    .PARAMETER Organization (optional)
        The Azure DevOps organization
        Default: EESC-CoR
 
    .PARAMETER ApiVersion (optional)
        The Azure DevOps API version
        Default: 7.1
#>

[CmdletBinding(DefaultParameterSetName = 'List')]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory = $false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1',

    [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [ValidateScript({
        [guid]::TryParse($_, $([ref][guid]::Empty))
    })]
    [Alias('id')]
    [string]
    $ProjectId,

    [Parameter(Mandatory)]
    [ValidateScript({
        [int]::TryParse($_, $([ref][int]::Empty))
    })]
    [string]
    $PipelineId,

    [Parameter(Mandatory)]
    [ValidateScript({
        [int]::TryParse($_, $([ref][int]::Empty))
    })]
    [string]
    $RunId,

    [Parameter(Mandatory,ParameterSetName = 'Single')]
    [ValidateScript({
        [int]::TryParse($_, $([ref][int]::Empty))
    })]
    [string]
    $LogId

    <#
    # Removing -GetContent
    # Instead, Always fetch the log's content
    # This is to align with the behaviour of Build Logs (where the content is retrieved instead of a json object)
    #
    # To re-enable, modify the following piece of code inside the Process{} block:
    # if($PSCmdlet.ParameterSetName -eq 'Single') {
    # [CODE]
    # } else {
    # by:
    # if($PSCmdlet.ParameterSetName -eq 'Single') { # = Unchanged
    # if($GetContent) { # < Added
    # [CODE] # = Unchanged
    # } else { # < Added
    # $_ | Write-Output # < Added
    # } # < Added
    # } else { # = Unchanged
    [Parameter(Mandatory=$false,ParameterSetName = 'Single')]
    [switch]
    $GetContent
    #>


)
Process {
    if ($PSCmdlet.ParameterSetName -eq 'Single') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -PipelineId $PipelineId -RunId $RunId -Log -LogId $LogId
    } elseif ($PSCmdlet.ParameterSetName -eq 'List') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -PipelineId $PipelineId -RunId $RunId -Log
    } else {
        throw 'Invalid parameter set'
    }
    Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -AbsoluteUri $AbsoluteUrl |
        ForEach-Object {
            if($PSCmdlet.ParameterSetName -eq 'Single') {
                # Retrieve a Temporary and Public URL for the log
                Invoke-Api -AbsoluteUri $_.url -QueryParameters @{'$expand' = 'signedContent'} -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion | Foreach-Object {
                    # Then retrieve the log's content using that public URI
                    Invoke-WebRequest $_.signedContent.url | Select-Object -ExpandProperty Content | Write-Output
                }
            } else {
                $_.logs | ForEach-Object {
                    $_ | Write-Output
                }
            }
        }
}
}
Function Get-ProjectRepository {
<#
    .SYNOPSIS
        Get the repositories for a project.
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER ProjectId
        The project's ID
 
    .PARAMETER RepositoryId
        The repository's ID
 
    .PARAMETER Organization (optional)
        The Azure DevOps organization
        Default: EESC-CoR
 
    .PARAMETER ApiVersion (optional)
        The Azure DevOps API version
        Default: 7.1
 
    .EXAMPLE
        # Get a specific project's repositories
        Get-ProjectRepository -Credential 'Personal Access Token' -id 'MyProject'
 
    .EXAMPLE
        # Get all the repositories for the first project
        Get-Project -Credential $AzureCredential | Select-Object -First 1 | Get-ProjectRepository -Credential $AzureCredential
 
#>

[CmdletBinding(DefaultParameterSetName = 'List')]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory = $false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1',

    [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [ValidateScript({
        [guid]::TryParse($_, $([ref][guid]::Empty))
    })]
    [Alias('id')]
    [string]
    $ProjectId,

    [Parameter(Mandatory,ParameterSetName = 'Single')]
    [ValidateScript({
        [guid]::TryParse($_, $([ref][guid]::Empty))
    })]
    [string]
    $RepositoryId
)
Process {
    if ($PSCmdlet.ParameterSetName -eq 'Single') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -Repository -RepositoryId $RepositoryId
    } elseif ($PSCmdlet.ParameterSetName -eq 'List') {
        $AbsoluteUrl = Resolve-ApiUri -Organization $Organization -ProjectId $ProjectId -Repository
    } else {
        throw 'Invalid parameter set'
    }
    Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -AbsoluteUri $AbsoluteUrl |
        ForEach-Object {
            if($PSCmdlet.ParameterSetName -eq 'Single') {
                $_ | Write-Output
            } else {
                $_.Value | ForEach-Object {
                    $_ | Write-Output
                }
            }
        }
}
}
Function Get-RepositoryItem {
<#
    .SYNOPSIS
        Get the pipelines for a project.
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER Organization (optional)
        The Azure DevOps organization
        Default: EESC-CoR
 
    .PARAMETER ApiVersion (optional)
        The Azure DevOps API version
        Default: 7.1
 
    .PARAMETER ProjectId
        The project's ID
 
    .PARAMETER RepositoryId
        The repository ID
 
    .PARAMETER Path
        The path to the item in the repository
#>

[CmdletBinding()]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory = $false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1',

    [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [ValidateScript({
        [guid]::TryParse($_, $([ref][guid]::Empty))
    })]
    [Alias('id')]
    [string]
    $ProjectId,

    [Parameter(Mandatory)]
    [ValidateScript({
        [guid]::TryParse($_, $([ref][guid]::Empty))
    })]
    [string]
    $RepositoryId,

    [Parameter(Mandatory)]
    [string]
    $Path

)
Process {
    Get-ProjectRepository -ProjectId $ProjectId -RepositoryId $RepositoryId -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion |
        Select-Object -ExpandProperty _links |
        Select-Object -ExpandProperty items |
        Select-Object -ExpandProperty href |
        ForEach-Object {
            $QueryString = @{
                path = $Path
            }
            try {
                Invoke-Api -AbsoluteUri $_ -QueryParameters $QueryString -Credential $Credential -ErrorAction Stop | Write-Output
            } catch {
                if(Test-Json -Json $_.Exception.Message -ErrorAction SilentlyContinue) {
                    $_.Exception.Message | ConvertFrom-Json | Select-Object -ExpandProperty message | Write-Error
                } else {
                    $_.Exception.Message | Write-Error
                }
            }
        }
}
}
Function Invoke-Api {
<#
    .SYNOPSIS
        Invoke the Azure DevOps REST API
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER RelativeUri
        The REST API's endpoint URI, relative to the the Azure DevOps API's root
 
    .PARAMETER AbsoluteUri
        The absolute REST API's endpoint URI
 
    .PARAMETER QueryParameters
        The hashtable containing the query parameters to append to the URI
 
    .PARAMETER HttpHeaders
        The hashtable containing the HTTP headers to append to the request
 
    .PARAMETER Method
        The HTTP method to use
        Default: Get
 
    .PARAMETER Organization
        The Azure DevOps organization
        Default: EESC-CoR
 
    .PARAMETER ApiVersion
        The Azure DevOps API version
        Default: 7.1
 
    .PARAMETER ContentType
        The content type of the request
        Default: application/json
 
    .EXAMPLE
        Invoke-Api -Credential 'Personal Access Token' -RelativeUri 'MyProject/_apis/wit/workitems' -QueryParameters @{'ids' = '1,2,3'}
 
    .EXAMPLE
        Invoke-Api -Credential 'Personal Access Token' -AbsoluteUri 'https://dev.azure.com/EESC-CoR/MyProject/_apis/wit/workitems' -QueryParameters @{'ids' = '1,2,3'}
 
    .EXAMPLE
        # Get all the details of all the repositories for all the projects
        Get-Project -Credential $AzureCredential | Get-ProjectRepository -Credential $AzureCredential | Invoke-Api -Credential $AzureCredential
 
#>

[CmdletBinding(DefaultParameterSetName='absolute')]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory,ParameterSetName='relative')]
    [string]
    [ValidatePattern("^[^/]")]
    $RelativeUri = '',

    [Parameter(Mandatory,ParameterSetName='absolute',ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [Alias('url')]
    [System.Uri]
    $AbsoluteUri = '',

    [Parameter(Mandatory=$false)]
    [AllowNull()]
    [hashtable]
    $QueryParameters = @{},

    [Parameter(Mandatory=$false)]
    [AllowNull()]
    [hashtable]
    $HttpHeaders = @{},

    [Parameter(Mandatory = $false)]
    [Microsoft.PowerShell.Commands.WebRequestMethod]
    $Method = [Microsoft.PowerShell.Commands.WebRequestMethod]::Get,

    [Parameter(Mandatory = $false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1',

    [Parameter(Mandatory = $false)]
    [string]
    $ContentType = 'application/json'
)
Process {
    $BaseUri = [System.Uri]"https://dev.azure.com/$($Organization)/"
    $QueryParametersWithApiVersion = (Join-Hashtable -LeftHandSide @{'api-version' = $ApiVersion} -RightHandSide $QueryParameters)
    if($RelativeUri) {
        $UserUri = Join-Uri -BaseUri $BaseUri -RelativeUri $RelativeUri -QueryParameters $QueryParametersWithApiVersion
    } else {
        $UserUri = Join-Uri -BaseUri $BaseUri -AbsoluteUri $AbsoluteUri.AbsoluteUri -QueryParameters $QueryParametersWithApiVersion
    }

    "Calling Azure DevOps API with Uri [$UserUri]" | Write-Debug
    $Headers = Join-Hashtable -LeftHandSide (ConvertTo-AuthorizationHeader -Credential $Credential) -RightHandSide $HttpHeaders

    $Response = Invoke-RestMethod -Method $Method -Uri $UserUri -ContentType $ContentType -Headers $Headers
    $Response | Write-Output
}
}
Function Invoke-ApiRaw {
<#
    .SYNOPSIS
        Invoke the Azure DevOps REST API, using a raw AbsoluteUri, not performing any transformation or validation
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER AbsoluteUri
        The absolute REST API's endpoint URI
 
    .PARAMETER QueryParameters
        The hashtable containing the query parameters to append to the URI
 
    .PARAMETER HttpHeaders
        The hashtable containing the HTTP headers to append to the request
 
    .PARAMETER Method
        The HTTP method to use
        Default: Get
 
    .EXAMPLE
        Invoke-Api -Credential 'Personal Access Token' -AbsoluteUri 'https://dev.azure.com/EESC-CoR/MyProject/_apis/wit/workitems' -QueryParameters @{'ids' = '1,2,3'}
#>

[CmdletBinding()]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,
    
    [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [Alias('url')]
    [System.Uri]
    $AbsoluteUri,

    [Parameter(Mandatory=$false)]
    [AllowNull()]
    [hashtable]
    $QueryParameters = @{},

    [Parameter(Mandatory=$false)]
    [AllowNull()]
    [hashtable]
    $HttpHeaders = @{},

    [Parameter(Mandatory = $false)]
    [Microsoft.PowerShell.Commands.WebRequestMethod]
    $Method = [Microsoft.PowerShell.Commands.WebRequestMethod]::Get,
    
    [Parameter(Mandatory = $false)]
    [AllowNull()]
    [string]
    $ContentType = $null,

    [Parameter(Mandatory = $false)]
    [string]
    $OutFile = $null
)
Process {
    $QueryString = ($QueryParameters.GetEnumerator() | Sort-Object -Property Key | Foreach-Object {
        "$([uri]::EscapeDataString($_.Key))=$([uri]::EscapeDataString($_.Value))"
    }) -join '&'
    if($AbsoluteUri.AbsoluteUri -match '\?') {
        $UserUri = "$($AbsoluteUri.AbsoluteUri)&$($QueryString)"
    } else {
        $UserUri = "$($AbsoluteUri.AbsoluteUri)?$($QueryString)"
    }

    "Calling Azure DevOps API with Uri [$UserUri]" | Write-Debug
    $Headers = ConvertTo-AuthorizationHeader -Credential $Credential

    $RestParameters = @{
        Method = $Method
        Uri = $UserUri
        Headers = $Headers
    }
    if($ContentType) {
        $RestParameters.Add('ContentType', $ContentType)
    }
    if($OutFile) {
        $RestParameters.Add('OutFile', $OutFile)
    }
    Invoke-RestMethod @RestParameters | Write-Output
}
}
Function Invoke-TestResultApi {
<#
    .SYNOPSIS
        Invoke the Azure DevOps REST API for Test Results
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER RelativeUri
        The REST API's endpoint URI, relative to the the Azure DevOps API's root
 
    .PARAMETER AbsoluteUri
        The absolute REST API's endpoint URI
 
    .PARAMETER QueryParameters
        The hashtable containing the query parameters to append to the URI
 
    .PARAMETER HttpHeaders
        The hashtable containing the HTTP headers to append to the request
 
    .PARAMETER Method
        The HTTP method to use
        Default: Get
 
    .PARAMETER Organization
        The Azure DevOps organization
        Default: EESC-CoR
 
    .PARAMETER ApiVersion
        The Azure DevOps API version
        Default: 7.1
 
    .PARAMETER ContentType
        The content type of the request
        Default: application/json
 
    .EXAMPLE
        Invoke-TestResultApi -Credential 'Personal Access Token' -RelativeUri 'MyProject/_apis/testresults/runs'
 
#>

[CmdletBinding(DefaultParameterSetName='absolute')]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory,ParameterSetName='relative')]
    [string]
    [ValidatePattern("^[^/]")]
    $RelativeUri = '',

    [Parameter(Mandatory,ParameterSetName='absolute',ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [Alias('url')]
    [System.Uri]
    $AbsoluteUri = '',

    [Parameter(Mandatory=$false)]
    [AllowNull()]
    [hashtable]
    $QueryParameters = @{},

    [Parameter(Mandatory=$false)]
    [AllowNull()]
    [hashtable]
    $HttpHeaders = @{},

    [Parameter(Mandatory = $false)]
    [Microsoft.PowerShell.Commands.WebRequestMethod]
    $Method = [Microsoft.PowerShell.Commands.WebRequestMethod]::Get,

    [Parameter(Mandatory = $false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1-preview.1',

    [Parameter(Mandatory = $false)]
    [string]
    $ContentType = 'application/json'
)
Process {
    $BaseUri = [System.Uri]"https://vstmr.dev.azure.com/$($Organization)/"
    $QueryParametersWithApiVersion = (Join-Hashtable -LeftHandSide @{'api-version' = $ApiVersion} -RightHandSide $QueryParameters)
    if($RelativeUri) {
        $UserUri = Join-Uri -BaseUri $BaseUri -RelativeUri $RelativeUri -QueryParameters $QueryParametersWithApiVersion
    } else {
        $UserUri = Join-Uri -BaseUri $BaseUri -AbsoluteUri $AbsoluteUri.AbsoluteUri -QueryParameters $QueryParametersWithApiVersion
    }

    "Calling Azure DevOps Test Result API with Uri [$UserUri]" | Write-Debug
    $Headers = Join-Hashtable -LeftHandSide (ConvertTo-AuthorizationHeader -Credential $Credential) -RightHandSide $HttpHeaders

    $Response = Invoke-RestMethod -Method $Method -Uri $UserUri -ContentType $ContentType -Headers $Headers
    $Response | Write-Output
}
}
Function Read-DevOps {
<#
.SYNOPSIS
    Reads Azure DevOps' Projects, Repositories and Pipelines
.PARAMETER Credential
    The Personal Access Token in the form of a PSCredential object
    Note: The username is ignored
.EXAMPLE
    # Reads Azure DevOps' Projects, Repositories and Pipelines
    Read-DevOps -Credential $PAT
#>

[CmdletBinding()]
[OutputType([PSCustomObject[]])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    [Alias('AzureDevOpsCredential','PAT')]
    $Credential
)
# Load the list of Projects and their Repositories and Pipelines
Get-Project -Credential $Credential | Foreach-Object {
    [pscustomobject]@{
        Project = $_
        Pipeline = $_ | Get-ProjectPipeline -Credential $Credential | Invoke-Api -Credential $Credential
        Repository = $_ | Get-ProjectRepository -Credential $Credential | Invoke-Api -Credential $Credential
        Yaml = @()
    }
} | Foreach-Object {    # Load the Yaml for each Pipeline (if any)
    $Project = $_
    $_.Yaml = $Project.Pipeline | Where-Object {
            $_.configuration.type -eq 'yaml'
        } | Foreach-Object {
            $p = $_
            $Project.Repository | Where-Object {
                $_.id -eq $p.configuration.repository.id
            } | Foreach-Object {
                # Prepare the output object (Pipeline, Repository, empty Yaml, empty YamlTemplate list)
                [pscustomobject]@{
                    Pipeline = $p
                    Repository = $_
                    Yaml = $null
                    YamlTemplate = $null
                } | Write-Output
            }
        } | Foreach-Object {
            $ItemUri = $_.Repository._links.items.href
            $ItemPath = $_.Pipeline.configuration.path
            try {
                # Load the Yaml for the Pipeline
                $_.Yaml = Invoke-Api -Credential $Credential -AbsoluteUri $ItemUri -QueryParameters @{path = $ItemPath}
                # Compute the list of imported Templates in the Yaml
                $_.YamlTemplate = ($_.Yaml -split "`n" | Where-Object {$_ -match '^\s*- template:.*?@'}|Foreach-Object{$_ -replace '^.*?@' }|Where-Object{$_ -notmatch 'self'}|Select-Object -Unique)
                $_ | Write-Output
            } catch {
                $_.ToString() | Write-Warning
            }
        }
    $_ | Write-Output
}
}
Function Resolve-ApiUri {
<#
    .SYNOPSIS
 
 
 
#>

[CmdletBinding(DefaultParameterSetName = 'ProjectList')]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory=$false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory=$false)]
    [switch]
    $Relative,

    [Parameter(Mandatory=$false,ParameterSetName = 'ProjectList')]
    [Parameter(Mandatory=$false,ParameterSetName = 'SingleProject')]
    [switch]
    $Project,

    [Parameter(Mandatory,ParameterSetName = 'SingleProject')]
    [Parameter(Mandatory,ParameterSetName = 'RepositoryList')]
    [Parameter(Mandatory,ParameterSetName = 'SingleRepository')]
    [Parameter(Mandatory,ParameterSetName = 'PipelineList')]
    [Parameter(Mandatory,ParameterSetName = 'SinglePipeline')]
    [Parameter(Mandatory,ParameterSetName = 'RunList')]
    [Parameter(Mandatory,ParameterSetName = 'SingleRun')]
    [Parameter(Mandatory,ParameterSetName = 'RunLogList')]
    [Parameter(Mandatory,ParameterSetName = 'SingleRunLog')]
    [Parameter(Mandatory,ParameterSetName = 'BuildList')]
    [Parameter(Mandatory,ParameterSetName = 'SingleBuild')]
    [Parameter(Mandatory,ParameterSetName = 'BuildDefinitionList')]
    [Parameter(Mandatory,ParameterSetName = 'SingleBuildDefinition')]
    [Parameter(Mandatory,ParameterSetName = 'BuildLogList')]
    [Parameter(Mandatory,ParameterSetName = 'SingleBuildLog')]
    [Parameter(Mandatory,ParameterSetName = 'BuildMetrics')]
    [Parameter(Mandatory,ParameterSetName = 'BuildArtifactList')]
    [ValidateScript({
        [guid]::TryParse($_, $([ref][guid]::Empty))
    })]
    [string]
    $ProjectId,

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

    [Parameter(Mandatory,ParameterSetName = 'RepositoryList')]
    [Parameter(Mandatory=$false,ParameterSetName = 'SingleRepository')]
    [switch]
    $Repository,

    [Parameter(Mandatory,ParameterSetName = 'SingleRepository')]
    [ValidateScript({
        [guid]::TryParse($_, $([ref][guid]::Empty))
    })]
    [string]
    $RepositoryId,

    [Parameter(Mandatory,ParameterSetName = 'PipelineList')]
    [Parameter(Mandatory=$false,ParameterSetName = 'SinglePipeline')]
    [switch]
    $Pipeline,

    [Parameter(Mandatory,ParameterSetName = 'SinglePipeline')]
    [Parameter(Mandatory,ParameterSetName = 'RunList')]
    [Parameter(Mandatory,ParameterSetName = 'SingleRun')]
    [Parameter(Mandatory,ParameterSetName = 'RunLogList')]
    [Parameter(Mandatory,ParameterSetName = 'SingleRunLog')]
    [long]
    $PipelineId,

    [Parameter(Mandatory,ParameterSetName = 'RunList')]
    [Parameter(Mandatory=$false,ParameterSetName = 'SingleRun')]
    [Parameter(Mandatory=$false,ParameterSetName = 'RunLogList')]
    [Parameter(Mandatory=$false,ParameterSetName = 'SingleRunLog')]
    [switch]
    $Run,

    [Parameter(Mandatory,ParameterSetName = 'SingleRun')]
    [Parameter(Mandatory,ParameterSetName = 'RunLogList')]
    [Parameter(Mandatory,ParameterSetName = 'SingleRunLog')]
    [long]
    $RunId,

    [Parameter(Mandatory,ParameterSetName = 'BuildList')]
    [Parameter(Mandatory=$false,ParameterSetName = 'SingleBuild')]
    [Parameter(Mandatory=$false,ParameterSetName = 'BuildLogList')]
    [Parameter(Mandatory=$false,ParameterSetName = 'SingleBuildLog')]
    [Parameter(Mandatory=$false,ParameterSetName = 'BuildArtifactList')]
    [switch]
    $Build,

    [Parameter(Mandatory,ParameterSetName = 'SingleBuild')]
    [Parameter(Mandatory,ParameterSetName = 'BuildLogList')]
    [Parameter(Mandatory,ParameterSetName = 'SingleBuildLog')]
    [Parameter(Mandatory,ParameterSetName = 'BuildArtifactList')]
    [long]
    $BuildId,

    [Parameter(Mandatory,ParameterSetName = 'BuildDefinitionList')]
    [Parameter(Mandatory=$false,ParameterSetName = 'SingleBuildDefinition')]
    [switch]
    $BuildDefinition,

    [Parameter(Mandatory,ParameterSetName = 'SingleBuildDefinition')]
    [long]
    $BuildDefinitionId,

    [Parameter(Mandatory,ParameterSetName = 'RunLogList')]
    [Parameter(Mandatory=$false,ParameterSetName = 'SingleRunLog')]
    [Parameter(Mandatory,ParameterSetName = 'BuildLogList')]
    [Parameter(Mandatory=$false,ParameterSetName = 'SingleBuildLog')]
    [switch]
    $Log,

    [Parameter(Mandatory,ParameterSetName = 'SingleRunLog')]
    [Parameter(Mandatory,ParameterSetName = 'SingleBuildLog')]
    [long]
    $LogId,

    [Parameter(Mandatory,ParameterSetName = 'BuildArtifactList')]
    [switch]
    $Artifact
)
Begin {
    $AzureDevOpsUri = @{
        Api = 'https://dev.azure.com'
    }
}
Process {
    $BaseUri = [System.Uri]"$($AzureDevOpsUri.Api)/$($Organization)/"
    $RelativeUri = switch($PSCmdlet.ParameterSetName) {
        'ProjectList' {
            '_apis/projects'
        }
        'SingleProject' {
            "_apis/projects/$($ProjectId)"
        }
        'BuildMetrics' {
            "$($ProjectId)/_apis/build/Metrics"
        }
        'RepositoryList' {
            "$($ProjectId)/_apis/git/repositories"
        }
        'SingleRepository' {
            "$($ProjectId)/_apis/git/repositories/$($RepositoryId)"
        }
        'PipelineList' {
            "$($ProjectId)/_apis/pipelines"
        }
        'SinglePipeline' {
            "$($ProjectId)/_apis/pipelines/$($PipelineId)"
        }
        'RunList' {
            "$($ProjectId)/_apis/pipelines/$($PipelineId)/runs"
        }
        'SingleRun' {
            "$($ProjectId)/_apis/pipelines/$($PipelineId)/runs/$($RunId)"
        }
        'RunLogList' {
            "$($ProjectId)/_apis/pipelines/$($PipelineId)/runs/$($RunId)/logs"
        }
        'SingleRunLog' {
            "$($ProjectId)/_apis/pipelines/$($PipelineId)/runs/$($RunId)/logs/$($LogId)"
        }
        'BuildList' {
            "$($ProjectId)/_apis/build/builds"
        }
        'SingleBuild' {
            "$($ProjectId)/_apis/build/builds/$($BuildId)"
        }
        'BuildDefinitionList' {
            "$($ProjectId)/_apis/build/definitions"
        }
        'SingleBuildDefinition' {
            "$($ProjectId)/_apis/build/definitions/$($BuildDefinitionId)"
        }
        'BuildLogList' {
            "$($ProjectId)/_apis/build/builds/$($BuildId)/logs"
        }
        'SingleBuildLog' {
            "$($ProjectId)/_apis/build/builds/$($BuildId)/logs/$($LogId)"
        }
        'BuildArtifactList' {
            "$($ProjectId)/_apis/build/builds/$($BuildId)/artifacts"
        }
        default {
            throw 'Invalid parameter set'
        }
    }
    if($Relative) {
        $RelativeUri | Write-Output
    } else {
        Join-Uri -BaseUri $BaseUri -RelativeUri $RelativeUri | Select-Object -ExpandProperty AbsoluteUri | Write-Output
    }
}
}
Function Resolve-Project {
<#
    .SYNOPSIS
        Resolve a Project URI, returning its absolute - non aliased - API friendly - URI
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER ProjectUri
        The Project URI to resolve
     
    .PARAMETER Name
        The Project Name to resolve
 
    .PARAMETER Organization (optional)
        The Azure DevOps organization
        Default: EESC-CoR
 
    .PARAMETER ApiVersion (optional)
        The Azure DevOps API version
        Default: 7.1
 
    .EXAMPLE
        # Resolve the Project URIs
        @(
            'https://dev.azure.com/EESC-CoR/_git/MembersPortal?path=/README.md'
            'https://EESC-CoR@dev.azure.com/EESC-CoR/DM2016/_git/Scripts'
            'https://dev.azure.com/EESC-CoR/_git/DM2016'
        ) | Resolve-Repository -Credential $AzureCredential
         
#>

[CmdletBinding(DefaultParameterSetName = 'Uri')]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName,ParameterSetName = 'Uri')]
    [ValidateScript({ 
        # Validate that the URL is a valid Azure DevOps Project URL
        # e.g.: https://dev.azure.com/Organization/ProjectName
        $_.DnsSafeHost -eq 'dev.azure.com' -and $_.LocalPath -match '^/[^/]+/[^/]+/?$'
    })] 
    [Alias('url')]
    [System.Uri]
    $ProjectUri,

    [Parameter(Mandatory,ValueFromPipelineByPropertyName,ParameterSetName = 'Name')]
    [Alias('name')]
    [string]
    $ProjectName,

    [Parameter(Mandatory = $false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1'
)
Process {
    switch($PSCmdlet.ParameterSetName) {
        'Name' {
            $Uri = "https://dev.azure.com/$($Organization)/_apis/projects/$($ProjectName)"
        }
        'Uri' {
            $Uri = $ProjectUri.AbsoluteUri

            # Discard query string arguments
            $Uri = $Uri -replace '\?.*$',''
            # Discard the trailing slash
            $Uri = $Uri -replace '/$',''
            # Rewrite Uri into an request to the Projects API
            $Uri = $Uri -replace "^(https?://.*?/[^/]+)/([^/]+)/?$",'$1/_apis/projects/$2'
        }
        Default {
            throw "Invalid ParameterSetName: $($PSCmdlet.ParameterSetName)"
        }
    }

    "Lookup for a Project matching URL '$($Uri)' in the '$($Organization)' Azure DevOps Organization" | Write-Debug

    Invoke-Api -Credential $Credential -AbsoluteUri $Uri -Organization $Organization -ApiVersion $ApiVersion |    # Get the project details
        Write-Output
}
}
Function Resolve-Repository {
<#
    .SYNOPSIS
        Resolve a Repository URI, returning its absolute - non aliased - API friendly - URI
 
    .PARAMETER Credential
        The Personal Access Token in the form of a PSCredential object
        Note: The username is ignored
 
    .PARAMETER RepositoryUri
        The Repository URI to resolve
 
    .PARAMETER Organization (optional)
        The Azure DevOps organization
        Default: EESC-CoR
 
    .PARAMETER ApiVersion (optional)
        The Azure DevOps API version
        Default: 7.1
 
    .EXAMPLE
        # Resolve the Repository URI
        @(
            'https://dev.azure.com/EESC-CoR/_git/MembersPortal?path=/README.md'
            'https://EESC-CoR@dev.azure.com/EESC-CoR/DM2016/_git/Scripts'
            'https://dev.azure.com/EESC-CoR/_git/DM2016'
        ) | Resolve-Repository -Credential $AzureCredential
         
#>

[CmdletBinding()]
[OutputType([PSCustomObject])]
param(
    [Parameter(Mandatory)]
    [System.Management.Automation.Credential()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [ValidateScript({ 
        # Validate that the URL is a valid Azure DevOps Repository URL
        # e.g.: https://dev.azure.com/Organization/_git/ProjectName
        # e.g.: https://dev.azure.com/Organization/ProjectName/_git/RepositoryName
        $_.DnsSafeHost -eq 'dev.azure.com' -and $_.LocalPath -match '^/[^/]+/([^/]+/)?_git/[^/]+/?$'
    })] 
    [Alias('url')]
    [System.Uri]
    $RepositoryUri,

    [Parameter(Mandatory = $false)]
    [string]
    $Organization = 'EESC-CoR',

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1'
)
Process {
    $Uri = $RepositoryUri.AbsoluteUri

    # Repository's WebUrl accepts query string arguments (e.g. to locate a specific file), discard them
    $Uri = $Uri -replace '\?.*$',''
    # Remove the username bit from the URI (there is one, when the user copied the "remoteUrl" of the repo instead of its "webUrl"
    $Uri = $Uri -replace "^(https?://)$($Organization)@",'$1'
    # Project all have a primary repository, with the same name as the project name
    # That repository can be referenced by both: https://AzureUrl/Organization/_git/ProjectName as well as https://AzureUrl/Organization/ProjectName/_git/RepoName
    $Uri = $Uri -replace "^(.*?/$($Organization))/_git/(.*)$",'$1/$2/_git/$2'

    "Lookup for Repository matching URL '$($Uri)' in the '$($Organization)' Azure DevOps Organization" | Write-Debug

    try {
        # Extract the project URL from the repository URL, and resolve it
        $Project = $Uri -replace '/_git/.*$','' |               
            Resolve-Project -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -ErrorAction Stop
    } catch {
        # Resolution failed, get all the projects
        $Project = Get-Project -Credential $Credential
    } finally {
        # Lookup for the repository inside the project
        $Project |
            Get-ProjectRepository -Credential $Credential |            # Get the Repositories in the project
            Where-Object {
                $_.webUrl -eq $Uri                              # Check if the repository matches the one we are searching for
            } |
            Select-Object -First 1 |                            # Interrupts the loop as soon as the first match is found
            Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion |    # Get the repository details
            Write-Output
    }
}
}