public/Get-MsecAzureDevOpsExtension.ps1

function Get-MsecAzureDevOpsExtension {
    <#
    .SYNOPSIS
        Marketplace extensions installed in an Azure DevOps organization, with the access each
        one holds - one row per extension.

    .DESCRIPTION
        An extension is third-party code running inside your organization with delegated access
        to it. The scopes it was granted at install time are permanent until someone uninstalls
        it, they apply organization-wide, and nothing prompts anyone to review them again.

        A publisher with `vso.serviceendpoint_manage` can read and rewrite service connections;
        one with `vso.code_manage` can rewrite repositories. Those are not hypothetical
        permissions - they are what the extension already has.

        ACCESS IS DERIVED FROM THE SCOPE SUFFIXES, and that derivation is this command's
        judgement rather than something the API states:

            Manage any *_manage scope - full control of that resource type
            Write any *_write or *_execute scope - can change things or run code
            Read read-only scopes
            None no scopes declared

        The raw Scopes are always returned alongside it, because the grouping is a convenience
        and the scope list is the fact.

        MICROSOFT-PUBLISHED IS NOT THE SAME AS SAFE, but it is the line most reviews draw first,
        so IsMicrosoftPublisher is a column rather than a filter. Judging the publisher is the
        reader's job.

    .PARAMETER Organization
        Azure DevOps organization name: the path segment after dev.azure.com/.

    .PARAMETER ThirdPartyOnly
        Exclude extensions published by Microsoft. On a real organization 43 of 50 were
        Microsoft-published, and the remainder is where a review usually starts.

    .EXAMPLE
        Connect-Msec -KeyVaultName kv-msec
        Get-MsecAzureDevOpsExtension -Organization 'contoso' |
            Sort-Object Access, Publisher | Format-Table Publisher, ExtensionName, Access, Scopes

    .EXAMPLE
        # Third-party code that can rewrite service connections or repositories.
        Get-MsecAzureDevOpsExtension -Organization 'contoso' -ThirdPartyOnly |
            Where-Object { $_.Access -in 'Manage', 'Write' }

    .OUTPUTS
        PSCustomObject per extension, PSTypeName 'MsecAzureDevOpsExtension'.

    .NOTES
        Needs Connect-Msec and organization membership. No extra permission: the extension
        management API is readable by any member, unlike repositories and service connections.

        Extensions are installed per ORGANIZATION, so there is no project dimension here.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, Position = 0)]
        [string] $Organization,

        [switch] $ThirdPartyOnly
    )

    Assert-MsecSession

    # Extension management lives on its own host, not dev.azure.com.
    $extensions = @(Invoke-MsecAzureDevOpsRequest -Organization $Organization `
                        -HostName 'extmgmt.dev.azure.com' -ApiVersion '7.1-preview.1' `
                        -Path '_apis/extensionmanagement/installedextensions')

    if (-not $extensions.Count) {
        Write-Warning "No extensions returned for '$Organization'. That is 'nothing was read', not 'none installed' - every organization has at least the built-in ones."
        return
    }

    foreach ($extension in $extensions) {
        $scopes = @($extension.scopes)

        $access =
            if     (-not $scopes.Count)                       { 'None' }
            elseif ($scopes -match '_manage$')                { 'Manage' }
            elseif ($scopes -match '_(write|execute)$')       { 'Write' }
            else                                              { 'Read' }

        # installState.flags is a comma-separated string: 'none', 'disabled', 'trusted'.
        $flags = [string] $extension.installState.flags

        $row = [PSCustomObject]@{
            PSTypeName           = 'MsecAzureDevOpsExtension'
            Organization         = $Organization
            Publisher            = $extension.publisherName
            ExtensionName        = $extension.extensionName
            # Derived, not stated by the API - see the help.
            Access               = $access
            Scopes               = ($scopes | Sort-Object) -join ', '
            # Microsoft Devlabs is Microsoft-published but explicitly experimental, so it is
            # counted as Microsoft here and left visible in Publisher for the reader to weigh.
            IsMicrosoftPublisher = [bool] ($extension.publisherId -eq 'ms' -or $extension.publisherName -match '^Microsoft')
            # A disabled extension keeps its grants and can be re-enabled without re-consent.
            IsDisabled           = $flags -match 'disabled'
            Version              = $extension.version
            LastPublished        = $extension.lastPublished
            PublisherId          = $extension.publisherId
            ExtensionId          = $extension.extensionId
            InstallFlags         = $flags
        }

        if ($ThirdPartyOnly -and $row.IsMicrosoftPublisher) { continue }
        $row
    }
}