Synchronizing Device Groups with Entra User Groups using PowerShell

One fun problem I tend to have on a weekly basis is being given a list of usernames and being asked to deploy a software or policy to their devices. Well, ultimately, I need their device names and not user names to properly deliver on the ask. If done manually, you need to find or create a device group, then locate each user and their respective devices (assuming the devices are Intune managed) and add them to the device group. This process can be tedious if the number of users is high. Additionally, this group needs to be maintained, adding and removing devices based on users added and removed from the source group.

I wrote a simple script to make this process easy and consistent.

Output

Script Requirements

Before running the script, ensure that the following requirements are met:

1️⃣ PowerShell Version

  • PowerShell 7.0 or later is required to utilize ForEach-Object -Parallel for concurrent execution.

Check your version with:

$PSVersionTable.PSVersion

2️⃣ Required PowerShell Modules

  • The script relies on the Microsoft.Graph.Beta module.

Install it if not already available:

Install-Module Microsoft.Graph.Beta -Scope CurrentUser -Force

3️⃣ Microsoft Graph API Permissions

  • The script requires the following Microsoft Graph API permissions:
    • Group.ReadWrite.All
    • DeviceManagementManagedDevices.Read.All
    • Directory.ReadWrite.All
  • These permissions should be granted to the account running the script.

How the Script Works (Step-by-Step Walkthrough)

1️⃣ Connect to Microsoft Graph API

Connect-MgGraph -Scopes "Group.ReadWrite.All", "DeviceManagementManagedDevices.Read.All", "Directory.ReadWrite.All"

2️⃣ Retrieve the User Group

The script searches for an Entra ID user group by its display name:

$userGroup = Get-MgBetaGroup -Filter "displayName eq '$UserGroupName'" | Select-Object -First 1

If the group does not exist, an error is thrown, and the script exits.

3️⃣ Create or Retrieve the Corresponding Device Group

A device group named $UserGroupName-Devices is required. If it does not exist, the script creates it:

$deviceGroupName = "$UserGroupName-Devices"
$deviceGroup = Get-MgBetaGroup -Filter "displayName eq '$deviceGroupName'" | Select-Object -First 1
if (-not $deviceGroup) {
    $groupParams = @{ ... }
    $deviceGroup = New-MgBetaGroup -BodyParameter $groupParams
}

Example: "Finance-Devices"

The naming convention can always be modified to suit your needs.

4️⃣ Retrieve All Users in the User Group

To identify the devices to be included in the device group, the script fetches all members of the user group:

$userMembers = Get-MgBetaGroupMember -GroupId $userGroup.Id -All |
Where-Object { $_.AdditionalProperties.'@odata.type' -eq "#microsoft.graph.user" }

5️⃣ Retrieve Each User’s Managed Devices and Current Group Members (Parallel Execution)

Using ForEach-Object -Parallel, the script retrieves devices for each user concurrently to maximize performance:

$deviceIdsFromUsers = $userMembers | ForEach-Object -Parallel {
    $devices = Get-MgBetaUserManagedDevice -UserId $_.Id -All
    $devices | ForEach-Object { $_.AzureAdDeviceId }
} -ThrottleLimit 10

Next, we gather all the current devices in the group:

$currentDevices = Get-MgBetaGroupMember -GroupId $deviceGroup.Id -All |
Where-Object { $_.AdditionalProperties.'@odata.type' -eq "#microsoft.graph.device" }
$currentDeviceIds = $currentDevices | ForEach-Object { $_.AdditionalProperties.deviceId }
Write-Output "Current device group contains $($currentDeviceIds.Count) device(s)."

6️⃣ Compare Desired vs. Current Device Group Membership

The script then compares the retrieved device IDs against the current device group membership:

$devicesToAdd = $desiredDeviceIds | Where-Object { $_ -notin $currentDeviceIds }
$devicesToRemove = $currentDeviceIds | Where-Object { $_ -notin $desiredDeviceIds }

This step determines which devices need to be added or removed.

7️⃣ Add Missing Devices (Parallel Execution)

Devices that are not currently in the device group but should be are added concurrently:

$devicesToAdd | ForEach-Object -Parallel {
    $device = Get-MgBetaDevice -Filter "DeviceID eq '$_'"
    $params = @{ "@odata.id" = "https://graph.microsoft.com/beta/directoryObjects/$($device.Id)" }
    New-MgBetaGroupMemberByRef -GroupId $using:deviceGroup.Id -BodyParameter $params
} -ThrottleLimit 10

This speeds up the process significantly by leveraging multiple threads.

8️⃣ Remove Devices That No Longer Belong (Parallel Execution)

Similarly, devices that should no longer be part of the group are removed concurrently:

$devicesToRemove | ForEach-Object -Parallel {
    $device = Get-MgBetaDevice -Filter "DeviceID eq '$_'"
    Remove-MgBetaGroupMemberByRef -GroupId $using:deviceGroup.Id -DirectoryObjectId $($device.Id)
} -ThrottleLimit 10

9️⃣ Final Confirmation

Once all operations are complete, the script outputs a confirmation message:

Write-Output "Device group synchronization complete."

💡 Key Benefits of This Approach

✅ Automated Group Management – Eliminates manual work, reducing administrative overhead.
✅ Real-Time Synchronization – Ensures device groups always reflect the latest user memberships.
✅ Optimized Performance – Uses PowerShell 7’s parallel processing for fast execution.
✅ Scalability – Works efficiently for both small and large organizations.

Full Script:

IntuneScripts/UserDeviceSync/Sync-UserDeviceGroup.ps1 at main · jorgeasaurus/IntuneScripts
Contribute to jorgeasaurus/IntuneScripts development by creating an account on GitHub.

Bonus

I am not sure if this is a bug or a feature:

# This command works
Get-MgBetaDevice -Filter "DeviceID eq '$deviceID'"

# This does not work
Get-MgBetaDevice -DeviceID $deviceID

Subscribe to > Jorgeasaurus

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe