tests/Test-Assessment.51018.ps1

<#
.SYNOPSIS
    Windows App Control for Business (WDAC) is configured and assigned via Intune
 
.DESCRIPTION
    Checks whether at least one Intune App Control for Business policy is configured with a build option
    (Microsoft built-in controls, configure XML, or upload XML) and assigned to managed Windows devices.
    Without App Control for Business, every executable a user can drop onto disk can run with the user's
    full token before any EDR signal fires. App Control disrupts this kill chain at the kernel level by
    establishing a Windows code-integrity policy that only permits executables signed by trusted publishers,
    on a trusted file path, or specifically allow-listed by the organization to run.
 
.NOTES
    Test ID: 51018
    Category: Devices
    Pillar: Devices
    Required APIs:
      Q1: GET beta/deviceManagement/managedDevices?$filter=operatingSystem eq 'Windows'&$top=1
      Q2: Export DB — ConfigurationPolicy (templateReference.templateFamily = 'endpointSecurityApplicationControl')
    Required Permissions: DeviceManagementConfiguration.Read.All, DeviceManagementManagedDevices.Read.All
#>


function Test-Assessment-51018 {
    [ZtTest(
        Category = 'Devices',
        CompatibleLicense = ('INTUNE_A'),
        ImplementationCost = 'Medium',
        Pillar = 'Devices',
        RiskLevel = 'High',
        SfiPillar = 'Protect engineering systems',
        TenantType = ('Workforce'),
        TestId = 51018,
        Title = 'Windows App Control for Business (WDAC) is configured and assigned via Intune',
        UserImpact = 'Medium'
    )]
    [CmdletBinding()]
    param(
        $Database
    )

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

    $activity = 'Checking that Windows App Control for Business policies are configured and assigned'
    $investigateMsg = 'The Intune App Control for Business policies API returned an authorization (401/403) or transient (5xx) error, so coverage could not be determined. Re-run after verifying caller permissions — Global Reader at tenant scope.'

    # Q1: Count enrolled Windows devices to determine if the check is in scope.
    # If zero Windows devices are enrolled, the check is reported as Skipped.
    Write-ZtProgress -Activity $activity -Status 'Checking enrolled Windows devices'
    $windowsDeviceCount = 0
    $q1Error = $false

    try {
        $windowsResult = Invoke-ZtGraphRequest -RelativeUri 'deviceManagement/managedDevices' -Filter "operatingSystem eq 'Windows'" -Select 'id' -Top 1 -QueryParameters @{'$count' = 'true'} -ApiVersion beta -DisablePaging -ErrorAction Stop
        $windowsDeviceCount = $windowsResult.'@odata.count'
    }
    catch {
        $q1Error = $true
        Write-PSFMessage "Windows enrolled-device query (Q1) failed: $_" -Tag Test -Level Warning
    }

    # If Q1 failed, scope cannot be determined — stop early.
    if ($q1Error) {
        $params = @{
            TestId       = '51018'
            Title        = 'Windows App Control for Business (WDAC) is configured and assigned via Intune'
            Status       = $false
            Result       = '⚠️ Unable to retrieve enrolled Windows device count due to API access error. Coverage could not be determined. Verify DeviceManagementManagedDevices.Read.All permission is granted and retry.'
            CustomStatus = 'Investigate'
        }
        Add-ZtTestResultDetail @params
        return
    }

    # Guard: Skip if no Windows devices are enrolled.
    if ($windowsDeviceCount -eq 0) {
        Add-ZtTestResultDetail -SkippedBecause NotApplicable -Result 'No Windows devices are enrolled in this tenant.'
        return
    }

    Write-ZtProgress -Activity $activity -Status 'Getting App Control for Business policies'

    # Q2: Retrieve all Intune Settings Catalog configuration policies in the App Control for Business template family.
    $q2Error = $false
    try {
        $sql = @"
    SELECT id, name, platforms, technologies, templateReference, to_json(settings) as settings, to_json(assignments) as assignments
    FROM ConfigurationPolicy
    WHERE templateReference.templateFamily = 'endpointSecurityApplicationControl'
"@

        $appControlPolicies = Invoke-DatabaseQuery -Database $Database -Sql $sql -AsCustomObject -ErrorAction Stop
    }
    catch {
        $q2Error = $true
        Write-PSFMessage "App Control policy query (Q2) failed: $_" -Tag Test -Level Warning
    }

    # If Q2 failed, policy evaluation cannot proceed — stop early.
    if ($q2Error) {
        $params = @{
            TestId       = '51018'
            Title        = 'Windows App Control for Business (WDAC) is configured and assigned via Intune'
            Status       = $false
            Result       = "⚠️ $investigateMsg"
            CustomStatus = 'Investigate'
        }
        Add-ZtTestResultDetail @params
        return
    }

    # Deserialize JSON fields and enrich each policy with evaluation metadata.
    foreach ($policy in $appControlPolicies) {
        if ($policy.settings -is [string]) {
            $policy.settings = $policy.settings | ConvertFrom-Json
        }
        if ($policy.assignments -is [string]) {
            $policy.assignments = $policy.assignments | ConvertFrom-Json
        }

        # Determine if the policy targets Windows (platforms must contain 'windows10').
        $isWindowsPolicy = $policy.platforms -and ($policy.platforms -match 'windows10')
        $policy | Add-Member -NotePropertyName IsWindowsPolicy -NotePropertyValue $isWindowsPolicy -Force

        # Detect the build-option setting in settings[*].settingInstance.settingDefinitionId.
        # Build-option values end with: _built_in_controls_selected, _configure_xml_selected, _upload_xml_selected.
        $buildOption = 'Not configured'
        if ($policy.settings -and $policy.settings.Count -gt 0) {
            foreach ($setting in $policy.settings) {
                $settingId = $setting.settingInstance.settingDefinitionId
                $choiceValue = $setting.settingInstance.choiceSettingValue.value

                if ($settingId -match 'device_vendor_msft_policy_config_applicationcontrol' -and $choiceValue) {
                    if ($choiceValue -match '_built_in_controls_selected$') {
                        $buildOption = 'Built-in controls'
                        break
                    }
                    elseif ($choiceValue -match '_configure_xml_selected$') {
                        $buildOption = 'Configure XML'
                        break
                    }
                    elseif ($choiceValue -match '_upload_xml_selected$') {
                        $buildOption = 'Upload XML'
                        break
                    }
                }
            }
        }
        $policy | Add-Member -NotePropertyName BuildOption -NotePropertyValue $buildOption -Force

        # Check assignment state using assignments.Count > 0 (do NOT rely on isAssigned).
        $isAssigned = $policy.assignments -and $policy.assignments.Count -gt 0
        $policy | Add-Member -NotePropertyName IsAssigned -NotePropertyValue $isAssigned -Force
    }
    #endregion Data Collection

    #region Assessment Logic
    # Filter to policies that target Windows.
    $windowsPolicies = @($appControlPolicies | Where-Object { $_.IsWindowsPolicy })

    # Pass: at least one Windows policy has a configured build option AND is assigned.
    $qualifyingPolicies = @($windowsPolicies | Where-Object { $_.BuildOption -ne 'Not configured' -and $_.IsAssigned })
    $passed = $qualifyingPolicies.Count -gt 0

    if ($passed) {
        $testResultMarkdown = "✅ Windows App Control for Business is configured (either via Microsoft built-in controls or a custom WDAC XML policy) and assigned to managed Windows devices via at least one Intune Settings Catalog policy.`n`n%TestResult%"
    }
    else {
        $testResultMarkdown = "❌ Windows devices are enrolled but no Intune App Control for Business policy is configured AND assigned — managed Windows devices have no kernel-level allow-listing of executables, allowing unsigned and unknown binaries to run with the user's token before any detective control fires.`n`n%TestResult%"
    }
    #endregion Assessment Logic

    #region Report Generation
    $portalUrl = 'https://intune.microsoft.com/#view/Microsoft_Intune_Workflows/SecurityManagementMenu/~/appControl'

    # Build the summary status line.
    $verdictMd = if ($passed) { 'Pass' } else { 'Fail' }
    $statusLine = "**Status: $verdictMd** — $($qualifyingPolicies.Count) of $($windowsPolicies.Count) App Control policies are assigned with a configured build option."

    $mdInfo = ''
    $tableRows = ''

    if ($windowsPolicies.Count -gt 0) {
        $displayedPolicies = if ($windowsPolicies.Count -gt 10) { $windowsPolicies[0..9] } else { $windowsPolicies }

        foreach ($policy in $displayedPolicies) {
            $policyName = Get-SafeMarkdown $policy.name
            $buildOption = $policy.BuildOption
            $assignedText = if ($policy.IsAssigned) { '✅ Yes' } else { '❌ No' }
            $policyQualifies = ($policy.BuildOption -ne 'Not configured' -and $policy.IsAssigned)
            $policyStatus = if ($policyQualifies) { '✅ Pass' } else { '❌ Fail' }

            $encodedTechnologies = ([string]$policy.technologies) -replace ',', '%2C'
            $policyLink = "https://intune.microsoft.com/#view/Microsoft_Intune_Workflows/PolicySummaryBlade/policyId/$($policy.id)/technology/$encodedTechnologies/templateId/$($policy.templateReference.templateId)/platformName/$($policy.platforms)"

            $tableRows += "| [$policyName]($policyLink) | $buildOption | $assignedText | $policyStatus |`n"
        }

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

        $formatTemplate = @'
 
## [{0}]({1})
 
{2}
 
| Policy name | Build option | Assigned | Status |
| :---------- | :----------- | :------- | :----- |
{3}
'@

        $mdInfo = $formatTemplate -f 'Windows App Control for Business — Settings Catalog policy posture', $portalUrl, $statusLine, $tableRows
    }
    else {
        # Edge case: Q2 returned policies but none target Windows.
        $formatTemplate = @'
 
## [{0}]({1})
 
**Status: Fail** — No App Control policies target the Windows platform.
'@

        $mdInfo = $formatTemplate -f 'Windows App Control for Business — Settings Catalog policy posture', $portalUrl
    }

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

    $params = @{
        TestId = '51018'
        Title  = 'Windows App Control for Business (WDAC) is configured and assigned via Intune'
        Status = $passed
        Result = $testResultMarkdown
    }
    Add-ZtTestResultDetail @params
}