Public/Get-IntuneAppInventory.ps1
|
function Get-IntuneAppInventory { <# .SYNOPSIS Audits Intune managed applications, install status, and assignment health. .DESCRIPTION Enumerates all mobile apps managed through Intune, checks install success/failure counts, identifies apps with high failure rates, unassigned apps, and apps with outdated or missing versions. .PARAMETER FailureThresholdPercent Percentage of failed installs before flagging an app. Defaults to 10. .PARAMETER IncludeStore Include store apps (iOS App Store, Google Play, Microsoft Store). Excluded by default. .EXAMPLE Get-IntuneAppInventory -FailureThresholdPercent 5 #> [CmdletBinding()] param( [Parameter()] [ValidateRange(1, 100)] [int]$FailureThresholdPercent = 10, [Parameter()] [switch]$IncludeStore ) begin { Test-GraphConnection $results = [System.Collections.Generic.List[PSObject]]::new() } process { $apps = Get-MgDeviceAppManagementMobileApp -All -Property @( 'id', 'displayName', '@odata.type', 'publisher', 'createdDateTime', 'lastModifiedDateTime' ) if (-not $IncludeStore) { $apps = $apps | Where-Object { $_.'@odata.type' -notmatch 'iosStoreApp|androidStoreApp|microsoftStoreForBusinessApp' } } foreach ($app in $apps) { $findings = @() $appType = ($app.AdditionalProperties.'@odata.type' ?? $app.'@odata.type') -replace '#microsoft.graph.', '' # Get assignments try { $assignments = Get-MgDeviceAppManagementMobileAppAssignment -MobileAppId $app.Id -ErrorAction Stop $assignmentCount = ($assignments | Measure-Object).Count } catch { $assignments = @() $assignmentCount = 0 } if ($assignmentCount -eq 0) { $findings += 'UNASSIGNED' } # Get device install status summary try { $statusSummary = Invoke-MgGraphRequest -Method GET ` -Uri "https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps/$($app.Id)/deviceStatuses" ` -ErrorAction Stop $statuses = $statusSummary.value $totalInstalls = ($statuses | Measure-Object).Count $failures = ($statuses | Where-Object { $_.installState -eq 'failed' } | Measure-Object).Count $pending = ($statuses | Where-Object { $_.installState -eq 'notInstalled' } | Measure-Object).Count if ($totalInstalls -gt 0) { $failureRate = [math]::Round(($failures / $totalInstalls) * 100, 1) if ($failureRate -ge $FailureThresholdPercent) { $findings += "HIGH FAILURE RATE ($failureRate% - $failures of $totalInstalls)" } } } catch { $totalInstalls = 0 $failures = 0 $pending = 0 $failureRate = 0 } # Check for stale apps (not modified in 180+ days and has failures) if ($app.LastModifiedDateTime) { $daysSinceUpdate = [math]::Round(((Get-Date) - $app.LastModifiedDateTime).TotalDays) if ($daysSinceUpdate -gt 180 -and $failures -gt 0) { $findings += "STALE APP ($daysSinceUpdate days since update)" } } $results.Add([PSCustomObject]@{ AppName = $app.DisplayName AppType = $appType Publisher = $app.Publisher Assignments = $assignmentCount TotalDevices = $totalInstalls Installed = $totalInstalls - $failures - $pending Failed = $failures Pending = $pending FailureRate = "$failureRate%" LastModified = $app.LastModifiedDateTime Finding = if ($findings.Count -gt 0) { $findings -join ' | ' } else { 'OK' } }) } } end { $flagged = $results | Where-Object { $_.Finding -ne 'OK' } Write-Host " Apps reviewed: $($results.Count) | Findings: $($flagged.Count)" -ForegroundColor Gray $results | Sort-Object Finding, AppName } } |