All scripts
Azure AI 233

Azure Container Registry Repository Report

Lists every repository in your Azure Container Registries, how many tagged and untagged images each one has, and when the last image was pushed, so you can spot registries that need a cleanup or a retention policy.

Get-AzureContainerRegistryReport.ps1
<#
.SYNOPSIS
    Reports on Azure Container Registry repositories, image counts, and untagged images across a subscription.

.DESCRIPTION
    Connects to Azure, loops through every Container Registry in the subscription (or a single
    resource group if you pass one), and lists each repository along with how many tagged images
    it has, when the most recent image was pushed, and how many manifests have no tag at all.
    Untagged manifests are usually leftover build artifacts nobody is using but that are still
    costing you storage, so this is a fast way to find registries that need a cleanup or a
    retention policy.

.PARAMETER SubscriptionId
    The Azure subscription to scan. If you don't pass one, the script uses whatever subscription
    is currently active in your session.

.PARAMETER ResourceGroupName
    Optional. Limit the report to registries in a single resource group instead of the whole
    subscription.

.PARAMETER ExportPath
    Optional. Path to a CSV file. If provided, the report is also exported there instead of just
    printed to the console.

.EXAMPLE
    .\Get-AzureContainerRegistryReport.ps1

    Reports on every Container Registry in the current subscription.

.EXAMPLE
    .\Get-AzureContainerRegistryReport.ps1 -ResourceGroupName "rg-prod-containers" -ExportPath "C:\Reports\acr-report.csv"

    Reports on registries in a single resource group and saves the results to a CSV file.

.NOTES
    Author: Shehryar Hassan
    Requires the Az.Accounts and Az.ContainerRegistry modules, plus the Azure CLI (az) available
    on PATH for the manifest level detail, since Az.ContainerRegistry doesn't expose per-tag
    push dates directly.
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory = $false)]
    [string]$SubscriptionId,

    [Parameter(Mandatory = $false)]
    [string]$ResourceGroupName,

    [Parameter(Mandatory = $false)]
    [string]$ExportPath
)

if (-not (Get-AzContext)) {
    Write-Host "No active Azure session found. Connecting now." -ForegroundColor Yellow
    Connect-AzAccount | Out-Null
}

if ($SubscriptionId) {
    Set-AzContext -SubscriptionId $SubscriptionId | Out-Null
}

$registries = if ($ResourceGroupName) {
    Get-AzContainerRegistry -ResourceGroupName $ResourceGroupName
} else {
    Get-AzContainerRegistry
}

if (-not $registries) {
    Write-Host "No Container Registries found in scope." -ForegroundColor Yellow
    return
}

$results = @()

foreach ($registry in $registries) {
    Write-Host "Scanning registry: $($registry.Name)" -ForegroundColor Cyan

    $repositories = az acr repository list --name $registry.Name --output json 2>$null | ConvertFrom-Json

    if (-not $repositories) {
        continue
    }

    foreach ($repo in $repositories) {
        $manifests = az acr repository show-manifests --name $registry.Name --repository $repo --output json 2>$null | ConvertFrom-Json

        if (-not $manifests) {
            continue
        }

        $taggedCount = ($manifests | Where-Object { $_.tags -and $_.tags.Count -gt 0 }).Count
        $untaggedCount = ($manifests | Where-Object { -not $_.tags -or $_.tags.Count -eq 0 }).Count
        $lastPush = ($manifests | Sort-Object -Property lastUpdateTime -Descending | Select-Object -First 1).lastUpdateTime

        $results += [PSCustomObject]@{
            RegistryName   = $registry.Name
            ResourceGroup  = $registry.ResourceGroupName
            Repository     = $repo
            TaggedImages   = $taggedCount
            UntaggedImages = $untaggedCount
            LastPushedUtc  = $lastPush
        }
    }
}

$results | Sort-Object RegistryName, Repository | Format-Table -AutoSize

$staleUntagged = $results | Where-Object { $_.UntaggedImages -gt 0 }
if ($staleUntagged) {
    Write-Host ""
    Write-Host "$($staleUntagged.Count) repositories have untagged manifests worth reviewing for cleanup." -ForegroundColor Yellow
}

if ($ExportPath) {
    $results | Export-Csv -Path $ExportPath -NoTypeInformation
    Write-Host "Report exported to $ExportPath" -ForegroundColor Green
}

Read it before you run it, and test in a safe tenant first.