All scripts
Governance 543

Get-EntraAppSecretExpiryReport

Scans every Entra ID app registration and flags client secrets and certificates that are expired or expiring soon, so you catch broken automation before it breaks.

Get-EntraAppSecretExpiryReport.ps1
<#
.SYNOPSIS
    Reports Entra ID app registrations with client secrets or certificates that are expired or expiring soon.
.DESCRIPTION
    Connects to Microsoft Graph and checks every app registration in the tenant for
    client secrets and certificates that have already expired or will expire within
    a chosen number of days. Expired app credentials are one of the most common causes
    of silent automation failures, this catches them before they cause an outage.
.PARAMETER DaysThreshold
    Number of days ahead to check for upcoming expiry. Defaults to 30.
.PARAMETER ExportPath
    Optional path to export the results as CSV.
.EXAMPLE
    .\Get-EntraAppSecretExpiryReport.ps1 -DaysThreshold 60 -ExportPath C:\Reports\AppSecrets.csv
.AUTHOR
    Shehryar Hassan
#>

[CmdletBinding()]
param(
    [int]$DaysThreshold = 30,
    [string]$ExportPath
)

# Requires: Install-Module Microsoft.Graph -Scope CurrentUser
Import-Module Microsoft.Graph.Applications -ErrorAction Stop

Connect-MgGraph -Scopes "Application.Read.All" -NoWelcome

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

Write-Host "Checking app registrations for credentials expiring within $DaysThreshold days..." -ForegroundColor Cyan

$apps = Get-MgApplication -All -Property Id, DisplayName, AppId, PasswordCredentials, KeyCredentials

foreach ($app in $apps) {
    foreach ($secret in $app.PasswordCredentials) {
        if ($secret.EndDateTime -le $cutoffDate) {
            $daysLeft = [math]::Round(((Get-Date $secret.EndDateTime) - (Get-Date)).TotalDays, 0)
            $results += [PSCustomObject]@{
                AppName        = $app.DisplayName
                AppId          = $app.AppId
                CredentialType = "Secret"
                CredentialName = $secret.DisplayName
                ExpiresOn      = $secret.EndDateTime
                DaysLeft       = $daysLeft
                Status         = if ($daysLeft -lt 0) { "Expired" } else { "Expiring soon" }
            }
        }
    }

    foreach ($cert in $app.KeyCredentials) {
        if ($cert.EndDateTime -le $cutoffDate) {
            $daysLeft = [math]::Round(((Get-Date $cert.EndDateTime) - (Get-Date)).TotalDays, 0)
            $results += [PSCustomObject]@{
                AppName        = $app.DisplayName
                AppId          = $app.AppId
                CredentialType = "Certificate"
                CredentialName = $cert.DisplayName
                ExpiresOn      = $cert.EndDateTime
                DaysLeft       = $daysLeft
                Status         = if ($daysLeft -lt 0) { "Expired" } else { "Expiring soon" }
            }
        }
    }
}

$results = $results | Sort-Object DaysLeft

if ($results.Count -eq 0) {
    Write-Host "No app credentials expiring within $DaysThreshold days." -ForegroundColor Green
}
else {
    $results | Format-Table AppName, CredentialType, ExpiresOn, DaysLeft, Status -AutoSize

    $expiredCount = ($results | Where-Object { $_.Status -eq "Expired" }).Count
    if ($expiredCount -gt 0) {
        Write-Host "$expiredCount credential(s) have already expired. Fix these first." -ForegroundColor Red
    }
}

if ($ExportPath) {
    $results | Export-Csv -Path $ExportPath -NoTypeInformation
    Write-Host "Exported to $ExportPath" -ForegroundColor Cyan
}

Disconnect-MgGraph | Out-Null

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