Public/Get-IODormantApps.ps1
|
function Get-IODormantApps { <# .SYNOPSIS Finds app registrations with no recent sign-in activity. .EXAMPLE Get-IODormantApps -InactiveDays 90 .EXAMPLE Get-IODormantApps -InactiveDays 180 -ToCsv "dormant-apps.csv" #> [CmdletBinding()] param( [ValidateRange(1, 3650)] [int]$InactiveDays = 90, [string]$ToCsv ) $cmdName = $MyInvocation.MyCommand.Name Write-IOLog "Scanning for apps with no sign-in activity in $InactiveDays+ days..." -Level Info -Component $cmdName $cutoff = [datetime]::UtcNow.AddDays(-$InactiveDays) $results = [System.Collections.Generic.List[PSCustomObject]]::new() # Service principals have signInActivity (requires Entra ID P1+) $sps = Invoke-IOGraphRequest -Uri "v1.0/servicePrincipals?`$filter=servicePrincipalType eq 'Application'&`$select=id,displayName,appId,createdDateTime,signInActivity" foreach ($sp in $sps) { $lastActivity = $null $lastDelegated = $null $lastAppOnly = $null $neverUsed = $true $sia = if ($sp.PSObject.Properties['signInActivity']) { $sp.signInActivity } else { $null } if ($sia) { if ($sia.lastSignInDateTime) { $lastDelegated = [datetime]::Parse($sia.lastSignInDateTime, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AssumeUniversal) $neverUsed = $false } if ($sia.lastNonInteractiveSignInDateTime) { $lastAppActivity = [datetime]::Parse($sia.lastNonInteractiveSignInDateTime, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AssumeUniversal) if (-not $lastDelegated -or $lastAppActivity -gt $lastDelegated) { $lastActivity = $lastAppActivity } else { $lastActivity = $lastDelegated } $neverUsed = $false } else { $lastActivity = $lastDelegated } } $isDormant = $neverUsed -or ($lastActivity -and $lastActivity -lt $cutoff) if ($isDormant) { $created = if ($sp.createdDateTime) { [datetime]::Parse($sp.createdDateTime, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AssumeUniversal).ToString('yyyy-MM-dd') } else { 'Unknown' } $results.Add([PSCustomObject]@{ ApplicationName = $sp.displayName ApplicationId = $sp.appId ObjectId = $sp.id CreatedDate = $created LastActivity = if ($lastActivity) { $lastActivity.ToString('yyyy-MM-dd') } else { 'Never' } InactiveDays = if ($lastActivity) { [math]::Floor(([datetime]::UtcNow - $lastActivity).TotalDays) } else { 'N/A' } Status = if ($neverUsed) { 'NEVER_USED' } else { 'DORMANT' } }) } } $sorted = $results | Sort-Object Status, LastActivity Export-IOResult -Data $sorted -ToCsv $ToCsv -CommandName $cmdName } |