Get-AllInstalledExtensioons.ps1

<#
    .Synopsis
        Get all installed extensions accross the Azure DevOps Organizations
    .Description
        Calling this function will return list of all installed extensions accross the organizations. It will help understand what is happening around the organizational elevated access for the projects.
    .Parameter Token
        [Mandatory] Specifies the access token to use for the communication. See description above for any access token requirements and run 'Get-Help Get-Token -Detailed' for additional detailed notes. (Mandatory)
    .Parameter ApprovedpublisherId
        [Mandatory] Comma/Delimeted separated list of approved PublishersID to differentiate between approved and unapproved etxnsions as output
    .Parameter listOfOrganizations
    Comma/Delimeted separated list of Azure DevOps organizatins if you want to run for specific list of organizations.
    .Example
        Get-AllInstalledExtensions -PAT <TOKEN> -approvedpublisherId ms,ms-devlabs,AammirMirza,AmazonWebServices,azsdktm,AzureSynapseWorkspace

        # Using with list of approved ExtensionsID

        Output:

        Executing for mv-shoppable-apps
        |-- Azure Artifacts | Microsoft
        |-- Package Management for TFS 2017 and TFS 2018 | Microsoft
        |-- Pipeline Artifacts | Microsoft
        |-- Aex Code Mapper | Microsoft
        |-- Aex platform | Microsoft
        |-- Aex user management | Microsoft
        |-- User Management | Microsoft
        |-- Bill | Microsoft
        |-- Gallery extensions for Portal Extension | Microsoft
        |-- Distributed Task | Microsoft
        |-- Service connections | Microsoft
        |-- Extensions | Microsoft
        |-- Favorites | Microsoft
        |-- Enterprise Administration | Microsoft
        |-- Package Search | Microsoft
        |-- Pipelines | Microsoft
        |-- Release | Microsoft
        |-- Release Artifacts | Microsoft
        |-- Release Management | Microsoft
        |-- Service Hooks | Microsoft
        |-- Service Hooks Web UI | Microsoft
        |-- Docker Tools | Microsoft
##[error] |-- SonarQube build breaker | Simon de Lang
##[error] |-- Tokenize in archive | Solidify Labs
##[error] |-- SonarQube | SonarSource
##[error] |-- Synopsys Detect | Synopsys Inc.
    .Example
        Get-AllInstalledExtensions -PAT <TOKEN> -approvedpublisherId ms,ms-devlabs,AammirMirza,AmazonWebServices,azsdktm,AzureSynapseWorkspace -listOfOrganizations Organization1,Organization2,Orgaanization3
        # Using with the list of organizations instead of all organizations
#>


function Get-AllInstalledExtensions {
    param (
        [Parameter(Mandatory = $true)]
        [string]$PAT,
        [Parameter(Mandatory = $true)]
        $approvedpublisherId = @(),
        $listOfOrganizations = @()
    )
################################################################
## Signature
$t = @"
    Package designed and managed
     _ _ _
    | |__ _ _ /_\ __ _ _ __ (_) _ _
    | '_ \| || | / _ \ / _` || ' \ | || '_|
    |_.__/ \_, | /_/ \_\\__,_||_|_|_||_||_|
           |__/
            Azure DevOps extensions management. Validate,
            Verify and Action for newly installed extensions
            accross organizations.

            Please suggest improvements at aammir.mirza@hotmail.com

"@

      for ($i=0;$i -lt $t.length;$i++) {
      if ($i%2) {
       $c = "white"
      }
      elseif ($i%5) {
       $c = "white"
      }
      elseif ($i%7) {
       $c = "white"
      }
      else {
         $c = "white"
      }
      write-host $t[$i] -NoNewline -ForegroundColor $c
      }
################################################################

    $AzureDevOpsAuthenicationHeader = @{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($PAT)")) }

    # Getting list of all organization within the AzDO
    $uProfile = Invoke-RestMethod -Uri "https://app.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=6.0" -Method get -Headers $AzureDevOpsAuthenicationHeader
    # $uProfile.publicAlias
    $organizationtoProcess = @()
    $allOrganization = Invoke-RestMethod -Uri "https://app.vssps.visualstudio.com/_apis/accounts?memberId=$($uprofile.publicAlias)&api-version=6.0" -Method get -Headers $AzureDevOpsAuthenicationHeader
    if (!$listOfOrganizations) {
        $organizationtoProcess = $allOrganization.value.accountName
    }
    else {
        $organizationtoProcess = $listOfOrganizations
    }
    Write-Host "---------------------------------------------"
    get-help Get-AllInstalledExtensions -Detailed
    Write-Host "---------------------------------------------"

    Write-Host "---------------------------------------------"
    Write-Host "##[command]Summary"
    Write-Host "##[command]Number_of_Organizations : $($organizationtoProcess.count)"
    Write-Host "---------------------------------------------"
    try {
        foreach ($Organization in $organizationtoProcess) {
            try {
                Write-Host "##[command]Executing for $($Organization)"
                $UriInstalledExtensions = "https://extmgmt.dev.azure.com/$($Organization)/_apis/extensionmanagement/installedextensions?api-version=6.0-preview.1"
                $InstalledExtensionsResult = Invoke-RestMethod -Uri $UriInstalledExtensions -Method get -Headers $AzureDevOpsAuthenicationHeader
                $result = @()

                Foreach ($extension in $InstalledExtensionsResult.value) {
                    $extName = $($extension.extensionName).Replace("'", "")
                    $extPublisherName = $($extension.publisherName).Replace("'", "")
                    $extPublisherId = $($extension.publisherId).Replace("'", "")
                    if (!$approvedpublisherId) {
                        Write-Host " |-- $($extName) | $($extPublisherName)"
                    }
                    elseif ($extPublisherId -in $approvedpublisherId) {
                        Write-Host " |-- $($extName) | $($extPublisherName)"
                    }
                    else {
                        Write-Host "##[error] |-- $($extName) | $($extPublisherName)"
                    }

                    #Write-Host ('{0,30}{10}' -f $extName,$extPublisherName)
                    $obj = [PSCustomObject]@{
                        Organization           = "[$($Organization)](https://dev.azure.com/$($Organization)/_settings/extensions)"
                        ExtensionId            = $extension.extensionId
                        ExtensionName          = $extName
                        ExtensionPublisherName = $extPublisherName
                        PublisherId            = $extension.publisherId
                        ExtensionVersion       = $extension.version
                        ExtensionLastPublished = $extension.lastPublished
                    }
                    $result += $obj
                }
                # $result | Export-Csv -Path "C:\Custom_Data\Scripts\allExtensions.csv" -NoTypeInformation -Append
                $result | Export-Csv -Path __AllInstalledExtensions.csv -NoTypeInformation -Append
                $result | ConvertTo-Json | Out-File "$($Organization).json"
                Continue
            }
            catch {
                Write-Host "##[error]!!! Exeption raised for $($Organization) !!!"
                Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
                Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
            }
        }
        $allFiles = Get-ChildItem *.json | Select-Object -ExpandProperty Name
        foreach ($item in $allFiles) {
            $allResults += Get-Content -Path $item -Raw | ConvertFrom-Json
            $allResults | ConvertTo-Json -Depth 5 | Out-File -FilePath latestInstalledExtensions.json
            Write-Host "##[warning]Completed parsing for $($item)"
        }
    }
    catch {
        Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
        Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
    }
}