Recently, I took on the challenge of improving our compliance policies, particularly by breaking them down into distinct configurations for better transparency. This setup aims to make it clear in views and reports which specific policy a device fails to meet. We also tailored email notifications to match each policy individually. However, along the way, some unexpected problems emerged that I want to share.

The Problem

I observed an issue with compliance policies for COPE (Corporate-Owned, Personally Enabled) devices. Despite clearly defined settings, the reports indicated that policies for devices, particularly under the fully managed, dedicated, and corporate-owned work profile, were reflecting configurations that were never specified during policy creation.

Hereā€™s what caught my attention:

The most baffling part was the ā€œPer-settings statusā€ in the compliance policy. At the top of this view, there were some empty entries showing 11 compliant devices, and further down, the ā€œPlay Integrityā€ setting showed some devices as compliant and others as ā€˜Not applicableā€™.

Per setting

Root Cause and Workaround šŸ› ļø

Using PowerShell and Microsoft Graph (check out my post on using Microsoft Edge Dev Tools here), I discovered that the compliance policy template for COPE devices had some built-in settings that were fixed by Microsoft, such as:

I received confirmation from Microsoft about my discovery, though thereā€™s no timeline for when this issue will be resolved. For now, the workaround involves creating compliance policies programmatically from code rather than relying on the default templates.

Below is a PowerShell script I prepared to create a compliance policy for COPE devices. This script helps overcome the built-in limitations by allowing more direct configuration and customization:

# Set the authorization header for Graph API
$Headers = @{  
    "Authorization" = "Bearer eyJ0e..."  # Replace with your valid OAuth token
}
$Uri = "https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicies/"  # Endpoint for device compliance policies

# Define the compliance policy details
$compliancePolicy = [pscustomobject]@{
    displayName              = "DISPLAYNAME"  # Name of the compliance policy
    description              = "DESCRIPTION"  # Description of the compliance policy
    roleScopeTagIds          = @()  # Define role scope tags if any
    "@odata.type"            = "#microsoft.graph.androidDeviceOwnerCompliancePolicy"  # Type of compliance policy
    
    # Define scheduled actions for specific rules
    scheduledActionsForRule  = @(
        [pscustomobject]@{
            ruleName                      = "PasswordRequired"  # Rule to enforce password requirement
            scheduledActionConfigurations = @(
                [pscustomobject]@{
                    actionType                = "block"  # Action to block non-compliant devices
                    gracePeriodHours          = 0  # No grace period
                    notificationTemplateId    = ""  # No notification template assigned
                    notificationMessageCCList = @()  # No additional notifications
                }
            )
        }
    )
    localActions             = @()  # Placeholder for local actions, if any
    
    # Uncomment and configure the following settings as needed:
    # deviceThreatProtectionEnabled                      = $false
    # deviceThreatProtectionRequiredSecurityLevel        = "unavailable"
    # advancedThreatProtectionRequiredSecurityLevel      = "unavailable"
    # securityRequireSafetyNetAttestationBasicIntegrity  = $false
    # securityRequireSafetyNetAttestationCertifiedDevice = $false
    # osMinimumVersion                                   = "11.0"
    # osMaximumVersion                                   = ""
    # minAndroidSecurityPatchLevel                       = "2024-04-01"
    # passwordRequired                                   = $true
    # passwordMinimumLength                              = 6
    # passwordRequiredType                               = "numericComplex"
    # passwordMinutesOfInactivityBeforeLock              = 1
    # passwordExpirationDays                             = 365
    # passwordPreviousPasswordCountToBlock               = 3
    storageRequireEncryption = $true  # Require storage encryption
}

# Convert the compliance policy to JSON
$jsonString = ConvertTo-Json -InputObject $compliancePolicy -Depth 10

# Send the request to create the compliance policy
Invoke-RestMethod -UseBasicParsing -Uri $Uri `
    -Method "POST" `
    -Headers $Headers `
    -ContentType "application/json" `
    -Body $jsonString

Results After Implementing Workaround āœ…

Summary šŸ“

The compliance policy mechanism functions as intended in verifying the specified settings, but the built-in reports provided by Microsoft for COPE policies contain some inaccuracies. For now, manual intervention through custom coding seems to be the best way to overcome these discrepancies.

This experience serves as a reminder: even well-established tools can come with hidden surprises, and sometimes we need to get our hands dirty with PowerShell scripts to make things right. šŸ’Ŗ

See you in next! šŸ˜‰ šŸ§