PSSonarCloud.psm1

<#
  _____ _____ _____ _____ _ _
 | __ \ / ____/ ____| / ____| | | |
 | |__) | (___| (___ ___ _ __ __ _ _ __| | | | ___ _ _ __| |
 | ___/ \___ \\___ \ / _ \| '_ \ / _` | '__| | | |/ _ \| | | |/ _` |
 | | ____) |___) | (_) | | | | (_| | | | |____| | (_) | |_| | (_| |
 |_| |_____/_____/ \___/|_| |_|\__,_|_| \_____|_|\___/ \__,_|\__,_|
 
#>


# --- Clean up psSonarCloudConnection variable on module remove
$ExecutionContext.SessionState.Module.OnRemove = {

    Remove-Variable -Name SonarCloudConnection -Force -ErrorAction SilentlyContinue

}
<#
    - Function: xCheckScriptSonarCloudConnection
#>


function xCheckScriptSonarCloudConnection {
<#
    .SYNOPSIS
    Checks for the presence of $Script:SonarCloudConnection
 
    .DESCRIPTION
    Checks for the presence of $Script:SonarCloudConnection
 
    .INPUTS
    None
 
    .OUTPUTS
    None
 
    .EXAMPLE
    xCheckScriptSonarCloudConnection
#>


[CmdletBinding()]

    Param (

    )
    # --- Test for SonarCloud Connection
    if (-not $Script:SonarCloudConnection){

        throw "SonarCloud Connection variable does not exist. Please run Connect-SonarCloud first to create it"
    }
}

<#
    - Function: xNewHttpQueryString
#>


function xNewHttpQueryString {
<#
    .SYNOPSIS
    Generates HTTP Query String from URI and parameters
 
    .DESCRIPTION
    Generates HTTP Query String from URI and parameters
    Based on https://powershellmagazine.com/2019/06/14/pstip-a-better-way-to-generate-http-query-strings-in-powershell/
 
    .INPUTS
    String
    Hashtable
 
    .OUTPUTS
    String
 
    .EXAMPLE
    $parameters = @{
        organization = 'test-321'
        ps = 100
    }
    $url = 'https://sonarcloud.io/api/projects/search'
 
    xNewHttpQueryString -Uri $url -QueryParameter $parameters
#>

[CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]$Uri,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [Hashtable]$QueryParameter
    )
    # Add System.Web
    Add-Type -AssemblyName System.Web

    # Create a http name value collection from an empty string
    $nvCollection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)

    foreach ($key in $QueryParameter.Keys) {
        $nvCollection.Add($key, $QueryParameter.$key)
    }

    # Build the uri
    $uriRequest = [System.UriBuilder]$uri
    $uriRequest.Query = $nvCollection.ToString()

    return $uriRequest.Uri.OriginalString
}

<#
    - Function: Connect-SonarCloud
#>


function Connect-SonarCloud {
<#
    .SYNOPSIS
    Make a connection to the SonarCloud API
 
    .DESCRIPTION
    Make a connection to the SonarCloud API. Set the SonarCloud API Key
 
    .PARAMETER APIKey
    SonarCloud API Key
 
    .INPUTS
    System.String
 
    .OUTPUTS
    System.Management.Automation.PSObject
 
    .EXAMPLE
    Connect-SonarCloud -APIKey 'xxxxxxxxxxxxxxxxx'
#>

[CmdletBinding()][OutputType('System.Management.Automation.PSObject')]

    Param
    (

    [parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [String]$apiKey
    )

    try {

        # --- Create Output Object
        $Script:sonarCloudConnection = [pscustomobject]@{

            apiKey = $apiKey
            url = 'https://sonarcloud.io/api'
        }
    }
    catch [Exception]{

        throw
    }
    finally {

        Write-Output $sonarCloudConnection
    }
}

<#
    - Function: Disconnect-SonarCloud
#>


function Disconnect-SonarCloud {
<#
    .SYNOPSIS
    Disconnect from the SonarCloud API
 
    .DESCRIPTION
    Disconnect from the SonarCloud API by removing the script SonarCloudConnection variable from PowerShell
 
    .EXAMPLE
    Disconnect-SonarCloud
 
    .EXAMPLE
    Disconnect-SonarCloud -Confirm:$false
#>

[CmdletBinding(SupportsShouldProcess,ConfirmImpact="High")]

    Param ()

    # --- Check for the presence of $Script:SonarCloudConnection
    xCheckScriptSonarCloudConnection

    if ($PSCmdlet.ShouldProcess($Script:SonarCloudConnection.url)){

        try {

            Write-Verbose -Message "Removing SonarCloudConnection Script Variable"
            Remove-Variable -Name SonarCloudConnection -Scope Script -Force -ErrorAction SilentlyContinue
        }
        catch [Exception]{

            throw

        }
    }
}

<#
    - Function: Invoke-SonarCloudRestMethod
#>


function Invoke-SonarCloudRestMethod {
<#
    .SYNOPSIS
    Wrapper for Invoke-RestMethod/Invoke-WebRequest with SonarCloud specifics
 
    .DESCRIPTION
    Wrapper for Invoke-RestMethod/Invoke-WebRequest with SonarCloud specifics
 
    .PARAMETER Method
    REST Method:
    Supported Methods: GET, POST, PUT,DELETE
 
    .PARAMETER URI
    API URI, e.g. /projects/search
 
    .PARAMETER QueryParameters
    HTTP Query String Parameters
 
    .PARAMETER Headers
    Optionally supply custom headers
 
    .PARAMETER Body
    REST Body in JSON format
 
    .PARAMETER OutFile
    Save the results to a file
 
    .PARAMETER WebRequest
    Use Invoke-WebRequest rather than the default Invoke-RestMethod
 
    .INPUTS
    System.String
    Hashtable
    Switch
 
    .OUTPUTS
    System.Management.Automation.PSObject
 
    .EXAMPLE
    $parameters = @{
        organization = 'test-321'
        ps = 100
    }
 
    Invoke-SonarCloudRestMethod -Method GET -URI '/projects/search' -queryParameters $parameters
#>

[CmdletBinding(DefaultParameterSetName="Standard")][OutputType('System.Management.Automation.PSObject')]

    Param (

        [Parameter(Mandatory=$true, ParameterSetName="Standard")]
        [Parameter(Mandatory=$true, ParameterSetName="Body")]
        [Parameter(Mandatory=$true, ParameterSetName="OutFile")]
        [ValidateSet("GET","POST","PUT","DELETE")]
        [String]$method,

        [Parameter(Mandatory=$true, ParameterSetName="Standard")]
        [Parameter(Mandatory=$true, ParameterSetName="Body")]
        [Parameter(Mandatory=$true, ParameterSetName="OutFile")]
        [ValidateNotNullOrEmpty()]
        [String]$uri,

        [Parameter(Mandatory=$true, ParameterSetName="Standard")]
        [Parameter(Mandatory=$true, ParameterSetName="Body")]
        [Parameter(Mandatory=$true, ParameterSetName="OutFile")]
        [ValidateNotNullOrEmpty()]
        [Hashtable]$queryParameters,

        [Parameter(Mandatory=$false, ParameterSetName="Standard")]
        [Parameter(Mandatory=$false, ParameterSetName="Body")]
        [Parameter(Mandatory=$false, ParameterSetName="OutFile")]
        [ValidateNotNullOrEmpty()]
        [System.Collections.IDictionary]$headers,

        [Parameter(Mandatory=$false, ParameterSetName="Body")]
        [ValidateNotNullOrEmpty()]
        [Hashtable]$body,

        [Parameter(Mandatory=$false, ParameterSetName="OutFile")]
        [ValidateNotNullOrEmpty()]
        [String]$outFile,

        [Parameter(Mandatory=$false, ParameterSetName="Standard")]
        [Parameter(Mandatory=$false, ParameterSetName="Body")]
        [Parameter(Mandatory=$false, ParameterSetName="OutFile")]
        [Switch]$webRequest
    )

    # --- Test for existing connection to vRA
    if (-not $Script:SonarCloudConnection){

        throw "SonarCloud Connection variable does not exist. Please run Connect-SonarCloud first to create it"
    }

    # --- Create Invoke-RestMethod Parameters
    $fullURI = "$($Script:SonarCloudConnection.url)$($uri)"

    # --- Update full URI if queryParameters have been supplied
    if ($PSBoundParameters.ContainsKey("queryParameters")){

        $fullURI = xNewHttpQueryString -Uri $fullURI -QueryParameter $queryParameters
    }
    Write-Verbose "Full URI is: $($fullURI)"

    # --- Prepare API Key for authentication
    $auth = $Script:SonarCloudConnection.apiKey + ':'
    $encoded = [System.Text.Encoding]::UTF8.GetBytes($auth)
    $base64 = [System.Convert]::ToBase64String($encoded)
    $basicAuthValue = "Basic $base64"

    # --- Add default headers if not passed
    if (!$PSBoundParameters.ContainsKey("Headers")){

        $headers = @{

            "Authorization" = $basicAuthValue;
        }
    }

    Write-Verbose "Headers are: $($headers | ConvertTo-Json)"

    # --- Set up default parmaeters
    $params = @{

        method = $method
        headers = $headers
        uri = $fullURI
    }

    if ($PSBoundParameters.ContainsKey("body")){

        $params.Add("body", $body)

        # --- Log the payload being sent to the server
        Write-Debug -Message ($body | ConvertTo-Json -Depth 5)
    }

    if ($PSBoundParameters.ContainsKey("outfile")){

        $params.Add("outFile", $outFile)

    }

    try {

        # --- Use either Invoke-WebRequest or Invoke-RestMethod
        if ($WebRequest.IsPresent) {

            Invoke-WebRequest @params
        }
        else {

            Invoke-RestMethod @params
        }
    }
    catch {

        throw $_
    }
}

<#
    - Function: New-SonarCloudProjectALMIntegrated
#>


function New-SonarCloudProjectALMIntegrated {
    <#
    .SYNOPSIS
    Create a SonarCloud Project Integrated with ALM
 
    .DESCRIPTION
    Create a SonarCloud Project Integrated with ALM, e.g. GitLab
    Note this is from an unsupported and undocumented part of the API, /alm_integration, so use with caution since it is subject to change / disruption
 
    .PARAMETER organization
    SonarCloud Organization
 
    .PARAMETER installationKeys
    SonarCloud InstallationKeys. For GitLab needs to be the ProjectId
 
    .INPUTS
    System.String.
 
    .OUTPUTS
    System.Management.Automation.PSObject
 
    .EXAMPLE
    New-SonarCloudProjectALMIntegrated -organization 'organization1' -installationKeys '32959331'
#>

    [CmdletBinding(SupportsShouldProcess,ConfirmImpact="Low")][OutputType('System.Management.Automation.PSObject')]

    Param (

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$organization,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$installationKeys
    )

    begin {

        # --- Check for the presence of $Script:SonarCloudConnection
        xCheckScriptSonarCloudConnection

        $apiUrl = '/alm_integration/provision_projects'

        function CalculateOutput([PSCustomObject]$project){

            [PSCustomObject] @{

                Key = $project.projectKey
            }
        }
    }

    process {

        try {

            $queryParameters = @{

                organization = $organization
                installationKeys = $installationKeys
            }

            if ($PSCmdlet.ShouldProcess($installationKeys)){

                $response = Invoke-SonarCloudRestMethod -Method POST -URI $apiUrl -queryParameters $queryParameters -Verbose:$VerbosePreference
                CalculateOutput $response.projects
            }

        }
        catch [Exception] {

            throw
        }
    }
}

<#
    - Function: Add-SonarCloudOrganizationMember
#>


function Add-SonarCloudOrganizationMember {
    <#
    .SYNOPSIS
    Add a member to a SonarCloud Organization
 
    .DESCRIPTION
    Add a member to a SonarCloud Organization
    Note this is from an unsupported and undocumented part of the API, /organizations, so use with caution since it is subject to change / disruption
 
    .PARAMETER organization
    SonarCloud Organization
 
    .PARAMETER userLogin
    SonarCloud User Login, e.g. testuser1@gitlab
 
    .INPUTS
    System.String.
 
    .OUTPUTS
    None
 
    .EXAMPLE
    Add-SonarCloudOrganizationMember -organization 'organization1' -userLogin 'testuser1@gitlab','testuser2@gitlab'
#>

    [CmdletBinding(SupportsShouldProcess,ConfirmImpact="Low")]

    Param (

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$organization,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String[]]$userLogin
    )

    begin {

        # --- Check for the presence of $Script:SonarCloudConnection
        xCheckScriptSonarCloudConnection

        $apiUrl = '/organizations/add_member'

        function CalculateOutput([PSCustomObject]$member){

            [PSCustomObject] @{

                Name = $member.name
                Login = $member.login
                Avatar = $member.avatar
                GroupCount = $member.groupCount
            }
        }
    }

    process {

        try {

            foreach ($user in $userLogin){

                $queryParameters = @{

                    organization = $organization
                    login = $user
                }

                if ($PSCmdlet.ShouldProcess($groupName)){

                    $response = Invoke-SonarCloudRestMethod -Method POST -URI $apiUrl -queryParameters $queryParameters -Verbose:$VerbosePreference
                    CalculateOutput $response.user
                }
            }
        }
        catch [Exception] {

            throw
        }
    }
}

<#
    - Function: Get-SonarCloudOrganizationMember
#>


function Get-SonarCloudOrganizationMember {
    <#
    .SYNOPSIS
    Get SonarCloud Organization Members
 
    .DESCRIPTION
    Get SonarCloud Organization Members
    Note this is from an unsupported and undocumented part of the API, /organizations, so use with caution since it is subject to change / disruption
 
    .PARAMETER organization
    SonarCloud Organization
 
    .PARAMETER pageSize
    Specify how many records to return per page
 
    .INPUTS
    System.String.
    System.Int
 
    .OUTPUTS
    System.Management.Automation.PSObject
 
    .EXAMPLE
    Get-SonarCloudOrganizationMember -organization 'organization1'
#>

    [CmdletBinding()][OutputType('System.Management.Automation.PSObject')]

    Param (

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]$organization,

        [parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [Int]$pageSize = 100
    )

    begin {

        # --- Check for the presence of $Script:SonarCloudConnection
        xCheckScriptSonarCloudConnection

        $apiUrl = '/organizations/search_members'
        function CalculateOutput([PSCustomObject]$member) {

            [PSCustomObject] @{

                Name       = $member.name
                Login      = $member.login
                Avatar     = $member.avatar
                GroupCount = $member.groupCount
                IsOrgAdmin = $member.isOrgAdmin
            }
        }
    }

    process {

        try {

            $page = 1
            do {
                $queryParameters = @{

                    organization = $organization
                    ps           = $pageSize
                    p            = $page
                }
                $response = Invoke-SonarCloudRestMethod -Method GET -URI $apiUrl -queryParameters $queryParameters -Verbose:$VerbosePreferenc

                if ($response.users){

                    foreach ($member in $response.users) {

                        CalculateOutput $member
                    }

                    $page++
                }
                else {
                    $escape = $true
                }
            }
            until ($escape)
        }
        catch [Exception] {

            throw
        }
    }
}

<#
    - Function: Get-SonarCloudPermissionTemplate
#>


function Get-SonarCloudPermissionTemplate {
    <#
    .SYNOPSIS
    Get SonarCloud PermissionTemplates
 
    .DESCRIPTION
    Get SonarCloud PermissionTemplates
 
    .PARAMETER organization
    SonarCloud Organization
 
    .PARAMETER permissionTemplateName
    SonarCloud permissionTemplateName
 
    .INPUTS
    System.String.
 
    .OUTPUTS
    System.Management.Automation.PSObject
 
    .EXAMPLE
    Get-SonarCloudPermissionTemplate -organization 'organization1'
 
    .EXAMPLE
    Get-SonarCloudPermissionTemplate -organization 'organization1' -permissionTemplateName 'PermissionTemplate A'
#>

    [CmdletBinding(DefaultParameterSetName="Standard")][OutputType('System.Management.Automation.PSObject')]

    Param (

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$organization,

        [parameter(Mandatory=$false,ParameterSetName="ByName")]
        [ValidateNotNullOrEmpty()]
        [String[]]$permissionTemplateName
    )

    begin {

        # --- Check for the presence of $Script:SonarCloudConnection
        xCheckScriptSonarCloudConnection

        $apiUrl = '/permissions/search_templates'
        function CalculateOutput([PSCustomObject]$permissionTemplate){

            [PSCustomObject] @{

                Name = $permissionTemplate.name
                Id = $permissionTemplate.id
                Description = $permissionTemplate.description
                ProjectKeyPattern = $permissionTemplate.projectKeyPattern
                CreatedAt = $permissionTemplate.createdAt
                UpdatedAt = $permissionTemplate.updatedAt
                Permissions = $permissionTemplate.permissions
            }
        }
    }

    process {

        try {

            switch ($PsCmdlet.ParameterSetName) {

                # --- Get PermissionTemplate by name
                'ByName' {

                    foreach ($name in $permissionTemplateName){

                        $queryParameters = @{

                            organization = $organization
                            q = $name
                        }

                        $response = Invoke-SonarCloudRestMethod -Method GET -URI $apiUrl -queryParameters $queryParameters -Verbose:$VerbosePreference

                        foreach ($permissionTemplate in $response.permissionTemplates){

                            CalculateOutput $permissionTemplate
                        }
                    }

                    break
                }
                # --- No parameters passed so return all PermissionTemplates
                'Standard' {

                    $queryParameters = @{

                        organization = $organization
                    }
                    $Response = Invoke-SonarCloudRestMethod -Method GET -URI $apiUrl -queryParameters $queryParameters -Verbose:$VerbosePreference

                    foreach ($permissionTemplate in $response.permissionTemplates){

                        CalculateOutput $permissionTemplate
                    }
                }
            }
        }
        catch [Exception] {

            throw
        }
    }
}

<#
    - Function: Initialize-SonarCloudPermissionTemplate
#>


function Initialize-SonarCloudPermissionTemplate {
    <#
    .SYNOPSIS
    Apply a SonarCloud Permission Template
 
    .DESCRIPTION
    Apply a SonarCloud Permission Template
 
    .PARAMETER organization
    SonarCloud Organization
 
    .PARAMETER projectKey
    Project Key, e.g. GitLab repo id
 
    .PARAMETER permissionTemplateName
    Name of Permission Template to apply
 
    .INPUTS
    System.String.
 
    .OUTPUTS
    None
 
    .EXAMPLE
    Initialize-SonarCloudPermissionTemplate -organization 'organization1' -projectKey '32959331' -permissionTemplateName 'PermissionTemplate A'
#>

    [CmdletBinding(SupportsShouldProcess,ConfirmImpact="High")]

    Param (

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$organization,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$projectKey,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$permissionTemplateName
    )

    begin {

        # --- Check for the presence of $Script:SonarCloudConnection
        xCheckScriptSonarCloudConnection

        $apiUrl = '/permissions/apply_template'
    }

    process {

        try {

            $queryParameters = @{

                organization = $organization
                projectKey = $projectKey
                templateName = $permissionTemplateName
            }

            if ($PSCmdlet.ShouldProcess($projectKey)){

                Invoke-SonarCloudRestMethod -Method POST -URI $apiUrl -queryParameters $queryParameters -Verbose:$VerbosePreference
            }

        }
        catch [Exception] {

            throw
        }
    }
}

<#
    - Function: Get-SonarCloudProject
#>


function Get-SonarCloudProject {
    <#
    .SYNOPSIS
    Get SonarCloud Projects
 
    .DESCRIPTION
    Get SonarCloud Projects
 
    .PARAMETER organization
    SonarCloud Organization
 
    .PARAMETER projectName
    SonarCloud Project Name
 
    .PARAMETER pageSize
    Specify how many records to return
 
    .INPUTS
    System.String.
    System.Int
 
    .OUTPUTS
    System.Management.Automation.PSObject
 
    .EXAMPLE
    Get-SonarCloudProject -organization 'organization1'
 
    .EXAMPLE
    Get-SonarCloudProject -organization 'organization1' -projectName 'Project A'
#>

    [CmdletBinding(DefaultParameterSetName = "Standard")][OutputType('System.Management.Automation.PSObject')]

    Param (

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]$organization,

        [parameter(Mandatory = $false, ParameterSetName = "ByName")]
        [ValidateNotNullOrEmpty()]
        [String[]]$projectName,

        [parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [Int]$pageSize = 100
    )

    begin {

        # --- Check for the presence of $Script:SonarCloudConnection
        xCheckScriptSonarCloudConnection

        $apiUrl = '/projects/search'
        function CalculateOutput([PSCustomObject]$project) {

            [PSCustomObject] @{

                Name         = $project.name
                Key          = $project.Key
                Organization = $project.organization
                Qualifier    = $project.qualifier
                Visibility   = $project.visibility
            }
        }
    }

    process {

        try {

            switch ($PsCmdlet.ParameterSetName) {

                # --- Get Project by name
                'ByName' {

                    foreach ($name in $projectName) {

                        $queryParameters = @{

                            organization = $organization
                            q            = $name
                            ps           = $pageSize
                        }

                        $response = Invoke-SonarCloudRestMethod -Method GET -URI $apiUrl -queryParameters $queryParameters -Verbose:$VerbosePreference

                        foreach ($project in $response.components) {

                            CalculateOutput $project
                        }
                    }

                    break
                }
                # --- No parameters passed so return all Projects
                'Standard' {

                    $page = 1
                    do {
                        $queryParameters = @{

                            organization = $organization
                            ps           = $pageSize
                            p            = $page
                        }
                        $Response = Invoke-SonarCloudRestMethod -Method GET -URI $apiUrl -queryParameters $queryParameters -Verbose:$VerbosePreference

                        if ($response.components){

                            foreach ($project in $response.components) {

                                CalculateOutput $project
                            }

                            $page++
                        }
                        else {
                            $escape = $true
                        }
                    }
                    until ($escape)
                }
            }
        }
        catch [Exception] {

            throw
        }
    }
}

<#
    - Function: New-SonarCloudProject
#>


function New-SonarCloudProject {
    <#
    .SYNOPSIS
    Create a SonarCloud Project
 
    .DESCRIPTION
    Create a SonarCloud Project
 
    .PARAMETER organization
    SonarCloud Organization
 
    .PARAMETER projectName
    SonarCloud Project Name
 
    .PARAMETER projectKey
    Project Key, e.g. GitLab repo id
 
    .PARAMETER visibility
    Whether the created project should be visible to everyone, or only specific user/groups
 
    .INPUTS
    System.String.
 
    .OUTPUTS
    System.Management.Automation.PSObject
 
    .EXAMPLE
    New-SonarCloudProject -organization 'organization1' -projectName 'Project A' -projectKey '32959331'
#>

    [CmdletBinding(SupportsShouldProcess,ConfirmImpact="Low")][OutputType('System.Management.Automation.PSObject')]

    Param (

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$organization,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$projectName,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$projectKey,

        [parameter(Mandatory=$false)]
        [ValidateSet('private', 'public', IgnoreCase=$false)]
        [String]$visibility = 'private'
    )

    begin {

        # --- Check for the presence of $Script:SonarCloudConnection
        xCheckScriptSonarCloudConnection

        $apiUrl = '/projects/create'

        function CalculateOutput([PSCustomObject]$project){

            [PSCustomObject] @{

                Name = $project.name
                Key = $project.Key
                Qualifier = $project.qualifier
                Visibility = $project.visibility
            }
        }
    }

    process {

        try {

            $queryParameters = @{

                organization = $organization
                name = $projectName
                project = $projectKey
                visibility = $visibility
            }

            if ($PSCmdlet.ShouldProcess($projectName)){

                $response = Invoke-SonarCloudRestMethod -Method POST -URI $apiUrl -queryParameters $queryParameters -Verbose:$VerbosePreference
                CalculateOutput $response.project
            }

        }
        catch [Exception] {

            throw
        }
    }
}

<#
    - Function: Add-SonarCloudUserGroupMember
#>


function Add-SonarCloudUserGroupMember {
    <#
    .SYNOPSIS
    Add a SonarCloud User to a Group
 
    .DESCRIPTION
    Add a SonarCloud User to a Group
 
    .PARAMETER organization
    SonarCloud Organization
 
    .PARAMETER groupName
    SonarCloud Group Name
 
    .PARAMETER userLogin
    SonarCloud User Login, e.g. testuser1@gitlab
 
    .INPUTS
    System.String.
 
    .OUTPUTS
    None
 
    .EXAMPLE
    Add-SonarCloudUserGroupMember -organization 'organization1' -groupName 'Group A' -userLogin 'testuser1@gitlab','testuser2@gitlab'
#>

    [CmdletBinding(SupportsShouldProcess,ConfirmImpact="Low")]

    Param (

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$organization,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$groupName,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String[]]$userLogin
    )

    begin {

        # --- Check for the presence of $Script:SonarCloudConnection
        xCheckScriptSonarCloudConnection

        $apiUrl = '/user_groups/add_user'
    }

    process {

        try {

            foreach ($user in $userLogin){

                $queryParameters = @{

                    organization = $organization
                    name = $groupName
                    login = $user
                }

                if ($PSCmdlet.ShouldProcess($groupName)){

                    Invoke-SonarCloudRestMethod -Method POST -URI $apiUrl -queryParameters $queryParameters -Verbose:$VerbosePreference
                }
            }
        }
        catch [Exception] {

            throw
        }
    }
}

<#
    - Function: Get-SonarCloudUserGroup
#>


function Get-SonarCloudUserGroup {
    <#
    .SYNOPSIS
    Get SonarCloud UserGroups
 
    .DESCRIPTION
    Get SonarCloud UserGroups
 
    .PARAMETER organization
    SonarCloud Organization
 
    .PARAMETER groupName
    SonarCloud Group Name
 
    .PARAMETER pageSize
    Specify how many records to return
 
    .INPUTS
    System.String.
    System.Int
 
    .OUTPUTS
    System.Management.Automation.PSObject
 
    .EXAMPLE
    Get-SonarCloudUserGroup -organization 'organization1'
 
    .EXAMPLE
    Get-SonarCloudUserGroup -organization 'organization1' -groupName 'UserGroup A'
#>

    [CmdletBinding(DefaultParameterSetName="Standard")][OutputType('System.Management.Automation.PSObject')]

    Param (

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$organization,

        [parameter(Mandatory=$false,ParameterSetName="ByName")]
        [ValidateNotNullOrEmpty()]
        [String[]]$groupName,

        [parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [Int]$pageSize = 100
    )

    begin {

        # --- Check for the presence of $Script:SonarCloudConnection
        xCheckScriptSonarCloudConnection

        $apiUrl = '/user_groups/search'
        function CalculateOutput([PSCustomObject]$userGroup){

            [PSCustomObject] @{

                Name = $userGroup.name
                Id = $userGroup.id
                Description = $userGroup.description
                MembersCount = $userGroup.membersCount
                Default = $userGroup.default
            }
        }
    }

    process {

        try {

            switch ($PsCmdlet.ParameterSetName) {

                # --- Get UserGroup by name
                'ByName' {

                    foreach ($name in $groupName){

                        $queryParameters = @{

                            organization = $organization
                            q = $name
                            ps  = $pageSize
                        }

                        $response = Invoke-SonarCloudRestMethod -Method GET -URI $apiUrl -queryParameters $queryParameters -Verbose:$VerbosePreference

                        foreach ($userGroup in $response.groups){

                            CalculateOutput $userGroup
                        }
                    }

                    break
                }
                # --- No parameters passed so return all UserGroups
                'Standard' {

                    $queryParameters = @{

                        organization = $organization
                        ps  = $pageSize
                    }
                    $Response = Invoke-SonarCloudRestMethod -Method GET -URI $apiUrl -queryParameters $queryParameters -Verbose:$VerbosePreference

                    foreach ($userGroup in $response.groups){

                        CalculateOutput $userGroup
                    }
                }
            }
        }
        catch [Exception] {

            throw
        }
    }
}

<#
    - Function: Remove-SonarCloudUserGroupMember
#>


function Remove-SonarCloudUserGroupMember {
    <#
    .SYNOPSIS
    Remove a SonarCloud User from a Group
 
    .DESCRIPTION
    Remove a SonarCloud User from a Group
 
    .PARAMETER organization
    SonarCloud Organization
 
    .PARAMETER groupName
    SonarCloud Group Name
 
    .PARAMETER userLogin
    SonarCloud User Login, e.g. testuser1@gitlab
 
    .INPUTS
    System.String.
 
    .OUTPUTS
    None
 
    .EXAMPLE
    Remove-SonarCloudUserGroupMember -organization 'organization1' -groupName 'Group A' -userLogin 'testuser1@gitlab','testuser2@gitlab'
#>

    [CmdletBinding(SupportsShouldProcess,ConfirmImpact="High")]

    Param (

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$organization,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$groupName,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String[]]$userLogin
    )

    begin {

        # --- Check for the presence of $Script:SonarCloudConnection
        xCheckScriptSonarCloudConnection

        $apiUrl = '/user_groups/remove_user'
    }

    process {

        try {

            foreach ($user in $userLogin){

                $queryParameters = @{

                    organization = $organization
                    name = $groupName
                    login = $user
                }

                if ($PSCmdlet.ShouldProcess($groupName)){

                    Invoke-SonarCloudRestMethod -Method POST -URI $apiUrl -queryParameters $queryParameters -Verbose:$VerbosePreference
                }
            }
        }
        catch [Exception] {

            throw
        }
    }
}

<#
    - Function: Get-SonarCloudUser
#>


function Get-SonarCloudUser {
    <#
    .SYNOPSIS
    Get SonarCloud Users
 
    .DESCRIPTION
    Get SonarCloud Users
 
    .PARAMETER userName
    SonarCloud User Name - filter on login, name or email
 
    .PARAMETER pageSize
    Specify how many records to return
 
    .INPUTS
    System.String.
    System.Int
 
    .OUTPUTS
    System.Management.Automation.PSObject
 
    .EXAMPLE
    Get-SonarCloudUser -userName 'User A','User B'
#>

    [CmdletBinding()][OutputType('System.Management.Automation.PSObject')]

    Param (

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String[]]$userName,

        [parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [Int]$pageSize = 100
    )

    begin {

        # --- Check for the presence of $Script:SonarCloudConnection
        xCheckScriptSonarCloudConnection

        $apiUrl = '/users/search'
        function CalculateOutput([PSCustomObject]$user){

            [PSCustomObject] @{

                Name = $user.name
                Login = $user.login
                Email = $user.email
                Active = $user.active
                Groups = $user.groups
                TokensCount = $user.tokensCount
                Local = $user.local
                ExternalIdentity = $user.externalIdentity
                ExternalProvider = $user.externalProvider
                LastConnectionDate = $user.lastConnectionDate
            }
        }
    }

    process {

        try {

            foreach ($name in $userName){

                $queryParameters = @{

                    q = $name
                    ps  = $pageSize
                }

                $response = Invoke-SonarCloudRestMethod -Method GET -URI $apiUrl -queryParameters $queryParameters -Verbose:$VerbosePreference

                foreach ($user in $response.users){

                    CalculateOutput $user
                }
            }
        }
        catch [Exception] {

            throw
        }
    }
}

<#
    - Function: Get-SonarCloudUserGroupMembership
#>


function Get-SonarCloudUserGroupMembership {
    <#
    .SYNOPSIS
    Get SonarCloud Groups a User belongs to
 
    .DESCRIPTION
    Get SonarCloud Groups a User belongs to
 
    .PARAMETER organization
    SonarCloud Organization
 
    .PARAMETER userLogin
    SonarCloud User Login
 
    .PARAMETER pageSize
    Specify how many records to return
 
    .INPUTS
    System.String.
    System.Int
 
    .OUTPUTS
    System.Management.Automation.PSObject
 
    .EXAMPLE
    Get-SonarCloudUserGroupMembership -organization 'organization1' -userLogin 'usera@gitlab','userb@gitlab'
#>

    [CmdletBinding()][OutputType('System.Management.Automation.PSObject')]

    Param (

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$organization,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String[]]$userLogin,

        [parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [Int]$pageSize = 25
    )

    begin {

        # --- Check for the presence of $Script:SonarCloudConnection
        xCheckScriptSonarCloudConnection

        $apiUrl = '/users/groups'
        function CalculateOutput([PSCustomObject]$group, [String]$login){

            [PSCustomObject] @{

                Login = $login
                Name = $group.name
                Id = $group.id
                Description = $group.description
                Selected = $group.Selected
                Default = $group.default
            }
        }
    }

    process {

        try {

            foreach ($login in $userLogin){

                $queryParameters = @{

                    login = $login
                    organization = $organization
                    ps  = $pageSize
                }

                $response = Invoke-SonarCloudRestMethod -Method GET -URI $apiUrl -queryParameters $queryParameters -Verbose:$VerbosePreference

                foreach ($group in $response.groups){

                    CalculateOutput $group $login
                }
            }
        }
        catch [Exception] {

            throw
        }
    }
}