Src/Private/Get-AbrIntuneAppManagement.ps1
|
function Get-AbrIntuneAppManagement { <# .SYNOPSIS Documents Intune App Management configuration (MAM/MDM apps and protection policies). .DESCRIPTION Collects and reports on: - Mobile Application Management (MAM) App Protection Policies - Managed app configurations - Published app inventory (top deployed apps) - App Assignment summary .NOTES Version: 0.1.0 Author: Pai Wei Sing #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory)] [string]$TenantId ) begin { Write-PScriboMessage -Message "Collecting Intune App Management for $TenantId." Show-AbrDebugExecutionTime -Start -TitleMessage 'App Management' } process { Section -Style Heading2 'App Management' { Paragraph "The following section documents App Management (MAM/MDM) configuration for tenant $TenantId." BlankLine # Aggregated metrics for compliance checks $TotalAppProtPolicies = 0 $UnassignedAppPolicies = 0 #region App Protection Policies try { Write-Host " - Retrieving App Protection Policies..." # iOS App Protection Policies $iOSAppProtResp = Invoke-MgGraphRequest -Method GET ` -Uri "$($script:GraphEndpoint)/v1.0/deviceAppManagement/iosManagedAppProtections?`$expand=assignments" ` -ErrorAction SilentlyContinue $iOSPolicies = $iOSAppProtResp.value # Android App Protection Policies $AndroidAppProtResp = Invoke-MgGraphRequest -Method GET ` -Uri "$($script:GraphEndpoint)/v1.0/deviceAppManagement/androidManagedAppProtections?`$expand=assignments" ` -ErrorAction SilentlyContinue $AndroidPolicies = $AndroidAppProtResp.value $AllAppProtPolicies = @() if ($iOSPolicies) { $AllAppProtPolicies += $iOSPolicies | ForEach-Object { $_ | Add-Member -NotePropertyName '_platform' -NotePropertyValue 'iOS / iPadOS' -PassThru -Force } } if ($AndroidPolicies) { $AllAppProtPolicies += $AndroidPolicies | ForEach-Object { $_ | Add-Member -NotePropertyName '_platform' -NotePropertyValue 'Android' -PassThru -Force } } $null = ($TotalAppProtPolicies = @($AllAppProtPolicies).Count) if ($AllAppProtPolicies -and @($AllAppProtPolicies).Count -gt 0) { Section -Style Heading3 'App Protection Policies' { BlankLine $AppProtObj = [System.Collections.ArrayList]::new() foreach ($AppProt in ($AllAppProtPolicies | Sort-Object displayName)) { $assignResolved = Resolve-IntuneAssignments -Assignments $AppProt.assignments -CheckMemberCount:$script:CheckEmptyGroups $AssignedTo = $assignResolved.AssignmentSummary if ($assignResolved.AssignmentSummary -eq 'Not assigned') { $null = ($UnassignedAppPolicies++) } $appProtInObj = [ordered] @{ 'Policy Name' = $AppProt.displayName 'Platform' = $AppProt._platform 'Data Transfer Block' = if ($null -ne $AppProt.allowedOutboundDataTransferDestinations) { $AppProt.allowedOutboundDataTransferDestinations } else { '--' } 'PIN Required' = if ($null -ne $AppProt.pinRequired) { $AppProt.pinRequired } else { '--' } 'Managed Browser Required'= if ($null -ne $AppProt.managedBrowserToOpenLinksRequired) { $AppProt.managedBrowserToOpenLinksRequired } else { '--' } 'Assignments' = $AssignedTo 'Last Modified' = if ($AppProt.lastModifiedDateTime) { ([datetime]$AppProt.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' } } $AppProtObj.Add([pscustomobject](ConvertTo-HashToYN $appProtInObj)) | Out-Null } $null = (& { if ($HealthCheck.Intune.AppManagement) { $null = ($AppProtObj | Where-Object { $_.'Assignments' -eq 'Not assigned' } | Set-Style -Style Warning | Out-Null) } }) $AppProtTableParams = @{ Name = "App Protection Policies - $TenantId"; ColumnWidths = 22, 14, 16, 11, 14, 13, 10 } if ($Report.ShowTableCaptions) { $AppProtTableParams['Caption'] = "- $($AppProtTableParams.Name)" } $AppProtObj | Table @AppProtTableParams if (Get-IntuneExcelSheetEnabled -SheetKey 'AppProtection') { $script:ExcelSheets['App Protection'] = $AppProtObj } if (Get-IntuneBackupSectionEnabled -SectionKey 'AppProtectionPolicies') { $script:BackupData['AppProtectionPolicies'] = $AllAppProtPolicies } # ── Per-policy detail sections ──────────────────────────────── foreach ($AppProt in ($AllAppProtPolicies | Sort-Object displayName)) { $assignResolved = Resolve-IntuneAssignments -Assignments $AppProt.assignments -CheckMemberCount:$script:CheckEmptyGroups Section -Style Heading4 $AppProt.displayName { BlankLine # Overview $APDetailObj = [System.Collections.ArrayList]::new() $APDetailObj.Add([pscustomobject]@{ Setting = 'Policy Name'; Value = $AppProt.displayName }) | Out-Null $APDetailObj.Add([pscustomobject]@{ Setting = 'Platform'; Value = $AppProt._platform }) | Out-Null $APDetailObj.Add([pscustomobject]@{ Setting = 'Created'; Value = if ($AppProt.createdDateTime) { ([datetime]$AppProt.createdDateTime).ToString('yyyy-MM-dd') } else { '--' } }) | Out-Null $APDetailObj.Add([pscustomobject]@{ Setting = 'Last Modified'; Value = if ($AppProt.lastModifiedDateTime) { ([datetime]$AppProt.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' } }) | Out-Null $APDetailObj.Add([pscustomobject]@{ Setting = 'Assignments'; Value = $assignResolved.AssignmentSummary }) | Out-Null $APOvTableParams = @{ Name = "Policy Overview - $($AppProt.displayName)"; ColumnWidths = 45, 55 } if ($Report.ShowTableCaptions) { $APOvTableParams['Caption'] = "- $($APOvTableParams.Name)" } $APDetailObj | Table @APOvTableParams BlankLine # ── Data Protection settings ────────────────────────── $DataProtRows = [System.Collections.ArrayList]::new() $DataProtRows.Add([pscustomobject]@{ Setting = 'Data Protection'; Value = '' }) | Out-Null $DataTransferLabel = { param($v) switch ($v) { 'allApps' { 'All apps' } 'managedApps' { 'Policy managed apps' } 'none' { 'None' } default { if ($v) { $v } else { '--' } } } } if ($AppProt.allowedOutboundDataTransferDestinations) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Send org data to other apps'; Value = (& $DataTransferLabel $AppProt.allowedOutboundDataTransferDestinations) }) | Out-Null } if ($AppProt.allowedInboundDataTransferSources) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Receive data from other apps'; Value = (& $DataTransferLabel $AppProt.allowedInboundDataTransferSources) }) | Out-Null } if ($null -ne $AppProt.saveAsBlocked) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Save copies of org data'; Value = if ($AppProt.saveAsBlocked) { 'Block' } else { 'Allow' } }) | Out-Null } if ($null -ne $AppProt.copyPasteBlockedToOtherApps) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Cut and copy between other apps'; Value = if ($AppProt.copyPasteBlockedToOtherApps) { 'Blocked' } else { 'Any app' } }) | Out-Null } if ($null -ne $AppProt.screenCaptureBlocked) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Screen capture and Google Assistant'; Value = if ($AppProt.screenCaptureBlocked) { 'Block' } else { 'Allow' } }) | Out-Null } if ($AppProt.allowedOutboundClipboardSharingLevel) { $clipLabel = switch ($AppProt.allowedOutboundClipboardSharingLevel) { 'allApps' { 'Any app' } 'managedApps' { 'Policy managed apps' } 'blocked' { 'Blocked' } default { $AppProt.allowedOutboundClipboardSharingLevel } } $DataProtRows.Add([pscustomobject]@{ Setting = 'Cut and copy between apps'; Value = $clipLabel }) | Out-Null } if ($null -ne $AppProt.encryptAppData) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Encrypt org data'; Value = if ($AppProt.encryptAppData) { 'Required' } else { 'Not Required' } }) | Out-Null } if ($null -ne $AppProt.disableAppEncryptionIfDeviceEncryptionIsEnabled) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Encrypt org data on enrolled devices'; Value = if ($AppProt.disableAppEncryptionIfDeviceEncryptionIsEnabled) { 'Disabled' } else { 'Enabled' } }) | Out-Null } if ($null -ne $AppProt.managedBrowserToOpenLinksRequired) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Open web content in Managed Browser'; Value = if ($AppProt.managedBrowserToOpenLinksRequired) { 'Required' } else { 'Not Required' } }) | Out-Null } if ($null -ne $AppProt.contactSyncBlocked) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Sync contacts to native contacts app'; Value = if ($AppProt.contactSyncBlocked) { 'Block' } else { 'Allow' } }) | Out-Null } if ($null -ne $AppProt.printBlocked) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Printing org data'; Value = if ($AppProt.printBlocked) { 'Block' } else { 'Allow' } }) | Out-Null } if ($AppProt.allowedDataStorageLocations -and @($AppProt.allowedDataStorageLocations).Count -gt 0) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Allowed data storage locations'; Value = ($AppProt.allowedDataStorageLocations -join ', ') }) | Out-Null } # allowedDataIngestionLocations — paste-source restrictions (HIGH gap fix) if ($AppProt.allowedDataIngestionLocations -and @($AppProt.allowedDataIngestionLocations).Count -gt 0) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Allowed data ingestion locations'; Value = ($AppProt.allowedDataIngestionLocations -join ', ') }) | Out-Null } # Exempted apps — protocols (iOS) and packages (Android) if ($AppProt.exemptedAppProtocols -and @($AppProt.exemptedAppProtocols).Count -gt 0) { $exemptStr = ($AppProt.exemptedAppProtocols | ForEach-Object { $n = if ($_ -is [System.Collections.IDictionary]) { $_['name'] } else { $_.name } $v = if ($_ -is [System.Collections.IDictionary]) { $_['value'] } else { $_.value } if ($n) { "$n ($v)" } else { "$v" } }) -join '; ' $DataProtRows.Add([pscustomobject]@{ Setting = 'Exempt apps (iOS protocols)'; Value = $exemptStr }) | Out-Null } if ($AppProt.exemptedAppPackages -and @($AppProt.exemptedAppPackages).Count -gt 0) { $exemptPkgStr = ($AppProt.exemptedAppPackages | ForEach-Object { $n = if ($_ -is [System.Collections.IDictionary]) { $_['name'] } else { $_.name } $v = if ($_ -is [System.Collections.IDictionary]) { $_['value'] } else { $_.value } if ($n) { "$n ($v)" } else { "$v" } }) -join '; ' $DataProtRows.Add([pscustomobject]@{ Setting = 'Exempt apps (Android packages)'; Value = $exemptPkgStr }) | Out-Null } # ── Access Requirements ─────────────────────────────── $DataProtRows.Add([pscustomobject]@{ Setting = 'Access Requirements'; Value = '' }) | Out-Null if ($null -ne $AppProt.pinRequired) { $DataProtRows.Add([pscustomobject]@{ Setting = 'PIN for access'; Value = if ($AppProt.pinRequired) { 'Required' } else { 'Not Required' } }) | Out-Null } if ($AppProt.pinRequiredInsteadOfBiometricTimeout) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Recheck access requirements after (minutes of inactivity)'; Value = "$($AppProt.pinRequiredInsteadOfBiometricTimeout)" }) | Out-Null } if ($AppProt.maximumPinRetries) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Max PIN attempts before reset'; Value = "$($AppProt.maximumPinRetries)" }) | Out-Null } if ($null -ne $AppProt.simplePinBlocked) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Simple PIN'; Value = if ($AppProt.simplePinBlocked) { 'Block' } else { 'Allow' } }) | Out-Null } if ($AppProt.minimumPinLength) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Minimum PIN length'; Value = "$($AppProt.minimumPinLength)" }) | Out-Null } if ($null -ne $AppProt.fingerprintBlocked) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Touch ID / Face ID instead of PIN'; Value = if ($AppProt.fingerprintBlocked) { 'Block' } else { 'Allow' } }) | Out-Null } if ($null -ne $AppProt.workAccountBlocked) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Corporate credentials for access'; Value = if ($AppProt.workAccountBlocked) { 'Block' } else { 'Require' } }) | Out-Null } # ── Conditional Launch ──────────────────────────────── $DataProtRows.Add([pscustomobject]@{ Setting = 'Conditional Launch'; Value = '' }) | Out-Null if ($AppProt.periodBeforePinReset) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Offline grace period (days)'; Value = $AppProt.periodBeforePinReset }) | Out-Null } # periodOnlineBeforeAccessCheck — online recheck interval (MEDIUM gap fix) if ($AppProt.periodOnlineBeforeAccessCheck) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Online recheck interval'; Value = $AppProt.periodOnlineBeforeAccessCheck }) | Out-Null } if ($AppProt.periodOfflineBeforeWipeInDays -and $AppProt.periodOfflineBeforeWipeInDays -gt 0) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Offline days before wiping app data'; Value = "$($AppProt.periodOfflineBeforeWipeInDays)" }) | Out-Null } if ($AppProt.minimumOsVersion) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Minimum OS version'; Value = $AppProt.minimumOsVersion }) | Out-Null } if ($AppProt.minimumAppVersion) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Minimum app version'; Value = $AppProt.minimumAppVersion }) | Out-Null } if ($AppProt.minimumWarningSdkVersion) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Minimum warning SDK version'; Value = $AppProt.minimumWarningSdkVersion }) | Out-Null } if ($AppProt.minimumWipeSdkVersion) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Minimum wipe SDK version'; Value = $AppProt.minimumWipeSdkVersion }) | Out-Null } if ($null -ne $AppProt.jailbrokenDevice -and $AppProt.jailbrokenDevice) { $DataProtRows.Add([pscustomobject]@{ Setting = 'Jailbroken / rooted devices'; Value = 'Block access' }) | Out-Null } if ($null -ne $AppProt.deviceThreatProtectionEnabled -and $AppProt.deviceThreatProtectionEnabled) { $threatLabel = if ($AppProt.maximumAllowedDeviceThreatLevel) { switch ($AppProt.maximumAllowedDeviceThreatLevel) { 'secured' { 'Secured' } 'low' { 'Low' } 'medium' { 'Medium' } 'high' { 'High' } default { $AppProt.maximumAllowedDeviceThreatLevel } } } else { 'Secured' } $DataProtRows.Add([pscustomobject]@{ Setting = 'Device Threat Protection — max allowed level'; Value = $threatLabel }) | Out-Null } # Style section headers bold $secHeaders = @('Data Protection','Access Requirements','Conditional Launch') $null = ($DataProtRows | Where-Object { $_.Setting -in $secHeaders } | Set-Style -Style 'TableSectionHeader') $APSettingsTableParams = @{ Name = "Policy Settings - $($AppProt.displayName)"; ColumnWidths = 55, 45 } if ($Report.ShowTableCaptions) { $APSettingsTableParams['Caption'] = "- $($APSettingsTableParams.Name)" } $DataProtRows | Table @APSettingsTableParams } } } } else { Paragraph "No App Protection Policies found in tenant $TenantId." } } catch { if (Test-AbrGraphForbidden -ErrorRecord $_) { Write-AbrPermissionError -Section 'App Protection Policies' -RequiredRole 'Intune Service Administrator or Global Administrator' } else { Write-AbrSectionError -Section 'App Protection Policies' -Message "$($_.Exception.Message)" } } #endregion #region Published Apps Summary if ($InfoLevel.AppManagement -ge 1) { try { Write-Host " - Retrieving published apps..." # /beta required -- v1.0 mobileApps returns BadRequest with $select on some tenants # @odata.type is a system property returned automatically -- do not include in $select # Removing $select entirely avoids BadRequest on tenants with strict Graph validation $AppsResp = Invoke-MgGraphRequest -Method GET ` -Uri "$($script:GraphEndpoint)/beta/deviceAppManagement/mobileApps" ` -ErrorAction SilentlyContinue $Apps = $AppsResp.value # Handle paging for large app libraries while ($AppsResp -and $AppsResp.'@odata.nextLink') { $AppsResp = Invoke-MgGraphRequest -Method GET -Uri $AppsResp.'@odata.nextLink' -ErrorAction SilentlyContinue if ($AppsResp.value) { $Apps += $AppsResp.value } } if ($Apps -and @($Apps).Count -gt 0) { Section -Style Heading3 'Published Apps' { BlankLine # Summary counts by type $AppTypes = $Apps | Group-Object { $_.'@odata.type' -replace '#microsoft.graph.', '' } | Sort-Object Count -Descending $AppSumObj = [System.Collections.ArrayList]::new() foreach ($AT in $AppTypes) { $atObj = [ordered] @{ 'App Type' = $AT.Name 'Count' = $AT.Count } $AppSumObj.Add([pscustomobject]$atObj) | Out-Null } $AppSumTableParams = @{ Name = "Published App Summary - $TenantId"; ColumnWidths = 65, 35 } if ($Report.ShowTableCaptions) { $AppSumTableParams['Caption'] = "- $($AppSumTableParams.Name)" } $AppSumObj | Table @AppSumTableParams BlankLine # Full app list (InfoLevel 2) if ($InfoLevel.AppManagement -ge 2) { $AppListObj = [System.Collections.ArrayList]::new() foreach ($App in ($Apps | Sort-Object displayName)) { $AppType = $App.'@odata.type' -replace '#microsoft.graph.', '' # Resolve per-app assignments (HIGH gap fix) $appAssignResolved = $null if ($InfoLevel.AppManagement -ge 2) { try { $appAssignResp = $null try { $appAssignResp = Invoke-MgGraphRequest -Method GET ` -Uri "$($script:GraphEndpoint)/beta/deviceAppManagement/mobileApps/$($App.id)/assignments" ` -ErrorAction Stop } catch { Write-AbrDebugLog "App assignments unavailable for '$($App.displayName)': $($_.Exception.Message)" 'WARN' 'AppMgmt' } if ($appAssignResp -and $appAssignResp.value) { $appAssignResolved = Resolve-IntuneAssignments -Assignments $appAssignResp.value -CheckMemberCount:$script:CheckEmptyGroups } } catch { Write-AbrDebugLog "App assignment resolution failed for '$($App.displayName)': $($_.Exception.Message)" 'WARN' 'AppMgmt' } } $publishLabel = switch ($App.publishingState) { 'published' { 'Published' } 'processing' { 'Processing' } 'notPublished'{ 'Not Published' } default { if ($App.publishingState) { $App.publishingState } else { '--' } } } $appInObj = [ordered] @{ 'App Name' = $App.displayName 'Publisher' = if ($App.publisher) { $App.publisher } else { '--' } 'App Type' = $AppType 'Version' = if ($App.version) { $App.version } else { '--' } 'State' = $publishLabel 'Featured' = if ($null -ne $App.isFeatured) { if ($App.isFeatured) { 'Yes' } else { 'No' } } else { '--' } 'Assigned To' = if ($appAssignResolved) { $appAssignResolved.IncludedGroups } else { '--' } 'Created' = if ($App.createdDateTime) { ([datetime]$App.createdDateTime).ToString('yyyy-MM-dd') } else { '--' } } $AppListObj.Add([pscustomobject]$appInObj) | Out-Null } $AppListTableParams = @{ Name = "App Inventory - $TenantId"; ColumnWidths = 22, 16, 16, 8, 9, 7, 14, 8 } if ($Report.ShowTableCaptions) { $AppListTableParams['Caption'] = "- $($AppListTableParams.Name)" } $AppListObj | Table @AppListTableParams if (Get-IntuneExcelSheetEnabled -SheetKey 'AppInventory') { $script:ExcelSheets['App Inventory'] = $AppListObj } } } } } catch { if (Test-AbrGraphForbidden -ErrorRecord $_) { Write-AbrPermissionError -Section 'Published Apps' -RequiredRole 'Intune Service Administrator or Global Administrator' } else { Write-AbrSectionError -Section 'Published Apps' -Message "$($_.Exception.Message)" } } } #endregion #region ACSC E8 Assessment if ($script:IncludeACSCe8) { BlankLine Paragraph "ACSC Essential Eight Maturity Level Assessment -- App Management:" BlankLine try { $_v = @{ TotalAppProtectionPolicies = $TotalAppProtPolicies UnassignedAppPolicies = $UnassignedAppPolicies } $E8Checks = Build-AbrIntuneComplianceChecks -Definitions (Get-AbrIntuneE8Checks -Section 'AppManagement') -Framework E8 -CallerVariables $_v New-AbrIntuneE8AssessmentTable -Checks $E8Checks -Name 'App Management' -TenantId $TenantId if ($E8Checks) { $null = $script:E8AllChecks.AddRange([object[]](@($E8Checks | Select-Object @{N='Section';E={'AppManagement'}}, ML, Control, Status, Detail))) } } catch { Write-AbrSectionError -Section 'E8 App Management Assessment' -Message "$($_.Exception.Message)" } } #endregion #region CIS Assessment if ($script:IncludeCISBaseline) { BlankLine Paragraph "CIS Microsoft 365 Foundations Benchmark Assessment -- App Management:" BlankLine try { $_v = @{ TotalAppProtectionPolicies = $TotalAppProtPolicies UnassignedAppPolicies = $UnassignedAppPolicies } $CISChecks = Build-AbrIntuneComplianceChecks -Definitions (Get-AbrIntuneCISChecks -Section 'AppManagement') -Framework CIS -CallerVariables $_v New-AbrIntuneCISAssessmentTable -Checks $CISChecks -Name 'App Management' -TenantId $TenantId if ($CISChecks) { $null = $script:CISAllChecks.AddRange([object[]](@($CISChecks | Select-Object @{N='Section';E={'AppManagement'}}, CISControl, Level, Status, Detail))) } } catch { Write-AbrSectionError -Section 'CIS App Management Assessment' -Message "$($_.Exception.Message)" } } #endregion #region Windows Information Protection (WIP) Policies — CRITICAL gap fix if ($InfoLevel.AppManagement -ge 1) { try { Write-Host " - Retrieving Windows Information Protection policies..." $WipMdmResp = $null; $WipMamResp = $null try { $WipMdmResp = Invoke-MgGraphRequest -Method GET -Uri "$($script:GraphEndpoint)/beta/deviceAppManagement/mdmWindowsInformationProtectionPolicies?`$expand=assignments" -ErrorAction Stop } catch { Write-AbrDebugLog "WIP MDM policies unavailable: $($_.Exception.Message)" 'WARN' 'AppMgmt' } try { $WipMamResp = Invoke-MgGraphRequest -Method GET -Uri "$($script:GraphEndpoint)/beta/deviceAppManagement/windowsInformationProtectionPolicies?`$expand=assignments" -ErrorAction Stop } catch { Write-AbrDebugLog "WIP MAM policies unavailable: $($_.Exception.Message)" 'WARN' 'AppMgmt' } $WipPolicies = @() if ($WipMdmResp -and $WipMdmResp.value) { $WipPolicies += $WipMdmResp.value | ForEach-Object { $_ | Add-Member -NotePropertyName '_wipType' -NotePropertyValue 'MDM' -PassThru -Force } } if ($WipMamResp -and $WipMamResp.value) { $WipPolicies += $WipMamResp.value | ForEach-Object { $_ | Add-Member -NotePropertyName '_wipType' -NotePropertyValue 'MAM' -PassThru -Force } } if ($WipPolicies.Count -gt 0) { Section -Style Heading3 'Windows Information Protection (WIP)' { BlankLine $WipObj = [System.Collections.ArrayList]::new() foreach ($Wip in ($WipPolicies | Sort-Object displayName)) { $assignResolved = Resolve-IntuneAssignments -Assignments $Wip.assignments -CheckMemberCount:$script:CheckEmptyGroups $enforcementLabel = switch ($Wip.enforcementLevel) { 'noProtection' { 'Off' } 'encryptAndAuditOnly' { 'Silent' } 'encryptAuditAndPrompt' { 'Override' } 'encryptAuditAndBlock' { 'Block' } default { if ($Wip.enforcementLevel) { $Wip.enforcementLevel } else { '--' } } } $protectedAppsStr = if ($Wip.protectedApps -and @($Wip.protectedApps).Count -gt 0) { $names = $Wip.protectedApps | ForEach-Object { $n = if ($_ -is [System.Collections.IDictionary]) { $_['displayName'] } else { $_.displayName } if ($n) { $n } else { 'Unnamed' } } "$(@($Wip.protectedApps).Count) app(s): $($names -join '; ')" } else { '--' } $exemptAppsStr = if ($Wip.exemptApps -and @($Wip.exemptApps).Count -gt 0) { "$(@($Wip.exemptApps).Count) app(s)" } else { '--' } $wipRow = [ordered] @{ 'Policy Name' = $Wip.displayName 'Type' = $Wip._wipType 'Enforcement' = $enforcementLabel 'Protected Apps' = $protectedAppsStr 'Exempt Apps' = $exemptAppsStr 'Included Groups' = $assignResolved.IncludedGroups 'Excluded Groups' = $assignResolved.ExcludedGroups } $WipObj.Add([pscustomobject]$wipRow) | Out-Null } $WipParams = @{ Name = "WIP Policies - $TenantId"; ColumnWidths = 22, 6, 10, 28, 10, 14, 10 } if ($Report.ShowTableCaptions) { $WipParams['Caption'] = "- $($WipParams.Name)" } $WipObj | Table @WipParams } } } catch { if (Test-AbrGraphForbidden -ErrorRecord $_) { Write-AbrPermissionError -Section 'WIP Policies' -RequiredRole 'Intune Service Administrator or Global Administrator' } else { Write-AbrSectionError -Section 'WIP Policies' -Message "$($_.Exception.Message)" } } } #endregion #region Apple VPP Tokens — MEDIUM gap fix if ($InfoLevel.AppManagement -ge 1) { try { Write-Host " - Retrieving Apple VPP tokens..." $VppResp = $null try { $VppResp = Invoke-MgGraphRequest -Method GET -Uri "$($script:GraphEndpoint)/beta/deviceAppManagement/vppTokens" -ErrorAction Stop } catch { Write-AbrDebugLog "VPP tokens unavailable: $($_.Exception.Message)" 'WARN' 'AppMgmt' } if ($VppResp -and $VppResp.value -and @($VppResp.value).Count -gt 0) { Section -Style Heading3 'Apple Volume Purchase Program (VPP) Tokens' { BlankLine $VppObj = [System.Collections.ArrayList]::new() foreach ($Token in ($VppResp.value | Sort-Object organizationName)) { $expiryDate = if ($Token.expirationDateTime) { ([datetime]$Token.expirationDateTime).ToString('yyyy-MM-dd') } else { '--' } $lastSyncDate = if ($Token.lastSyncDateTime) { ([datetime]$Token.lastSyncDateTime).ToString('yyyy-MM-dd') } else { '--' } $stateLabel = switch ($Token.state) { 'valid' { 'Valid' } 'expired' { 'Expired' } 'invalid' { 'Invalid' } default { if ($Token.state) { $Token.state } else { '--' } } } $vppRow = [ordered] @{ 'Organisation' = if ($Token.organizationName) { $Token.organizationName } else { '--' } 'Apple ID' = if ($Token.appleId) { $Token.appleId } else { '--' } 'State' = $stateLabel 'Licences' = if ($null -ne $Token.totalLicenseCount) { "$($Token.usedLicenseCount) / $($Token.totalLicenseCount)" } else { '--' } 'Token Type' = if ($Token.tokenActionResults) { 'Business' } else { if ($Token.appleId) { 'Education' } else { '--' } } 'Expires' = $expiryDate 'Last Sync' = $lastSyncDate } $VppObj.Add([pscustomobject]$vppRow) | Out-Null } $VppParams = @{ Name = "VPP Tokens - $TenantId"; ColumnWidths = 22, 24, 8, 12, 10, 12, 12 } if ($Report.ShowTableCaptions) { $VppParams['Caption'] = "- $($VppParams.Name)" } $VppObj | Table @VppParams } } } catch { if (Test-AbrGraphForbidden -ErrorRecord $_) { Write-AbrPermissionError -Section 'Apple VPP Tokens' -RequiredRole 'Intune Service Administrator or Global Administrator' } else { Write-AbrSectionError -Section 'Apple VPP Tokens' -Message "$($_.Exception.Message)" } } } #endregion } } end { Show-AbrDebugExecutionTime -End -TitleMessage 'App Management' } } |