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 {
        "$($_.Key)=$($_.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
 
    .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()]
[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'
)
Process {
    Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -RelativeUri '_apis/projects' |
        Select-Object -ExpandProperty Value |
        ForEach-Object {
            [pscustomobject]$_ | 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 id
        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
 
    .EXAMPLE
        # Get a specific project's pipelines
        Get-ProjectPipeline -Credential 'Personal Access Token' -id 'MyProject'
 
    .EXAMPLE
        # Get all the pipelines for all the projects
        Get-Project -Credential $AzureCredential | Get-ProjectPipeline -Credential $AzureCredential
 
    .EXAMPLE
        # Get all the details of all the pipelines for all the projects (by calling Invoke-Api on each pipeline's url)
        Get-Project -Credential $AzureCredential | Get-ProjectPipeline -Credential $AzureCredential | Invoke-Api -Credential $AzureCredential
#>

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

    [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [Alias('ProjectId')]
    [string]
    $id,

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

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1'
)
Process {
    Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -RelativeUri "$id/_apis/pipelines" |
        Select-Object -ExpandProperty Value |
        ForEach-Object {
            [pscustomobject]$_ | 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 id
        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
 
    .EXAMPLE
        # Get a specific project's repositories
        Get-ProjectRepository -Credential 'Personal Access Token' -id 'MyProject'
 
    .EXAMPLE
        # Get all the repositories for all the projects
        Get-Project -Credential $AzureCredential | Get-ProjectRepository -Credential $AzureCredential
 
    .EXAMPLE
        # Get all the details of all the repositories for all the projects (by calling Invoke-Api on each repository's url)
        Get-Project -Credential $AzureCredential | Get-ProjectRepository -Credential $AzureCredential | Invoke-Api -Credential $AzureCredential
 
#>

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

    [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [Alias('ProjectId')]
    [string]
    $id,

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

    [Parameter(Mandatory = $false)]
    [string]
    $ApiVersion = '7.1'
)
Process {
    Invoke-Api -Credential $Credential -Organization $Organization -ApiVersion $ApiVersion -RelativeUri "$id/_apis/git/repositories" |
        Select-Object -ExpandProperty Value |
        ForEach-Object {
            [pscustomobject]$_ | Write-Output
        }
}
}
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

    <# catch {
        $Message = $_.ToString()
        "$($_.Exception.Response.StatusCode.value__) $($_.Exception.Response.StatusDescription) $($Message)" | Write-Error
       }
    #>

}
}