Scenario

A scripted method to find Azure AD Application Registrations with expired credentials.

Applying to both Password and Certificate-based application credentials

Returning a summary of applications registered including the:

  • Total
  • Those with credentials
  • Those with no active credentials (all expired)
  • Those expiring soon; and
  • Those expiring soon without a future credential

The threshold for finding those expiring in future is to be adjustable.

Sample Output

App Credential expiry lookahead: 30 days.
Total app registrations               : 100
 with credentials                     : 50
 with all expired credentials         : 20
 expiring in the lookahead            : 15
  and without a carryover credential  : 5

Native Microsoft Graph

List Applications
MS Docs Reference
GET https://graph.microsoft.com/v1.0/applications
Least Privileged Scope Delegated - Application.Read.All

Code

using namespace System.Collections.Generic
$ErrorActionPreference = 'stop'

$url = 'https://graph.microsoft.com/v1.0/applications'
# Least privileged permission: Delegated | Application.Read.All

# Look ahead for credential expiry (in days)
$AppCredentialExpiryLookahead = 30

# Utilise MSAL
$bearer_token = 'ey......' # Obtain via MSAL
$headers = @{
    Authorization = "Bearer $bearer_token"
}

$resultList = [List[PSCustomObject]]::new()

try {
    do {
        $result = Invoke-RestMethod -Method GET -Uri $url -headers $headers
        $result.Value.ForEach({
                $resultList.Add($_)
            })
        $url = $result.'@Odata.NextLink'
    } while ($result.'@Odata.NextLink')
    $resultList.Count
}
catch {
    $failureMsg = "$((ConvertFrom-Json $_).error.code)`n$((ConvertFrom-Json $_).error.message)"
    Write-Host -ForegroundColor Red $failureMsg
}

$appRegistrationsWithCredentialsList = [List[PSCustomObject]]::new()

$AppCredentialExpiryLookaheadDateTime = (Get-Date).AddDays($AppCredentialExpiryLookahead)

