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 } #> } } |