Functions/DevOps.Repos.ps1

<#
    .SYNOPSIS
    Get all repos from Azure DevOps project

    .DESCRIPTION
    Get all repos from Azure DevOps project using Azure DevOps Rest API

    .PARAMETER PAT
    Personal Access Token (PAT) for Azure DevOps

    .PARAMETER TokenType
    Token Type for Azure DevOps, can be FullAccess, FineGrained or ReadOnly

    .PARAMETER Organization
    Organization name for Azure DevOps

    .PARAMETER Project
    Project name for Azure DevOps

    .EXAMPLE
    Get-AzDevOpsRepos -PAT $PAT -Organization $Organization -Project $Project
#>

Function Get-AzDevOpsRepos {
    [CmdletBinding(DefaultParameterSetName = 'PAT')]
    [OutputType([System.Object[]])]
    param (
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $PAT,
        [Parameter(ParameterSetName = 'PAT')]
        [ValidateSet('FullAccess', 'FineGrained', 'ReadOnly')]
        [string]
        $TokenType = 'FullAccess',
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $Organization,
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $Project
    )
    $header = Get-AzDevOpsHeader -PAT $PAT
    Write-Verbose "Getting repos for project $Project"
    $uri = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories?api-version=6.0"
    Write-Verbose "URI: $uri"
    try {
        $response = Invoke-RestMethod -Uri $uri -Method Get -Headers $header
        # If the response is a string and not an object, throw an exception for authentication failure or project not found
        if ($response -is [string]) {
            throw "Authentication failed or project not found"
        }
    }
    catch {
        throw $_.Exception.Message
    }
    return @($response.value)
}
Export-ModuleMember -Function Get-AzDevOpsRepos
# End of Function Get-AzDevOpsRepos

<#
    .SYNOPSIS
    Get Azure DevOps branch policy for a branch in a repo

    .DESCRIPTION
    Get Azure DevOps branch policy for a branch in a repo using Azure DevOps Rest API

    .PARAMETER PAT
    Personal Access Token (PAT) for Azure DevOps

    .PARAMETER TokenType
    Token Type for Azure DevOps, can be FullAccess, FineGrained or ReadOnly

    .PARAMETER Organization
    Organization name for Azure DevOps

    .PARAMETER Project
    Project name for Azure DevOps

    .PARAMETER Repository
    Repository name for Azure DevOps

    .PARAMETER Branch
    Branch name for Azure DevOps as a git ref. Example: refs/heads/main

    .EXAMPLE
    Get-AzDevOpsBranchPolicy -PAT $PAT -Organization $Organization -Project $Project -Repository $Repository -Branch $Branch

    .NOTES
    This function returns an empty object if no branch policy is found for the branch
#>

Function Get-AzDevOpsBranchPolicy {
    [CmdletBinding(DefaultParameterSetName = 'PAT')]
    [OutputType([object[]])]
    param (
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $PAT,
        [Parameter(ParameterSetName = 'PAT')]
        [ValidateSet('FullAccess', 'FineGrained', 'ReadOnly')]
        [string]
        $TokenType = 'FullAccess',
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $Organization,
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $Project,
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $Repository,
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $Branch
    )
    $header = Get-AzDevOpsHeader -PAT $PAT
    Write-Verbose "Getting branch policy for branch $Branch in repo $Repository in project $Project"
    $uri = "https://dev.azure.com/$Organization/$Project/_apis/policy/configurations?api-version=6.0"
    Write-Verbose "URI: $uri"
    # Try to get the branch policy, return an empty object if no branch policy is found for the branch
    try {
        $response = Invoke-RestMethod -Uri $uri -Method Get -Headers $header
        # If the response is a string and not an object, throw an exception for authentication failure or project not found
        if ($response -is [string]) {
            throw "Authentication failed or project not found"
        }
    }
    catch {
        throw $_.Exception.Message
    }
    $branchPolicy = @($response.value | Where-Object {$_.settings.scope.refName -eq $Branch -and $_.settings.scope.repositoryId -eq $Repository})

    return $branchPolicy
}
Export-ModuleMember -Function Get-AzDevOpsBranchPolicy
# End of Function Get-AzDevOpsBranchPolicy

