tests/Test-Assessment.35016.ps1

<#
.SYNOPSIS
    Mandatory Labeling Enabled for Sensitivity Labels
#>


function Test-Assessment-35016 {
    [ZtTest(
        Category = 'Information Protection',
        ImplementationCost = 'Medium',
        MinimumLicense = ('Microsoft 365 E3'),
        Pillar = 'Data',
        RiskLevel = 'High',
        SfiPillar = 'Protect tenants and production systems',
        TenantType = ('Workforce','External'),
        TestId = 35016,
        Title = 'Mandatory labeling enabled for sensitivity labels',
        UserImpact = 'High'
    )]
    [CmdletBinding()]
    param()

    #region Data Collection
    Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose
    $activity = 'Checking mandatory labeling configuration'

    # Q1: Retrieve all enabled sensitivity label policies to assess mandatory labeling configuration
    Write-ZtProgress -Activity $activity -Status 'Getting sensitivity label policies'
    $errorMsg = $null
    $enabledPolicies = @()

    try {
        $enabledPolicies = Get-LabelPolicy -ErrorAction Stop | Where-Object { $_.Enabled -eq $true }
    }
    catch {
        $errorMsg = $_
        Write-PSFMessage "Error querying label policies: $_" -Level Error
    }
    #endregion Data Collection

    #region Assessment Logic
    $allPolicySettings = @()
    $mandatoryPolicies = @()
    $xmlParseErrors = @()
    $passed = $false
    $customStatus = $null

    if ($errorMsg) {
        $testResultMarkdown = "⚠️ Unable to determine auto-labeling enforcement mode status due to permissions issues or query failure.`n`n"
        $customStatus = 'Investigate'
    }
    else {
        Write-PSFMessage "Found $($enabledPolicies.Count) enabled label policies" -Level Verbose

        try {
            # Examine label policy settings for mandatory labeling
            foreach ($policy in $enabledPolicies) {
                # Determine policy scope:
                # - Global if any location is set to "All"
                # - Scoped if specific users/groups are defined
                $allLocationNames = @(
                    $policy.ExchangeLocation.Name
                    $policy.ModernGroupLocation.Name
                    $policy.SharePointLocation.Name
                    $policy.OneDriveLocation.Name
                    $policy.SkypeLocation.Name
                    $policy.PublicFolderLocation.Name
                ) | Where-Object { $_ }

                $isGlobal = $allLocationNames -contains 'All'

                $policySettings = @{
                    PolicyName = $policy.Name
                    Guid = $policy.Guid
                    Enabled = $policy.Enabled
                    EmailMandatory = $false
                    TeamworkMandatory = $false
                    SiteGroupMandatory = $false
                    PowerBIMandatory = $false
                    EmailOverride = $false
                    Scope = if ($isGlobal) { 'Global' } else { 'User/Group-scoped' }
                    LabelsCount = $policy.Labels.Count
                }

                # Parse PolicySettingsBlob XML for mandatory labeling flags
                if (-not [string]::IsNullOrWhiteSpace($policy.PolicySettingsBlob)) {
                    try {
                        $xmlSettings = [xml]$policy.PolicySettingsBlob

                        # Validate XML structure before accessing properties
                        if ($xmlSettings.settings -and $xmlSettings.settings.setting) {
                            # Access settings as XML elements for direct property lookup
                            foreach ($setting in $xmlSettings.settings.setting) {
                                # Add null safety for key and value attributes
                                if (-not $setting.key -or -not $setting.value) {
                                    Write-PSFMessage "Skipping setting with null key or value in policy '$($policy.Name)'" -Level Verbose
                                    continue
                                }

                                $key = $setting.key.ToLower()
                                $value = $setting.value.ToLower()

                                switch ($key) {
                                    'mandatory' {
                                        $policySettings.EmailMandatory = ($value -eq 'true')
                                    }
                                    'teamworkmandatory' {
                                        $policySettings.TeamworkMandatory = ($value -eq 'true')
                                    }
                                    'siteandgroupmandatory' {
                                        $policySettings.SiteGroupMandatory = ($value -eq 'true')
                                    }
                                    'powerbimandatory' {
                                        $policySettings.PowerBIMandatory = ($value -eq 'true')
                                    }
                                    'disablemandatoryinoutlook' {
                                        $policySettings.EmailOverride = ($value -eq 'true')
                                    }
                                    default {
                                        Write-PSFMessage "Unknown setting key '$key' in policy '$($policy.Name)'" -Level Verbose
                                    }
                                }
                            }
                        }
                        else {
                            Write-PSFMessage "Policy '$($policy.Name)' has PolicySettingsBlob but no settings elements found" -Level Verbose
                        }
                    }
                    catch {
                        # Track parsing errors for reporting
                        $xmlParseErrors += [PSCustomObject]@{
                            PolicyName = $policy.Name
                            Error = $_.Exception.Message
                        }
                        Write-PSFMessage "Error parsing PolicySettingsBlob XML for policy '$($policy.Name)': $_" -Level Warning
                    }
                }

                # Per Microsoft documentation, disablemandatoryinoutlook can be set to explicitly
                # disable mandatory labeling in Outlook even when the 'mandatory' setting is true.
                # This provides an exception path for organizations that need mandatory labeling
                # for files but not emails. Apply the override logic:
                if ($policySettings.EmailMandatory -and $policySettings.EmailOverride) {
                    $policySettings.EmailMandatory = $false
                }

                # Store all policy settings
                $allPolicySettings += [PSCustomObject]$policySettings

                # Determine if this policy has ANY mandatory setting enabled (after applying overrides)
                $hasMandatory = $policySettings.EmailMandatory -or
                                $policySettings.TeamworkMandatory -or
                                $policySettings.SiteGroupMandatory -or
                                $policySettings.PowerBIMandatory

                if ($hasMandatory) {
                    $mandatoryPolicies += [PSCustomObject]$policySettings
                }
            }
        }
        catch {
            Write-PSFMessage "Error parsing label policy settings: $_" -Level Error
            $testResultMarkdown = "⚠️ Unable to determine mandatory labeling status due to policy complexity or unclear Settings structure.`n`n"
            $customStatus = 'Investigate'
        }

        # Determine pass/fail status and message (only if no error occurred)
        if ($null -eq $customStatus) {
            if ($mandatoryPolicies.Count -gt 0) {
                $passed = $true
                $testResultMarkdown = "✅ Mandatory labeling is configured and enforced through at least one active sensitivity label policy across one or more workloads (Outlook, Teams/OneDrive, SharePoint/Microsoft 365 Groups, or Power BI).`n`n%TestResult%"
            }
            else {
                $passed = $false

                if ($enabledPolicies.Count -eq 0) {
                    $testResultMarkdown = "❌ No enabled sensitivity label policies were found in your tenant.`n`n%TestResult%"
                }
                else {
                    $testResultMarkdown = "❌ No sensitivity label policies require users to apply labels across any workload (emails, files, sites, groups, or Power BI content).`n`n%TestResult%"
                }
            }
        }
    }

    #endregion Assessment Logic

    #region Report Generation
    $mdInfo = ''

    # Show table whenever we have policy settings
    if ($allPolicySettings.Count -gt 0) {
        # Build policy table
        $mdInfo += "`n`n### [Enabled label policies](https://purview.microsoft.com/informationprotection/labelpolicies)`n"
        $mdInfo += "| Policy name | Email | Files/Collab | Sites/Groups | Power BI | Email override | Scope | Labels |`n"
        $mdInfo += "| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |`n"

        foreach ($policy in $allPolicySettings) {
            $policyName = Get-SafeMarkdown -Text $policy.PolicyName
            $emailIcon = if ($policy.EmailMandatory) { '✅' } else { '❌' }
            $teamworkIcon = if ($policy.TeamworkMandatory) { '✅' } else { '❌' }
            $siteGroupIcon = if ($policy.SiteGroupMandatory) { '✅' } else { '❌' }
            $powerBIIcon = if ($policy.PowerBIMandatory) { '✅' } else { '❌' }
            $overrideIcon = if ($policy.EmailOverride) { 'Yes' } else { 'No' }
            $mdInfo += "| $policyName | $emailIcon | $teamworkIcon | $siteGroupIcon | $powerBIIcon | $overrideIcon | $($policy.Scope) | $($policy.LabelsCount) |`n"
        }

        # Build summary metrics
        $emailCount = ($mandatoryPolicies | Where-Object { $_.EmailMandatory }).Count
        $teamworkCount = ($mandatoryPolicies | Where-Object { $_.TeamworkMandatory }).Count
        $siteGroupCount = ($mandatoryPolicies | Where-Object { $_.SiteGroupMandatory }).Count
        $powerBICount = ($mandatoryPolicies | Where-Object { $_.PowerBIMandatory }).Count

        $mdInfo += "`n`n### Summary`n"
        $mdInfo += "| Metric | Count |`n"
        $mdInfo += "| :--- | :--- |`n"
        $mdInfo += "| Total enabled label policies | $($allPolicySettings.Count) |`n"
        $mdInfo += "| Total enabled label policies with mandatory labeling | $($mandatoryPolicies.Count) |`n"
        $mdInfo += "| Email mandatory labeling | $emailCount |`n"
        $mdInfo += "| File/collaboration mandatory labeling | $teamworkCount |`n"
        $mdInfo += "| Site/group mandatory labeling | $siteGroupCount |`n"
        $mdInfo += "| Power BI mandatory labeling | $powerBICount |"
    }

    # Report XML parsing errors if any occurred
    if ($xmlParseErrors.Count -gt 0) {
        $mdInfo += "`n`n### ⚠️ XML parsing errors`n"
        $mdInfo += "The following policies could not be parsed and were excluded from analysis:`n`n"
        $mdInfo += "| Policy name | Error |`n"
        $mdInfo += "| :--- | :--- |`n"
        foreach ($error in $xmlParseErrors) {
            $errorMsg = Get-SafeMarkdown -Text $error.Error
            $policyName = Get-SafeMarkdown -Text $error.PolicyName
            $mdInfo += "| $policyName | $errorMsg |`n"
        }
        $mdInfo += "`n**Note**: These policies were treated as having no mandatory labeling configured.`n"
    }

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

    $params = @{
        TestId = '35016'
        Title  = 'Mandatory labeling enabled for sensitivity labels'
        Status = $passed
        Result = $testResultMarkdown
    }

    # Add CustomStatus if status is 'Investigate'
    if ($null -ne $customStatus) {
        $params.CustomStatus = $customStatus
    }

    Add-ZtTestResultDetail @params
}