All scripts
Azure AI 619

Get-AzureKeyVaultExpiringSecretsReport

Scans one or more Azure Key Vaults for secrets and certificates that are expiring soon, so you can rotate them on your own schedule instead of finding out when an app breaks in production.

Get-AzureKeyVaultExpiringSecretsReport.ps1
<#
.SYNOPSIS
    Reports Key Vault secrets and certificates that are expiring soon.
.DESCRIPTION
    Connects to Azure, scans one or more Key Vaults for secrets and certificates,
    and lists any that expire within a given number of days. Run this on a
    schedule so nothing quietly expires and breaks an app in production.
.AUTHOR
    Shehryar Hassan
.EXAMPLE
    .\Get-AzureKeyVaultExpiringSecretsReport.ps1 -VaultNames "prod-vault","shared-vault" -DaysThreshold 30
#>

param(
    [Parameter(Mandatory = $true)]
    [string[]]$VaultNames,

    [int]$DaysThreshold = 30
)

if (-not (Get-Module -ListAvailable -Name Az.KeyVault)) {
    Write-Error "Az.KeyVault module is not installed. Run: Install-Module Az.KeyVault -Scope CurrentUser"
    exit 1
}

Import-Module Az.KeyVault -ErrorAction Stop

$context = Get-AzContext
if (-not $context) {
    Write-Host "No active Azure session found, connecting now."
    Connect-AzAccount | Out-Null
}

$cutoff = (Get-Date).AddDays($DaysThreshold)
$results = @()

foreach ($vaultName in $VaultNames) {
    Write-Host "Checking vault: $vaultName"

    try {
        $secrets = Get-AzKeyVaultSecret -VaultName $vaultName -ErrorAction Stop
    } catch {
        Write-Warning "Could not read secrets from $vaultName. Check access policy or RBAC role. $($_.Exception.Message)"
        continue
    }

    foreach ($secret in $secrets) {
        if ($secret.Expires -and $secret.Expires -le $cutoff) {
            $results += [PSCustomObject]@{
                VaultName = $vaultName
                ItemType  = "Secret"
                Name      = $secret.Name
                Expires   = $secret.Expires
                DaysLeft  = [math]::Round(($secret.Expires - (Get-Date)).TotalDays, 1)
                Enabled   = $secret.Enabled
            }
        }
    }

    try {
        $certificates = Get-AzKeyVaultCertificate -VaultName $vaultName -ErrorAction Stop
    } catch {
        Write-Warning "Could not read certificates from $vaultName. $($_.Exception.Message)"
        continue
    }

    foreach ($cert in $certificates) {
        if ($cert.Expires -and $cert.Expires -le $cutoff) {
            $results += [PSCustomObject]@{
                VaultName = $vaultName
                ItemType  = "Certificate"
                Name      = $cert.Name
                Expires   = $cert.Expires
                DaysLeft  = [math]::Round(($cert.Expires - (Get-Date)).TotalDays, 1)
                Enabled   = $cert.Enabled
            }
        }
    }
}

if ($results.Count -eq 0) {
    Write-Host "Nothing expiring within $DaysThreshold days across $($VaultNames.Count) vault(s)."
} else {
    $results = $results | Sort-Object Expires
    $results | Format-Table -AutoSize

    $exportPath = ".\KeyVaultExpiringItems_$(Get-Date -Format 'yyyyMMdd').csv"
    $results | Export-Csv -Path $exportPath -NoTypeInformation
    Write-Host "Report saved to $exportPath"
}

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