<#
    .SYNOPSIS
    Get Repository pipeline permissions for a repo

    .DESCRIPTION
    Get Repository pipeline permissions for a repo using Azure DevOps Rest API

    .PARAMETER PAT
    Personal Access Token (PAT) for Azure DevOps

    .PARAMETER TokenType
    Token Type for Azure DevOps, can be FullAccess, FineGrained or ReadOnly

    .PARAMETER Organization
    Organization name for Azure DevOps

    .PARAMETER ProjectId
    Project ID for Azure DevOps project

    .PARAMETER RepositoryId
    Repository ID for Azure DevOps

    .EXAMPLE
    Get-AzDevOpsRepositoryPipelinePermissions -PAT $PAT -Organization $Organization -ProjectId $ProjectId -RepositoryId $RepositoryId
#>

Function Get-AzDevOpsRepositoryPipelinePermissions {
    [CmdletBinding(DefaultParameterSetName = 'PAT')]
    [OutputType([object[]])]
    param (
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $PAT,
        [Parameter(ParameterSetName = 'PAT')]
        [ValidateSet('FullAccess', 'FineGrained', 'ReadOnly')]
        [string]
        $TokenType = 'FullAccess',
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $Organization,
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $ProjectId,
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $RepositoryId
    )
    $header = Get-AzDevOpsHeader -PAT $PAT
    $uri = "https://dev.azure.com/{0}/{1}/_apis/pipelines/pipelinePermissions/repository/{2}.{3}" -f $Organization, $ProjectId, $ProjectId, $RepositoryId
    try {
        $response = Invoke-RestMethod -Uri $uri -Method Get -Headers $header -ContentType "application/json"
        # If the response is a string and not an object, throw an exception for authentication failure or project not found
        if ($response -is [string]) {
            throw "Authentication failed or project not found"
        }
    }
    catch {
        throw $_.Exception.Message
    }
    return $response
}
Export-ModuleMember -Function Get-AzDevOpsRepositoryPipelinePermissions
# End of Function Get-AzDevOpsRepositoryPipelinePermissions

<#
    .SYNOPSIS
    Get Azure DevOps repos ACLs

    .DESCRIPTION
    Get Azure DevOps repos ACLs using Azure DevOps Rest API

    .PARAMETER PAT
    Personal Access Token (PAT) for Azure DevOps

    .PARAMETER TokenType
    Token Type for Azure DevOps, can be FullAccess, FineGrained or ReadOnly

    .PARAMETER Organization
    Organization name for Azure DevOps

    .PARAMETER ProjectId
    Project ID for Azure DevOps project

    .PARAMETER RepositoryId
    Repository ID for Azure DevOps

    .EXAMPLE
    Get-AzDevOpsRepositoryAcls -PAT $PAT -Organization $Organization -ProjectId $ProjectId -RepositoryId $RepositoryId
#>

Function Get-AzDevOpsRepositoryAcls {
    [CmdletBinding(DefaultParameterSetName = 'PAT')]
    [OutputType([object[]])]
    param (
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $PAT,
        [Parameter(ParameterSetName = 'PAT')]
        [ValidateSet('FullAccess', 'FineGrained', 'ReadOnly')]
        [string]
        $TokenType = 'FullAccess',
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $Organization,
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $ProjectId,
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $RepositoryId
    )
    # If the token type is ReadOnly, write a warning and return null
    if ($TokenType -eq "ReadOnly") {
        Write-Warning "The ReadOnly token type does not have access to the Repositories ACLs API, returning null"
        return $null
    } else {
        $header = Get-AzDevOpsHeader -PAT $PAT
        $uri = "https://dev.azure.com/{0}/_apis/accesscontrollists/2e9eb7ed-3c0a-47d4-87c1-0ffdd275fd87?api-version=6.0" -f $Organization
        try {
            $response = Invoke-RestMethod -Uri $uri -Method Get -Headers $header -ContentType "application/json"
            # If the response is a string and not an object, throw an exception for authentication failure or project not found
            if ($response -is [string]) {
                throw "Authentication failed or project not found"
            }
            $thisRepoPerms = $response.value | where-object {($_.token -eq "repoV2/$($ProjectId)/$($RepositoryId)")}
        }
        catch {
            throw $_.Exception.Message
        }
        return $thisRepoPerms
    }
}
Export-ModuleMember -Function Get-AzDevOpsRepositoryAcls
# End of Function Get-AzDevOpsRepositoryAcls

<#
    .SYNOPSIS
    Check the existance of a file in an Azure DevOps repo

    .DESCRIPTION
    Check the existance of a file in an Azure DevOps repo using Azure DevOps Rest API

    .PARAMETER PAT
    Personal Access Token (PAT) for Azure DevOps

    .PARAMETER TokenType
    Token Type for Azure DevOps, can be FullAccess, FineGrained or ReadOnly

    .PARAMETER Organization
    Organization name for Azure DevOps

    .PARAMETER Project
    Project name for Azure DevOps

    .PARAMETER Repository
    Repository name for Azure DevOps

    .PARAMETER Path
    Path to file in repo

    .EXAMPLE
    Test-AzDevOpsFileExists -PAT $PAT -Organization $Organization -Project $Project -Repository $Repository -Path $Path

    .NOTES
    This function return $true if the file exists and $false if it does not
