Custom Compliance Policies for MacOS
Are you already using custom compliance policies for Windows? It's a nice feature, isn't it? Wouldn't it be cool to have something similar for macOS devices to use it in conditional access policies?
All you need is a Custom Attribute and some PowerShell (...and an Automation Account and a Key Vault and an App Registration 😃
Custom Attribute
First we need to create the custom attribute itself. For this we need a bash script that checks something on the device and provides an output. Like checking if a specific process is running. the most common use case is probably to check some crucial process like AntiVirus, EDR an so on.
#!/bin/bash
PROCESS=AVProcessName
number=$(ps aux | grep -ci $PROCESS)
if [ $number -gt 0 ]
then
echo Running;
fi
In the device status view it will look like this:
From Custom Attribute to ExtensionAttribute
Unfortunately we can't use this custom attribute directly in an conditional access policy. We only can use properties on the devices EntraID object.
Which property could we use..... hmm.... what's that at the bottom of the dropdown??? ExtensionAttribute!
The Script
In the script we need to do 3 things:
- Get all devices that have the desired output as their custom attribute value (= compliant devices)
- Write the ExtensionAttribute for all compliant devices (which don't already have it)
- Remove the ExtensionAttribute on all devices that are non compliant (which have it)
Here is the script:
<#
.SYNOPSIS
This script is used to implement a similar functionality like Custom Compliance Scripts for Windows devices.
.DESCRIPTION
This script collects all macOS devices from Intune that have a specific result from custom attribute script execution.
For these devices ExtensionAttribute1 is set to "Compliant". Devices that already have the ExtAttr. but aren't compliant anymore, the ExtAttr. will be cleared.
The ExtensionAttribute1 can be used as a device filter on a Conditional Access policy.
.NOTES
Author : Max Weber / https://www.intuneblog.org
Contact : max.weber(at)mailbox.org
#>
# ID of the Custom Attribute Script in Intune
$scriptId = "<Paste ID of Custom Attribute Script here"
# Required Output of the script for a device to be 'compliant'
$desiredResult = "Running"
# Output which should be written to the ExtensionAttribute1 if a device is compliant
$desiredExtensionAttributeOutput = "Compliant"
# Connecto Managed Identity
Connect-AzAccount -Identity
# Credentials from Key Vault
$VaultName = "<the name of your key vault>"
$TenantId = Get-AzKeyVaultSecret -VaultName $VaultName -Name 'TenantId' -AsPlainText
$ClientId = Get-AzKeyVaultSecret -VaultName $VaultName -Name 'AppRegistration-IntuneAutomation-ClientId' -AsPlainText
$ClientSecretCredential = Get-AzKeyVaultSecret -VaultName $VaultName -Name 'AppRegistration-IntuneAutomation-ClientSecret' -AsPlainText
$Creds = [System.Management.Automation.PSCredential]::new($ClientId, (ConvertTo-SecureString $ClientSecretCredential -AsPlainText -Force))
Connect-MgGraph -NoWelcome -ClientSecretCredential $Creds -TenantId $TenantId
# Get Devices where desired Result matches
$compliantDevicesEntraId = @()
try {
Write-Output "Getting devices with Result '$desiredResult' from script $scriptId"
$compliantDevices = $(Get-MgBetaDeviceManagementScriptDeviceRunState -ErrorAction Stop -All -DeviceManagementScriptId $scriptId -ExpandProperty ManagedDevice | Where-Object ResultMessage -eq $desiredResult).ManagedDevice.Id
Write-Output "Found $($compliantDevices.Count) Compliant Devices in Intune"
$compliantDevices | ForEach-Object {
$entraDeviceId = $(Get-MgBetaDeviceManagementManagedDevice -ManagedDeviceId $_).AzureAdDeviceId
if($entraDeviceId -ne "00000000-0000-0000-0000-000000000000") {
$compliantDevicesEntraId += $entraDeviceId
}
}
Write-Output "Found $($compliantDevicesEntraId.Count) Compliant Devices with valid EntraID device object"
}
catch {
Write-Error $_
Write-Error "Aborting Script"
Exit 1
}
# Remove ExtensionAttribute1 from all devices which are not compliant
$currentMembers = Get-MgBetaDevice -Filter "OperatingSystem eq 'macMDM'" -all -Property DeviceId, ExtensionAttributes | Select-Object -Property DeviceId, ExtensionAttributes -ExpandProperty ExtensionAttributes | Where-Object {$_.ExtensionAttribute1 -eq $desiredExtensionAttributeOutput}
$currentMembers | Where-Object {
$_.DeviceId -ne $null -and !$compliantDevicesEntraId.Contains($($_.DeviceId))} | ForEach-Object {
try {
Write-Output "Removing ExtensionAttribute1 from $($_.DeviceId)"
$params = @{
extensionAttributes = @{
extensionAttribute1 = ""
}
}
Update-MgBetaDeviceByDeviceId -DeviceId $_.DeviceId -BodyParameter $params
}
catch {
Write-Error $_
}
}
# Write ExtensionAttribute1 on new devices
$compliantDevicesEntraId | Where-Object {
!$currentMembers -or !$currentMembers.DeviceId.Contains($_)} | ForEach-Object {
try {
Write-Output "Writing ExtensionAttribute1 for device $_"
$params = @{
extensionAttributes = @{
extensionAttribute1 = $desiredExtensionAttributeOutput
}
}
Update-MgBetaDeviceByDeviceId -DeviceId $_ -BodyParameter $params
}
catch {
Write-Error $_
}
}
The script is meant to be running in an Automation Account. For local testing you can comment out lines 31 to 42 and run Connect-MgGraph first, to authenticate with a user account.
Lines 23, 26 and 29 have to be adjusted for your environment (you can get the script Id from the browsers address bar).
I'm not handling the setup of the automation account and the key vault in this post. Please refer to Powerstacks.com (thx to John Marcum) for detailed instructions.
You need to add the following modules to the automation account:
- Microsoft.Graph.Authentication
- Microsoft.Graph.Beta.DeviceManagement
- Microsoft.Graph.Beta.Identity.DirectoryManagement
The Conditional Access Policy
Now we can edit the conditional access policy and add a device filter rule:
Depending on the design of your CA policies you can Include or Exclude compliant devices.
Considerations
The custom attribute script is executed every 8 hours. That's why there is a delay between the actual status on the device and the status of the extension attribute. Especially for recently enrolled devices this can be an issue, because the custom attribute script ran before the AntiVirus-Software (or whatever) was installed.
Maybe the script could be improved with special handling of new devices (enrollment date < 24h). What do you think?