Public/Get-IOExpiringSSOCerts.ps1

function Get-IOExpiringSSOCerts {
    <#
    .SYNOPSIS
        Scans enterprise apps for SAML/SSO signing certificates that are expiring.
    .EXAMPLE
        Get-IOExpiringSSOCerts -DaysUntilExpiry 60
    .EXAMPLE
        Get-IOExpiringSSOCerts -DaysUntilExpiry 30 -ToCsv "sso-certs.csv"
    #>

    [CmdletBinding()]
    param(
        [ValidateRange(1, 3650)]
        [int]$DaysUntilExpiry = 30,

        [switch]$IncludeExpired,

        [string]$ToCsv
    )

    $cmdName = $MyInvocation.MyCommand.Name
    Write-IOLog "Scanning enterprise apps for SSO certificates expiring within $DaysUntilExpiry days..." -Level Info -Component $cmdName

    $now     = [datetime]::UtcNow
    $cutoff  = $now.AddDays($DaysUntilExpiry)
    $results = [System.Collections.Generic.List[PSCustomObject]]::new()

    # Get service principals that have SAML SSO configured
    $sps = Invoke-IOGraphRequest -Uri "v1.0/servicePrincipals?`$select=id,displayName,appId,preferredSingleSignOnMode,keyCredentials,passwordCredentials"

    foreach ($sp in $sps) {
        # Only check apps with SAML SSO
        if ($sp.preferredSingleSignOnMode -ne 'saml' -and -not $sp.keyCredentials) { continue }

        if ($sp.keyCredentials) {
            foreach ($cred in $sp.keyCredentials) {
                if (-not $cred.endDateTime) { continue }

                $endDate   = [datetime]::Parse($cred.endDateTime, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AssumeUniversal)
                $isExpired = $endDate -lt $now
                $isExpiring = $endDate -le $cutoff

                if ($isExpiring -or ($IncludeExpired -and $isExpired)) {
                    $results.Add([PSCustomObject]@{
                        ApplicationName   = $sp.displayName
                        ApplicationId     = $sp.appId
                        ObjectId          = $sp.id
                        SSOMode           = $sp.preferredSingleSignOnMode
                        CredentialType    = $cred.type
                        Usage             = $cred.usage
                        KeyId             = $cred.keyId
                        ExpiryDate        = $endDate.ToString('yyyy-MM-dd')
                        DaysRemaining     = [math]::Floor(($endDate - $now).TotalDays)
                        Status            = if ($isExpired) { 'EXPIRED' } else { 'EXPIRING' }
                    })
                }
            }
        }
    }

    $sorted = $results | Sort-Object DaysRemaining
    Export-IOResult -Data $sorted -ToCsv $ToCsv -CommandName $cmdName
}