#>

function Test-AzDevOpsFileExists {
    [CmdletBinding(DefaultParameterSetName = 'PAT')]
    [OutputType([bool])]
    param (
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $PAT,
        [Parameter(ParameterSetName = 'PAT')]
        [ValidateSet('FullAccess', 'FineGrained', 'ReadOnly')]
        [string]
        $TokenType = 'FullAccess',
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $Organization,
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $Project,
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $Repository,
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $Path
    )
    $header = Get-AzDevOpsHeader -PAT $PAT
    Write-Verbose "Checking if file $Path exists in repo $Repository in project $Project"
    $uri = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories/$Repository/items?path=$Path&api-version=6.0"
    Write-Verbose "URI: $uri"
    try {
        $response = Invoke-RestMethod -Uri $uri -Method Get -Headers $header
    }
    catch {
        return $false
    }
    return $true
}
Export-ModuleMember -Function Test-AzDevOpsFileExists
# End of Function Test-AzDevOpsFileExists

<#
    .SYNOPSIS
    Get GitHub Advanced Security (GHAS) data for a repository

    .DESCRIPTION
    Get GitHub Advanced Security (GHAS) data for a repository using Azure DevOps Rest API

    .PARAMETER PAT
    Personal Access Token (PAT) for Azure DevOps

    .PARAMETER TokenType
    Token Type for Azure DevOps, can be FullAccess, FineGrained or ReadOnly

    .PARAMETER Organization
    Organization name for Azure DevOps

    .PARAMETER ProjectId
    Project ID for Azure DevOps

    .PARAMETER Repository
    Repository name for Azure DevOps

    .EXAMPLE
    Get-AzDevOpsRepositoryGhas -PAT $PAT -Organization $Organization -Project $Project -Repository $Repository
#>

Function Get-AzDevOpsRepositoryGhas {
    [CmdletBinding(DefaultParameterSetName = 'PAT')]
    [OutputType([object])]
    param (
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $PAT,
        [Parameter(ParameterSetName = 'PAT')]
        [ValidateSet('FullAccess', 'FineGrained', 'ReadOnly')]
        [string]
        $TokenType = 'FullAccess',
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $Organization,
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $ProjectId,
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $RepositoryId
    )
    # token is not FullAccess, write a warning and return null
    if ($TokenType -ne "FullAccess") {
        Write-Warning "The $TokenType token type does not have access to the Repositories API, returning null"
        return $null
    } else {
        $header = Get-AzDevOpsHeader -PAT $PAT
        $payload = @{
            contributionIds = @(
                "ms.vss-features.my-organizations-data-provider"
                "ms.vss-advsec.advanced-security-enablement-data-provider"
            )
            dataProviderContext = @{
                properties = @{
                    givenProjectId = $ProjectId
                    givenRepoId = $RepositoryId
                }
            }
        }
        $url = "https://dev.azure.com/$Organization/_apis/Contribution/HierarchyQuery?api-version=5.0-preview.1"
        try {
            $response = Invoke-RestMethod -Uri $url -Method Post -Headers $header -Body ($payload | ConvertTo-Json) -ContentType "application/json"
            # If the response is a string and not an object, throw an exception for authentication failure or project not found
            if ($response -is [string]) {
                throw "Authentication failed or project not found"
            }
        }
        catch {
            throw $_.Exception.Message
        }
        return $response.dataProviders.'ms.vss-advsec.advanced-security-enablement-data-provider'
    }
}
Export-ModuleMember -Function Get-AzDevOpsRepositoryGhas
# End of Function Get-AzDevOpsRepositoryGhas

<#
    .SYNOPSIS
    Get and export all Azure DevOps repos in a project with default, main and master branches and branch policies and export to JSON with 1 file per repo

    .DESCRIPTION
    Get and export all Azure DevOps repos in a project with default, main and master branches and branch policies and export to JSON using Azure DevOps Rest API and this modules functions

    .PARAMETER PAT
    Personal Access Token (PAT) for Azure DevOps

    .PARAMETER TokenType
    Token Type for Azure DevOps, can be FullAccess, FineGrained or ReadOnly

    .PARAMETER Organization
    Organization name for Azure DevOps

    .PARAMETER Project
    Project name for Azure DevOps

    .PARAMETER OutputPath
    Output path for JSON file

    .EXAMPLE
    Export-AzDevOpsReposAndBranchPolicies -PAT $PAT -Organization $Organization -Project $Project -OutputPath $OutputPath

    .NOTES
    This function returns an empty object if no branch policy is found for the branch
