Src/Private/Get-AbrIntuneAppConfigPolicies.ps1

function Get-AbrIntuneAppConfigPolicies {
    <#
    .SYNOPSIS
    Documents Intune App Configuration Policies (managed device and managed app).
    .DESCRIPTION
        Collects and reports on:
          - Managed Device App Configuration policies (settings pushed to enrolled devices)
          - Managed App (MAM) App Configuration policies (settings pushed via App Protection)
          - Assignments with resolved group names and exclusion detection
    .NOTES
        Version: 0.1.0
        Author: Pai Wei Sing
    #>

    [CmdletBinding()]
    param (
        [Parameter(Position = 0, Mandatory)]
        [string]$TenantId
    )

    begin {
        Write-PScriboMessage -Message "Collecting Intune App Configuration Policies for $TenantId."
        Show-AbrDebugExecutionTime -Start -TitleMessage 'App Config Policies'
    }

    process {
        Section -Style Heading2 'App Configuration Policies' {
            Paragraph "The following section documents App Configuration Policies configured in tenant $TenantId."
            BlankLine

            $TotalAppConfigPolicies   = 0
            $UnassignedAppConfigCount = 0

            #region Managed Device App Config Policies
            try {
                Write-Host " - Retrieving App Configuration Policies (managed devices)..."
                $MdmConfigResp = Invoke-MgGraphRequest -Method GET `
                    -Uri "$($script:GraphEndpoint)/beta/deviceAppManagement/mobileAppConfigurations?`$expand=assignments" `
                    -ErrorAction SilentlyContinue
                $MdmConfigs = $MdmConfigResp.value

                if ($MdmConfigs -and @($MdmConfigs).Count -gt 0) {
                    $null = ($TotalAppConfigPolicies += @($MdmConfigs).Count)
                    Section -Style Heading3 'Managed Device App Configurations' {
                        BlankLine
                        $MdmObj = [System.Collections.ArrayList]::new()
                        foreach ($Cfg in ($MdmConfigs | Sort-Object displayName)) {
                            $assignResolved = Resolve-IntuneAssignments -Assignments $Cfg.assignments -CheckMemberCount:$script:CheckEmptyGroups
                            if ($assignResolved.AssignmentSummary -eq 'Not assigned') { $null = ($UnassignedAppConfigCount++) }

                            $scopeTagStr = if ($script:ResolveScopeTagNames -and $Cfg.roleScopeTagIds) {
                                Get-IntuneScopeTagNames -ScopeTagIds $Cfg.roleScopeTagIds
                            } else { 'Default' }

                            $targetApps = if ($Cfg.targetedMobileApps -and @($Cfg.targetedMobileApps).Count -gt 0) {
                                "$(@($Cfg.targetedMobileApps).Count) app(s)"
                            } else { '--' }

                            $cfgInObj = [ordered] @{
                                'Policy Name'      = $Cfg.displayName
                                'Platform'         = if ($Cfg.targetedMobileApps) { 'MDM' } else { '--' }
                                'Targeted Apps'    = $targetApps
                                'Included Groups'  = $assignResolved.IncludedGroups
                                'Excluded Groups'  = if ($script:ShowExcludedGroups) { $assignResolved.ExcludedGroups } else { $null }
                                'Scope Tags'       = $scopeTagStr
                                'Last Modified'    = if ($Cfg.lastModifiedDateTime) { ([datetime]$Cfg.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' }
                            }

                            $MdmObj.Add([pscustomobject]$cfgInObj) | Out-Null
                        }

                        $null = (& {
                            if ($HealthCheck.Intune.AppManagement) {
                                $null = ($MdmObj | Where-Object { $_.'Included Groups' -eq '--' } | Set-Style -Style Warning | Out-Null)
                            }
                        })

                        $MdmTableParams = @{ Name = "Managed Device App Configurations - $TenantId"; ColumnWidths = 18, 7, 10, 20, 18, 8, 19 }
                        if ($Report.ShowTableCaptions) { $MdmTableParams['Caption'] = "- $($MdmTableParams.Name)" }
                        $MdmObj | Table @MdmTableParams

                        if (Get-IntuneExcelSheetEnabled -SheetKey 'AppConfigPolicies') {
                            $script:ExcelSheets['App Config Policies (MDM)'] = $MdmObj
                        }
                        if (Get-IntuneBackupSectionEnabled -SectionKey 'AppConfigPolicies') {
                            $script:BackupData['AppConfigPolicies'] = $MdmConfigs
                        }

                        #region InfoLevel 2
                        if ($InfoLevel.AppConfigPolicies -ge 2) {
                            foreach ($Cfg in ($MdmConfigs | Sort-Object displayName)) {
                                $assignResolved = Resolve-IntuneAssignments -Assignments $Cfg.assignments
                                Section -Style Heading4 $Cfg.displayName {
                                    BlankLine
                                    $ovObj = [System.Collections.ArrayList]::new()
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Policy Name';      Value = $Cfg.displayName }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Description';      Value = if ($Cfg.description) { $Cfg.description } else { '--' } }) | Out-Null

                                    # Resolve targeted app names from the mobileApps endpoint
                                    if ($Cfg.targetedMobileApps -and @($Cfg.targetedMobileApps).Count -gt 0) {
                                        $appNameList = $Cfg.targetedMobileApps | ForEach-Object {
                                            $appId = if ($_ -is [string]) { $_ } else { $_.id }
                                            if ($appId) {
                                                try {
                                                    $ar = Invoke-MgGraphRequest -Method GET `
                                                        -Uri "$($script:GraphEndpoint)/beta/deviceAppManagement/mobileApps/$appId`?`$select=displayName" `
                                                        -ErrorAction Stop
                                                    if ($ar.displayName) { $ar.displayName } else { $appId }
                                                } catch { $appId }
                                            }
                                        }
                                        $ovObj.Add([pscustomobject]@{ Setting = 'Targeted Apps'; Value = ($appNameList -join '; ') }) | Out-Null
                                    } else {
                                        $ovObj.Add([pscustomobject]@{ Setting = 'Targeted Apps'; Value = '--' }) | Out-Null
                                    }

                                    $ovObj.Add([pscustomobject]@{ Setting = 'Included Groups';  Value = $assignResolved.IncludedGroups }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Excluded Groups';  Value = $assignResolved.ExcludedGroups }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Last Modified';    Value = if ($Cfg.lastModifiedDateTime) { ([datetime]$Cfg.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' } }) | Out-Null
                                    $OvParams = @{ Name = "App Config Detail - $($Cfg.displayName)"; ColumnWidths = 30, 70 }
                                    if ($Report.ShowTableCaptions) { $OvParams['Caption'] = "- $($OvParams.Name)" }
                                    $ovObj | Table @OvParams

                                    # Custom settings key-value pairs
                                    # settings[] items have a nested structure: { name/key, settingValue: { value/... } }
                                    # payloadJson is used for XML/JSON payloads (e.g. OEMConfig)
                                    if ($Cfg.settings -and @($Cfg.settings).Count -gt 0) {
                                        BlankLine
                                        Paragraph "Custom Settings ($(@($Cfg.settings).Count) setting(s)):"
                                        BlankLine
                                        $kvRows = $Cfg.settings | ForEach-Object {
                                            $settingKey = if ($_ -is [System.Collections.IDictionary]) {
                                                if ($_['name']) { $_['name'] } elseif ($_['key']) { $_['key'] } else { '--' }
                                            } else {
                                                if ($_.name) { $_.name } elseif ($_.key) { $_.key } else { '--' }
                                            }
                                            # settingValue is a nested object with a 'value' property
                                            $sv = if ($_ -is [System.Collections.IDictionary]) { $_['settingValue'] } else { $_.settingValue }
                                            $rawVal = if ($sv -is [System.Collections.IDictionary]) { $sv['value'] }
                                                      elseif ($null -ne $sv) { $sv.value }
                                                      else {
                                                          # Fallback: direct value property
                                                          if ($_ -is [System.Collections.IDictionary]) { $_['value'] } else { $_.value }
                                                      }
                                            $displayVal = if ($null -ne $rawVal) {
                                                $s = "$rawVal"
                                                if ($s.Length -gt 100) { "$($s.Substring(0,100))..." } else { $s }
                                            } else { '--' }
                                            [pscustomobject]([ordered]@{ 'Key' = $settingKey; 'Value' = $displayVal })
                                        }
                                        $KvParams = @{ Name = "Settings - $($Cfg.displayName)"; ColumnWidths = 40, 60 }
                                        if ($Report.ShowTableCaptions) { $KvParams['Caption'] = "- $($KvParams.Name)" }
                                        $kvRows | Table @KvParams
                                    } elseif ($Cfg.payloadJson) {
                                        BlankLine
                                        Paragraph "Configuration payload: JSON/XML payload configured (see backup export for full content)."
                                    }
                                }
                            }
                        }
                        #endregion
                    }
                }
            } catch {
                if (Test-AbrGraphForbidden -ErrorRecord $_) {
                    Write-AbrPermissionError -Section 'App Configuration Policies' -RequiredRole 'Intune Service Administrator or Global Administrator'
                } else {
                    Write-AbrSectionError -Section 'App Configuration Policies' -Message "$($_.Exception.Message)"
                }
            }
            #endregion

            #region MAM App Config Policies
            try {
                Write-Host " - Retrieving App Configuration Policies (managed apps / MAM)..."
                $MamConfigResp = Invoke-MgGraphRequest -Method GET `
                    -Uri "$($script:GraphEndpoint)/beta/deviceAppManagement/targetedManagedAppConfigurations?`$expand=assignments" `
                    -ErrorAction SilentlyContinue
                $MamConfigs = $MamConfigResp.value

                if ($MamConfigs -and @($MamConfigs).Count -gt 0) {
                    $null = ($TotalAppConfigPolicies += @($MamConfigs).Count)
                    Section -Style Heading3 'Managed App (MAM) Configurations' {
                        BlankLine
                        $MamObj = [System.Collections.ArrayList]::new()
                        foreach ($Cfg in ($MamConfigs | Sort-Object displayName)) {
                            $assignResolved = Resolve-IntuneAssignments -Assignments $Cfg.assignments -CheckMemberCount:$script:CheckEmptyGroups
                            if ($assignResolved.AssignmentSummary -eq 'Not assigned') { $null = ($UnassignedAppConfigCount++) }

                            $cfgInObj = [ordered] @{
                                'Policy Name'      = $Cfg.displayName
                                'Deployed Apps'    = if ($Cfg.apps) { @($Cfg.apps).Count } else { 0 }
                                'Settings Count'   = if ($Cfg.customSettings) { @($Cfg.customSettings).Count } else { 0 }
                                'Included Groups'  = $assignResolved.IncludedGroups
                                'Excluded Groups'  = if ($script:ShowExcludedGroups) { $assignResolved.ExcludedGroups } else { $null }
                                'Last Modified'    = if ($Cfg.lastModifiedDateTime) { ([datetime]$Cfg.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' }
                            }

                            $MamObj.Add([pscustomobject]$cfgInObj) | Out-Null
                        }

                        $null = (& {
                            if ($HealthCheck.Intune.AppManagement) {
                                $null = ($MamObj | Where-Object { $_.'Included Groups' -eq '--' } | Set-Style -Style Warning | Out-Null)
                            }
                        })

                        $MamTableParams = @{ Name = "Managed App Configurations (MAM) - $TenantId"; ColumnWidths = 22, 12, 11, 22, 18, 15 }
                        if ($Report.ShowTableCaptions) { $MamTableParams['Caption'] = "- $($MamTableParams.Name)" }
                        $MamObj | Table @MamTableParams

                        if (Get-IntuneExcelSheetEnabled -SheetKey 'AppConfigPolicies') {
                            if ($script:ExcelSheets['App Config Policies (MAM)']) {
                                $script:ExcelSheets['App Config Policies (MAM)'] += $MamObj
                            } else {
                                $script:ExcelSheets['App Config Policies (MAM)'] = $MamObj
                            }
                        }
                    }
                }
            } catch {
                if (Test-AbrGraphForbidden -ErrorRecord $_) {
                    Write-AbrPermissionError -Section 'MAM App Configuration Policies' -RequiredRole 'Intune Service Administrator or Global Administrator'
                } else {
                    Write-AbrSectionError -Section 'MAM App Configuration Policies' -Message "$($_.Exception.Message)"
                }
            }
            #endregion

            if ($TotalAppConfigPolicies -eq 0) {
                Paragraph "No App Configuration Policies found in tenant $TenantId."
            }
        }
    }

    end {
        Show-AbrDebugExecutionTime -End -TitleMessage 'App Config Policies'
    }
}