Entra / Microsoft 365 · Applications
Find unused service principals
Find service principals that have not signed in to Microsoft 365 in the last year and generate statistics about service principals in the tenant.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-MgGraph -Scopes AuditLog.Read.All, Application.Read.All
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
param([string] $TenantId = "",[int] $LookbackDays = 7)Connect-MgGraph -Scopes AuditLog.Read.All, Application.Read.AllWrite-Host "Finding service principals..."[array]$ServicePrincipals = Get-MgServicePrincipal -All -PageSize 500 | Sort-Object AppId$SPHash = @{}ForEach ($SP in $ServicePrincipals) {$SPHash.Add($SP.AppId, $SP.DisplayName)}$CheckDate = [datetime]::UtcNow.AddDays(-$LookbackDays).ToString("s") + "Z"# Output reportWrite-Host "Fetching service principal sign-in activity data..."$Report = [System.Collections.Generic.List[Object]]::new()[array]$SPSignInLogs = Get-MgBetaReportServicePrincipalSignInActivity -Filter "(lastSignInActivity/lastSignInDateTime lt $CheckDate)" -All -PageSize 500If (!($SPSignInLogs)) {Write-Host "No sign-ins found for service principals"Break} Else {Write-Host ("Found {0} sign-ins for service principals" -f $SPSignInLogs.Count)}Write-Host "Analyzing data..."ForEach ($SPSignIn in $SPSignInLogs) {$SPName = $SPHash[$SPSignIn.appId]$DaysSince = (New-TimeSpan $SPSignIn.lastSignInActivity.lastSignInDateTime).Days$ReportLine = [PSCustomObject]@{'Service Principal Name' = $SPNameAppId = $SPSignin.AppIdLastSignIn = Get-Date $SPSignIn.lastSignInActivity.lastSignInDateTime -format 'dd-MMM-yyyy HH:mm:ss''Days Since Last Sign-In' = $DaysSince}$Report.Add($ReportLine)}$TenantReport = [System.Collections.Generic.List[Object]]::new()$HomeTenant = (Get-MgOrganization).DisplayName[array]$TenantIds = $ServicePrincipals | Sort-Object AppOwnerOrganizationId -Unique | Select-Object -ExpandProperty AppOwnerOrganizationIdForEach ($TenantId in $TenantIds) {$NumberApps = ($ServicePrincipals | Where-Object {$_.AppOwnerOrganizationId -eq $TenantId}).Count$Uri = ("https://graph.microsoft.com/V1.0/tenantRelationships/findTenantInformationByTenantId(tenantId='{0}')" -f $TenantId.ToString())$TenantData = Invoke-MgGraphRequest -Uri $Uri -Method Get$ReportLine = [PSCustomObject]@{'Tenant Name' = $TenantData.DisplayName'Tenant ID' = $TenantId'Number of Apps'= $NumberApps}$TenantReport.Add($ReportLine)}$TenantReport = $TenantReport | Sort-Object 'Number of apps' -Descending[array]$AppsNoName = $Report | Where-Object {$_.'Service Principal Name' -eq $null}Write-Host ("Some notes about service principals for the {0} tenant" -f $HomeTenant)Write-Host "------------------------------------------------------------------------"Write-Host ""Write-Host "Service Principals by owning tenant"$TenantReport | Format-Table -AutoSizeWrite-Host ""Write-Host ("Total Service Principals {0}" -f $ServicePrincipals.Count)Write-Host ("Service Principals with no sign-ins in the last year {0}" -f $Report.Count)Write-Host ("Service Principals with sign-ins in the last year {0}" -f ($ServicePrincipals.Count - $Report.Count))Write-Host ("Number of apps with no service principal {0}" -f $AppsNoName.Count)Write-Host ""# Generate some reports$Report | Out-GridView -Title "Service Principals with no sign-ins in the last year"$Report | Export-CSV -Path ServicePrincpalsNoSignIn.csv -NoTypeInformation -Encoding UTF8Write-Host "Report detailing service principals with last sign-in longer than a year written to ServicePrincipalsNoSignIn.csv"
Parameters
ParameterDefaultNotes
-TenantId""Microsoft Entra tenant ID for app-only Graph authentication.Attribution
Author
Office365itpros