#>

function Export-AzDevOpsReposAndBranchPolicies {
    [CmdletBinding(DefaultParameterSetName = 'PAT')]
    param (
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $PAT,
        [Parameter(ParameterSetName = 'PAT')]
        [ValidateSet('FullAccess', 'FineGrained', 'ReadOnly')]
        [string]
        $TokenType = 'FullAccess',
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $Organization,
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $Project,
        [Parameter(Mandatory, ParameterSetName = 'PAT')]
        [string]
        $OutputPath
    )
    # If the parameter set is PAT, get the repositories
    if ($PSCmdlet.ParameterSetName -eq 'PAT') {
        $repos = Get-AzDevOpsRepos -PAT $PAT -TokenType $TokenType -Organization $Organization -Project $Project
    }
    $repos | ForEach-Object {
        if ($null -ne $_) {
            $repo = $_
            # Add ObjectType Azure.DevOps.Repo to repo object
            $repo | Add-Member -MemberType NoteProperty -Name ObjectType -Value "Azure.DevOps.Repo"
            Write-Verbose "Getting branch policy for repo $($repo.name)"
            If($repo.defaultBranch) {
                $branchPolicy = Get-AzDevOpsBranchPolicy -PAT $PAT -TokenType $TokenType -Organization $Organization -Project $Project -Repository $repo.id -Branch $repo.defaultBranch
            }
            $repo | Add-Member -MemberType NoteProperty -Name MainBranchPolicy -Value $branchPolicy
            # Add a property indicating if a file named README.md or README exists in the repo
            $readmeExists = ((Test-AzDevOpsFileExists -PAT $PAT -TokenType $TokenType -Organization $Organization -Project $Project -Repository $repo.id -Path "README.md") -or (Test-AzDevOpsFileExists -PAT $PAT -Organization $Organization -Project $Project -Repository $repo.id -Path "README"))
            $repo | Add-Member -MemberType NoteProperty -Name ReadmeExists -Value $readmeExists

            # Add a property indicating if a file named LICENSE or LICENSE.md exists in the repo
            $licenseExists = ((Test-AzDevOpsFileExists -PAT $PAT -TokenType $TokenType -Organization $Organization -Project $Project -Repository $repo.id -Path "LICENSE") -or (Test-AzDevOpsFileExists -PAT $PAT -Organization $Organization -Project $Project -Repository $repo.id -Path "LICENSE.md"))
            $repo | Add-Member -MemberType NoteProperty -Name LicenseExists -Value $licenseExists

            # Add a property for GitHub Advanced Security (GHAS) data if the token type is FullAccess
            if ($TokenType -eq "FullAccess") {
                $ghas = Get-AzDevOpsRepositoryGhas -PAT $PAT -TokenType $TokenType -Organization $Organization -ProjectId $repo.project.id -RepositoryId $repo.id
                $repo | Add-Member -MemberType NoteProperty -Name Ghas -Value $ghas
            } else {
                Write-Warning "The $TokenType token type does not have access to the GHAS API, returning null"
            }
            
            # Add a property with pipeline permissions
            $pipelinePermissions = Get-AzDevOpsRepositoryPipelinePermissions -PAT $PAT -TokenType $TokenType -Organization $Organization -ProjectId $repo.project.id -RepositoryId $repo.id
            $repo | Add-Member -MemberType NoteProperty -Name PipelinePermissions -Value $pipelinePermissions

            # Add a property with repo ACLs if the token type is not ReadOnly
            if ($TokenType -ne "ReadOnly") {
                $repoAcls = Get-AzDevOpsRepositoryAcls -PAT $PAT -TokenType $TokenType -Organization $Organization -ProjectId $repo.project.id -RepositoryId $repo.id
                $repo | Add-Member -MemberType NoteProperty -Name Acls -Value $repoAcls
            }
            
            # Export repo object to JSON file
            Write-Verbose "Exporting repo $($repo.name) to JSON as file $($repo.name).ado.repo.json"
            $repo | ConvertTo-Json -Depth 100 | Out-File -FilePath "$OutputPath\$($repo.name).ado.repo.json"
        }
    }
}
Export-ModuleMember -Function Export-AzDevOpsReposAndBranchPolicies
# End of Function Export-AzDevOpsReposAndBranchPolicies