if ($resultList.Count -ne 0) {
    $resultList | ForEach-Object {
        # If the App Registration has credentials
        if ($_.passwordCredentials.Count -gt 0 -or $_.keyCredentials.Count -gt 0) {
            # Retreive the number of credentials of each kind
            $_ | Add-Member -NotePropertyName passwordCredentialsCount $_.passwordCredentials.Count
            $_ | Add-Member -NotePropertyName keyCredentialsCount $_.keyCredentials.Count
    
            $_ | Add-Member -NotePropertyName willExpireInThreshold $false -Force
    
            # Find those password credentials that have expired or will.
            $expiredPasswordCredentialCount = 0
            foreach ($passwordCredential in $_.passwordCredentials) {
                $passwordCredential | Add-Member -NotePropertyName hasExpired -NotePropertyValue $false
                if ((Get-Date) -gt $passwordCredential.endDateTime) {
                    $passwordCredential | Add-Member -NotePropertyName hasExpired -NotePropertyValue $true -Force
                    $expiredPasswordCredentialCount++
                }
                else {
                    # If it will expire in the provided look ahead
                    if ($AppCredentialExpiryLookaheadDateTime -gt $passwordCredential.endDateTime ) {
                        $_ | Add-Member -NotePropertyName willExpireInThreshold $true -Force
                    }
                }
            }
    
            # Find those certificate credentials that have expired or will.
            $expiredKeyCredentialCount = 0
            foreach ($keyCredential in $_.keyCredentials) {
                $keyCredential | Add-Member -NotePropertyName hasExpired -NotePropertyValue $false
                if ((Get-Date) -gt $keyCredential.endDateTime) {
                    $keyCredential | Add-Member -NotePropertyName hasExpired -NotePropertyValue $true -Force
                    $expiredKeyCredentialCount++
                }
                else {
                    # If it will expire in the provided look ahead
                    if ($AppCredentialExpiryLookaheadDateTime -gt $keyCredential.endDateTime ) {
                        $_ | Add-Member -NotePropertyName willExpireInThreshold $true -Force
                    }
                }
            }
    
            # If the App has Password or Certificate credentials and all of them have expired.
            if ( ($_.PasswordCredentialsCount -ne 0 -and $_.PasswordCredentialsCount -eq $expiredPasswordCredentialCount) -or `
                ($_.KeyCredentialsCount -ne 0 -and $_.KeyCredentialsCount -eq $expiredKeyCredentialCount) ) {
                $_ | Add-Member -NotePropertyName hasAllExpiredCredentials -NotePropertyValue $true
            }
    
            $appRegistrationsWithCredentialsList.Add($_)
        }
    }
    
    $appRegistrationsWithAllExpiredCredentials = [Linq.Enumerable]::ToList([Linq.Enumerable]::Where($appRegistrationsWithCredentialsList, `
                [Func[Object, bool]] { param($x); return ($x.hasAllExpiredCredentials -eq $true) }
        ))
    
    $appRegistrationsWithCredentialExpiryInThreshold = [Linq.Enumerable]::ToList([Linq.Enumerable]::Where($appRegistrationsWithCredentialsList, `
                [Func[Object, bool]] { param($x); return ($x.willExpireInThreshold -eq $true) }
        ))
        
    # Those expiring credentials without a newer one available
    $appRegistrationsWithCredentialExpiryInThreshold | ForEach-Object {
        $_ | Add-Member -NotePropertyName shouldAddCredential $true -Force
        foreach ($passwordCredential in $_.PasswordCredentials) {
            if ($passwordCredential.endDateTime -gt $AppCredentialExpiryLookaheadDateTime) {
                $_ | Add-Member -NotePropertyName shouldAddCredential $false -Force
            }
        }
        foreach ($keyCredential in $_.KeyCredentials) {
            if ($keyCredential.endDateTime -gt $AppCredentialExpiryLookaheadDateTime) {
                $_ | Add-Member -NotePropertyName shouldAddCredential $false -Force
            }
        }
    }
    
    $withoutCarryoverCredential = [Linq.Enumerable]::ToList([Linq.Enumerable]::Where($appRegistrationsWithCredentialExpiryInThreshold, `
                [Func[Object, bool]] { param($x); return ($x.shouldAddCredential -eq $true) }
        ))
    
    Write-Host -ForegroundColor Yellow "App Credential expiry lookahead: $AppCredentialExpiryLookahead days.`n"
    
    Write-Host -ForegroundColor Yellow "Total applications found              : $($resultList.Count)"
    Write-Host -ForegroundColor Yellow " with credentials                     : $($appRegistrationsWithCredentialsList.Count)"
    Write-Host -ForegroundColor Yellow "  with all expired credentials        : $($appRegistrationsWithAllExpiredCredentials.Count)"
    Write-Host -ForegroundColor Yellow "  expiring in the lookahead           : $($appRegistrationsWithCredentialExpiryInThreshold.Count)"
    Write-Host -ForegroundColor Yellow "   and without a carryover credential : $($withoutCarryoverCredential.Count)`n"

    # $appRegistrationsList
    # $appRegistrationsWithAllExpiredCredentials
    # $appRegistrationsWithCredentialExpiryInThreshold
    # $withoutCarryoverCredential

}

Azure AD PowerShell (Deprecated)

Deprecated

This snippet depends upon the Azure AD or AzureADPreview PowerShell modules. These modules use elements of the AzureAD Graph API which is being retired June 2022.

Code

using namespace System.Collections.Generic
using namespace Microsoft.Open.AzureAD.Model
using namespace Microsoft.Open.Azure.AD.CommonLibrary
$ErrorActionPreference = 'stop'

try {
    Get-AzureADTenantDetail | Out-Null
}
catch [AadNeedAuthenticationException] {
    Connect-AzureAD
}

# Look ahead for expiry (in days)
$AppCredentialExpiryLookahead = 30

$AppCredentialExpiryLookaheadDateTime = (Get-Date).AddDays($AppCredentialExpiryLookahead)
$appRegistrations = Get-AzureADApplication -All $true
$appRegistrationsList = [List[DirectoryObject]]::new()

$appRegistrations | ForEach-Object {
    # If the App Registration has credentials
    if ($_.PasswordCredentials.Count -gt 0 -or $_.KeyCredentials.Count -gt 0) {
        # Retreive the number of credentials of each kind
        $_ | Add-Member -NotePropertyName PasswordCredentialsCount $_.PasswordCredentials.Count
        $_ | Add-Member -NotePropertyName KeyCredentialsCount $_.KeyCredentials.Count

        $_ | Add-Member -NotePropertyName WillExpireInThreshold $false -Force

        # Find those password credentials that have expired or will.
        $expiredPasswordCredentialCount = 0
        foreach ($passwordCredential in $_.PasswordCredentials) {
            $passwordCredential | Add-Member -NotePropertyName HasExpired -NotePropertyValue $false
            if ((Get-Date) -gt $passwordCredential.EndDate) {
                $passwordCredential | Add-Member -NotePropertyName HasExpired -NotePropertyValue $true -Force
                $expiredPasswordCredentialCount++
            }
            else {
                # If it will expire in the provided look ahead
                if ($AppCredentialExpiryLookaheadDateTime -gt $passwordCredential.EndDate ) {
                    $_ | Add-Member -NotePropertyName WillExpireInThreshold $true -Force
                }
            }
        }

        # Find those certificate credentials that have expired or will.
        $expiredKeyCredentialCount = 0
        foreach ($keyCredential in $_.KeyCredentials) {
            $keyCredential | Add-Member -NotePropertyName HasExpired -NotePropertyValue $false
            if ((Get-Date) -gt $keyCredential.EndDate) {
                $keyCredential | Add-Member -NotePropertyName HasExpired -NotePropertyValue $true -Force
                $expiredKeyCredentialCount++
            }
            else {
                # If it will expire in the provided look ahead
                if ($AppCredentialExpiryLookaheadDateTime -gt $keyCredential.EndDate ) {
                    $_ | Add-Member -NotePropertyName WillExpireInThreshold $true -Force
                }
            }
        }

        # If the App has Password or Certificate credentials and all of them have expired.
        if ( ($_.PasswordCredentialsCount -ne 0 -and $_.PasswordCredentialsCount -eq $expiredPasswordCredentialCount) -or `
            ($_.KeyCredentialsCount -ne 0 -and $_.KeyCredentialsCount -eq $expiredKeyCredentialCount) ) {
            $_ | Add-Member -NotePropertyName HasAllExpiredCredentials -NotePropertyValue $true
        }

        $appRegistrationsList.Add($_)
    }
}

$appRegistrationsWithAllExpiredCredentials = [Linq.Enumerable]::ToList([Linq.Enumerable]::Where($appRegistrationsList, `
            [Func[Object, bool]] { param($x); return ($x.HasAllExpiredCredentials -eq $true) }
    ))

$appRegistrationsWithCredentialExpiryInThreshold = [Linq.Enumerable]::ToList([Linq.Enumerable]::Where($appRegistrationsList, `
            [Func[Object, bool]] { param($x); return ($x.WillExpireInThreshold -eq $true) }
    ))
    
# Those expiring credentials without a newer one available
$appRegistrationsWithCredentialExpiryInThreshold | ForEach-Object {
    $_ | Add-Member -NotePropertyName ShouldAddCredential $true -Force
    foreach ($passwordCredential in $_.PasswordCredentials) {
        if ($passwordCredential.EndDate -gt $AppCredentialExpiryLookaheadDateTime) {
            $_ | Add-Member -NotePropertyName ShouldAddCredential $false -Force
        }
    }
    foreach ($keyCredential in $_.KeyCredentials) {
        if ($keyCredential.EndDate -gt $AppCredentialExpiryLookaheadDateTime) {
            $_ | Add-Member -NotePropertyName ShouldAddCredential $false -Force
        }
    }
}

$withoutCarryoverCredential = [Linq.Enumerable]::ToList([Linq.Enumerable]::Where($appRegistrationsWithCredentialExpiryInThreshold, `
            [Func[Object, bool]] { param($x); return ($x.ShouldAddCredential -eq $true) }
    ))

Write-Host -ForegroundColor Yellow "App Credential expiry lookahead: $AppCredentialExpiryLookahead days.`n"

Write-Host -ForegroundColor Yellow "Total app registrations               : $($appRegistrations.Count)"
Write-Host -ForegroundColor Yellow " with credentials                     : $($appRegistrationsList.Count)"
Write-Host -ForegroundColor Yellow "  with all expired credentials        : $($appRegistrationsWithAllExpiredCredentials.Count)"
Write-Host -ForegroundColor Yellow "  expiring in the lookahead           : $($appRegistrationsWithCredentialExpiryInThreshold.Count)"
Write-Host -ForegroundColor Yellow "   and without a carryover credential : $($withoutCarryoverCredential.Count)"

# $appRegistrationsList
# $appRegistrationsWithAllExpiredCredentials
# $appRegistrationsWithCredentialExpiryInThreshold
# $withoutCarryoverCredential