tests/Test-Assessment.51015.ps1

<#
.SYNOPSIS
    Multi Admin Approval is enabled in Intune to require a second admin to approve sensitive tenant changes.
 
.DESCRIPTION
    Checks whether at least one Intune Multi Admin Approval (MAA) access policy exists with an approver
    group assigned. Without MAA, a single compromised admin account can push apps, run scripts, wipe
    devices, modify compliance or configuration policies, and change role assignments with no second
    pair of eyes. A policy only enforces the approval gate when it has at least one approver group
    configured in approverGroupIds.
 
.NOTES
    Test ID: 51015
    Category: Devices
    Pillar: Devices
    Required API: Microsoft Graph beta — deviceManagement/operationApprovalPolicies
                  Q1: list (id, displayName, policyType, policyPlatform, lastModifiedDateTime)
                  Q2: per-policy detail (approverGroupIds)
#>


function Test-Assessment-51015 {
    [ZtTest(
        Category = 'Devices',
        CompatibleLicense = ('INTUNE_A'),
        ImplementationCost = 'Medium',
        Pillar = 'Devices',
        RiskLevel = 'High',
        SfiPillar = 'Protect identities and secrets',
        TenantType = ('Workforce'),
        TestId = 51015,
        Title = 'Multi Admin Approval is enabled in Intune to require a second admin to approve sensitive tenant changes',
        UserImpact = 'Low'
    )]
    [CmdletBinding()]
    param()

    #region Data Collection
    Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose

    $activity = 'Checking Intune Multi Admin Approval access policies'
    Write-ZtProgress -Activity $activity -Status 'Getting access policies'

    # Q1: List all Intune operation approval policies (Multi Admin Approval).
    # approverGroupIds is intentionally excluded — the list response can omit it; Q2 fetches it reliably per policy.
    $policies = @()
    try {
        $policies = @(Invoke-ZtGraphRequest -RelativeUri 'deviceManagement/operationApprovalPolicies' -Select 'id,displayName,policyType,policyPlatform,lastModifiedDateTime' -ApiVersion beta -ErrorAction Stop)
    }
    catch {
        # Surface all Q1 failures as Investigate so the run continues and the assessor gets a clear message.
        # 403/Forbidden/accessDenied = permission gap; anything else = transient or unexpected error.
        $result = if ($_.Exception.Message -match '403|Forbidden|accessDenied') {
            '⚠️ Insufficient permissions when querying Intune Multi Admin Approval policies. Ensure you have the required access to run this assessment.'
        }
        else {
            "⚠️ An error occurred while querying Intune Multi Admin Approval policies: $($_.Exception.Message)"
        }
        $params = @{
            TestId       = '51015'
            Title        = 'Multi Admin Approval is enabled in Intune to require a second admin to approve sensitive tenant changes'
            Status       = $false
            Result       = $result
            CustomStatus = 'Investigate'
        }
        Add-ZtTestResultDetail @params
        return
    }

    # Q2: Fetch per-policy detail to reliably retrieve approverGroupIds.
    # The list response (Q1) omits approverGroupIds; individual GETs always return it.
    $policyDetails = @{}
    if ($policies.Count -gt 0) {
        Write-ZtProgress -Activity $activity -Status 'Getting policy details'
        foreach ($policy in $policies) {
            try {
                $detail = Invoke-ZtGraphRequest -RelativeUri "deviceManagement/operationApprovalPolicies/$($policy.id)" -ApiVersion beta -ErrorAction Stop
                $policyDetails[$policy.id] = $detail
            }
            catch {
                Write-PSFMessage "Q2 failed for policy $($policy.id): $_" -Tag Test -Level Warning
            }
        }
    }
    #endregion Data Collection

    #region Assessment Logic
    # Guard: Q1 found policies but every Q2 detail call failed — cannot evaluate confidently.
    if ($policies.Count -gt 0 -and $policyDetails.Count -eq 0) {
        $params = @{
            TestId       = '51015'
            Title        = 'Multi Admin Approval is enabled in Intune to require a second admin to approve sensitive tenant changes'
            Status       = $false
            Result       = '⚠️ Unable to retrieve Intune Multi Admin Approval policy details for evaluation. Please retry and verify the required permissions.'
            CustomStatus = 'Investigate'
        }
        Add-ZtTestResultDetail @params
        return
    }

    # Pass: at least one Q2 policy detail has a non-empty approverGroupIds collection.
    # Fail: zero policies exist, OR every Q2 detail has an empty approverGroupIds.
    $passed = @($policyDetails.Values | Where-Object { $_ -and $_.approverGroupIds -and $_.approverGroupIds.Count -gt 0 }).Count -gt 0

    if ($passed) {
        $testResultMarkdown = "✅ At least one Intune Multi Admin Approval access policy exists with an approver group assigned, so a second administrator must approve protected actions before they take effect.`n`n%TestResult%"
    }
    else {
        $testResultMarkdown = "❌ No Intune Multi Admin Approval access policy is configured with an approver group — a single compromised admin account can perform sensitive tenant actions (app push, script run, device wipe, policy change, role change) with no second-administrator gate.`n`n%TestResult%"
    }
    #endregion Assessment Logic

    #region Report Generation
    $mdInfo = ''
    $portalUrl = 'https://intune.microsoft.com/#view/Microsoft_Intune_DeviceSettings/TenantAdminMenu/~/multiAdminApproval'

    if ($policies.Count -gt 0) {
        $tableRows = ''
        # Cap output at 10 rows; append a summary row when the list is longer.
        $displayedPolicies = if ($policies.Count -gt 10) {
            $policies[0..9]
        }
        else {
            $policies
        }

        foreach ($policy in $displayedPolicies) {
            $policyName = Get-SafeMarkdown $policy.displayName
            $policyType = $policy.policyType
            $platform = $policy.policyPlatform
            $lastModified = Get-FormattedDate $policy.lastModifiedDateTime
            # Use Q2 detail for approverGroupIds.
            # Distinguish Q2 fetch failure ($null detail) from a genuine policy with no approver group.
            $detail = $policyDetails[$policy.id]
            if ($null -eq $detail) {
                $approverGroupCount = 'Error'
                $rowStatus = '⚠️ Unknown'
            }
            elseif ($detail.approverGroupIds -and $detail.approverGroupIds.Count -gt 0) {
                $approverGroupCount = $detail.approverGroupIds.Count
                $rowStatus = '✅ Pass'
            }
            else {
                $approverGroupCount = 0
                $rowStatus = '❌ Fail'
            }
            $policyLink = "https://intune.microsoft.com/#view/Microsoft_Intune_DeviceSettings/AccessPolicyPropertiesSummary/policyId/$($policy.id)"

            $tableRows += "| [$policyName]($policyLink) | $policyType | $platform | $approverGroupCount | $lastModified | $rowStatus |`n"
        }

        if ($policies.Count -gt 10) {
            $tableRows += "| ... ($($policies.Count) total) | | | | | |`n"
        }

        $formatTemplate = @'
 
## [Intune Multi Admin Approval access policies]({1})
 
| Policy name | Policy type | Platform | Approver group count | Last modified | Status |
| :---------- | :---------- | :------- | :------------------- | :------------ | :----- |
{0}
'@

        $mdInfo = $formatTemplate -f $tableRows, $portalUrl
    }

    $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo
    #endregion Report Generation

    $params = @{
        TestId = '51015'
        Title  = 'Multi Admin Approval is enabled in Intune to require a second admin to approve sensitive tenant changes'
        Status = $passed
        Result = $testResultMarkdown
    }
    Add-ZtTestResultDetail @params
}