In Microsoft Configuration Manager (ConfigMgr), collections allow administrators to organize resources into manageable units for easier, large-scale management. Collections can be customized to meet specific client needs, enabling tasks such as deploying applications, managing compliance settings, and installing updates. According to the ConfigMgr documentation, creating custom collections—rather than using the default “All Systems” collection—is best practice for effectively targeting specific devices or users.
In ConfigMgr, we can create dynamic collections by defining query rules. Some examples from Microsoft documentation include:
A great reference for ConfigMgr queries is this blog post by Anders Rødland, which includes dynamic collections like
However, in Intune, creating similar dynamic collections based on device or user properties, such as hardware information or specific software installations, isn’t directly supported.
For those who started managing devices in Intune, the absence of ConfigMgr-style collections may go unnoticed. But for those of us who transitioned from ConfigMgr, this limitation is a significant hurdle. In fact, during one session that I attended at MMS Flamingo Edition 2024, a Microsoft presenter fielded many questions about this topic, with participants eager to see these features in Intune.
This blog post presents an alternative solution to help bridge the gap between ConfigMgr and Intune’s dynamic device and user grouping capabilities. I’ve created an automated approach to build the equivalent of query-based collections in Intune using Entra ID groups, Power BI, Power Automate, and Logic Apps.
Note: This post serves as an encouragement to explore creative solutions to Intune limitations rather than waiting for Microsoft to address every shortcoming. Although PowerStacks will assist its customers in implementing this solution, I’ll outline the steps here for those who wish to tackle it independently.
This solution relies on four main components:
Let’s take a closer look at each component.
Using Power BI Desktop, connect to the BI for Intune semantic model and create a simple table that lists the IDs of devices matching your criteria. For example, you can generate a table of all computers (Entra ID device ID’s) with “Zoom” installed.
This DAX query generates a list of device IDs, which we can use to populate a group in Entra ID. If you’re not a PowerStacks customer, you’ll need an alternative way to obtain this device information.
Once configured, export the DAX query, which should look something like this:
DEFINE
VAR __DS0FilterTable = TREATAS({"Zoom", "Zoom (64-bit)"}, 'App Inventory'[App Inventory Name])
VAR __DS0Core = CALCULATETABLE(DISTINCT('Device'[Azure AD Device ID]), KEEPFILTERS(__DS0FilterTable))
VAR __DS0PrimaryWindowed = TOPN(501, __DS0Core, 'Device'[Azure AD Device ID], 1)
EVALUATE
__DS0PrimaryWindowed
ORDER BY
'Device'[Azure AD Device ID]
The Power Automate flow queries the Power BI model to retrieve device IDs using the DAX query, applies a predefined group name, and posts the information to a Logic App. The flow is scheduled to run periodically. Here’s an example HTTP POST payload:
Here’s an example HTTP POST payload:
{
"type": "Http",
"inputs": {
"uri": "https://prod-02.westus2.logic.azure.com:443/workflows/<confidential_info>/triggers/When_a_HTTP_request_is_received/paths/invoke?api-version=2016-10-01&sp=%2Ftriggers%2FWhen_a_HTTP_request_is_received%2Frun&sv=1.0&sig=<confidential_info>",
"method": "POST",
"headers": {
"Content-type": "application/json"
},
"body": {
"deviceIds": "@outputs('Select')",
"groupName": "@variables('GroupName')"
}
}
}
The managed identity provides the Logic App with the necessary permissions (“GroupMember.ReadWrite.All” and “Device.Read.All”) to interact with Entra ID securely.
The Logic App receives the list of device IDs and the group name from the flow, checks for the group’s existence, and updates group membership accordingly. If the group exists, the Logic App retrieves the current group members, compares them to the provided list, and synchronizes any additions or removals. Finally, it sends an email summary of the changes.
As you can see from the screenshot below, the Logic App is quite extensive and complex, with over 80 hours invested in its development. Because of this, I opted not to provide a detailed step-by-step guide, as such a blog would be incredibly lengthy.
Below is an example of the JSON code for the Logic App workflow. This code controls the logic of creating, updating, and maintaining Entra ID groups based POST from the Flow. (Code might vary based on your exact requirements.)
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"contentVersion": "1.0.0.0",
"triggers": {
"When_a_HTTP_request_is_received": {
"type": "Request",
"kind": "Http",
"inputs": {
"schema": {
"type": "object",
"properties": {
"deviceIds": {
"type": "array",
"items": {
"type": "string"
}
},
"groupName": {
"type": "string"
}
},
"required": [
"deviceIds",
"groupName"
]
}
}
}
},
"actions": {
"HTTP_Check_Group": {
"type": "Http",
"inputs": {
"uri": "https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '@{triggerBody()?['groupName']}'",
"method": "GET",
"authentication": {
"type": "ManagedServiceIdentity",
"audience": "https://graph.microsoft.com/"
}
},
"runAfter": {
"Response": [
"Succeeded"
]
},
"runtimeConfiguration": {
"contentTransfer": {
"transferMode": "Chunked"
}
}
},
"Group_Exists": {
"type": "If",
"expression": {
"and": [
{
"greater": [
"@length(body('HTTP_Check_Group')?['value'])",
0
]
}
]
},
"actions": {
"Set_GroupID_variable": {
"type": "SetVariable",
"inputs": {
"name": "GroupID",
"value": "body('HTTP_Check_Group')?['value'][0]['id']\n"
}
},
"HTTP_Get_Group_Members": {
"type": "Http",
"inputs": {
"uri": "https://graph.microsoft.com/v1.0/groups/@{body('HTTP_Check_Group')?['value'][0]['id']}/members?$top=999&$select=id,displayName",
"method": "GET",
"authentication": {
"type": "ManagedServiceIdentity",
"audience": "https://graph.microsoft.com"
}
},
"runAfter": {
"Set_GroupID_variable": [
"Succeeded"
]
},
"runtimeConfiguration": {
"contentTransfer": {
"transferMode": "Chunked"
},
"paginationPolicy": {
"minimumItemCount": 999
}
}
},
"For_each_member": {
"type": "Foreach",
"foreach": "@body('HTTP_Get_Group_Members')?['value']\n",
"actions": {
"Append_members_array": {
"type": "AppendToArrayVariable",
"inputs": {
"name": "allMembers",
"value": "@json(concat('{\"deviceId\": \"', item()?['Id'], '\"}'))\r\n"
}
}
},
"runAfter": {
"HTTP_Get_Group_Members": [
"Succeeded"
]
}
},
"View_Members_Arrary": {
"type": "Compose",
"inputs": "@variables('allMembers')",
"runAfter": {
"For_each_member": [
"Succeeded"
]
}
}
},
"else": {
"actions": {
"HTTP_Create_Group": {
"type": "Http",
"inputs": {
"uri": "https://graph.microsoft.com/v1.0/groups",
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"body": {
"displayName": "@{triggerBody()?['groupName']}",
"mailEnabled": false,
"mailNickname": "@{replace(triggerBody()?['groupName'], ' ', '')}",
"securityEnabled": true
},
"authentication": {
"type": "ManagedServiceIdentity",
"audience": "https://graph.microsoft.com/"
}
},
"runtimeConfiguration": {
"contentTransfer": {
"transferMode": "Chunked"
}
}
},
"Get_New_GroupID": {
"type": "SetVariable",
"inputs": {
"name": "GroupID",
"value": "body('HTTP_Check_Group')?['value'][0]['id']\n"
},
"runAfter": {
"HTTP_Create_Group": [
"Succeeded"
]
}
}
}
},
"runAfter": {
"Initialize_GroupID_variable": [
"Succeeded"
],
"Initialize_allMembers_variable": [
"Succeeded"
],
"Initialize_nextLink_Variable": [
"Succeeded"
],
"Initialize_LoopDone_variable": [
"Succeeded"
],
"Initialize_DeviceIDs_Array": [
"Succeeded"
],
"HTTP_Check_Group": [
"Succeeded"
]
}
},
"Initialize_GroupID_variable": {
"type": "InitializeVariable",
"inputs": {
"variables": [
{
"name": "GroupID",
"type": "string"
}
]
},
"runAfter": {
"HTTP_Check_Group": [
"Succeeded"
]
}
},
"Initialize_allMembers_variable": {
"type": "InitializeVariable",
"inputs": {
"variables": [
{
"name": "allMembers",
"type": "array"
}
]
},
"runAfter": {
"HTTP_Check_Group": [
"Succeeded"
]
}
},
"Initialize_LoopDone_variable": {
"type": "InitializeVariable",
"inputs": {
"variables": [
{
"name": "LoopDone",
"type": "boolean",
"value": false
}
]
},
"runAfter": {
"HTTP_Check_Group": [
"Succeeded"
]
}
},
"Initialize_nextLink_Variable": {
"type": "InitializeVariable",
"inputs": {
"variables": [
{
"name": "nextLink",
"type": "string"
}
]
},
"runAfter": {
"HTTP_Check_Group": [
"Succeeded"
]
}
},
"View_the_POST_for_Troubleshooting": {
"type": "Compose",
"inputs": "@triggerBody()?['deviceIds']",
"runAfter": {
"Response": [
"Succeeded"
]
}
},
"Initialize_DeviceIDs_Array": {
"type": "InitializeVariable",
"inputs": {
"variables": [
{
"name": "DeviceIds",
"type": "array",
"value": "@triggerBody()?['deviceIds']?['body']"
}
]
},
"runAfter": {
"HTTP_Check_Group": [
"Succeeded"
]
}
},
"Response": {
"type": "Response",
"kind": "Http",
"inputs": {
"statusCode": 200,
"body": "@triggerOutputs()?['body/deviceIds']\r\n"
},
"runAfter": {}
},
"Previous_Members": {
"type": "If",
"expression": {
"and": [
{
"not": {
"equals": [
"@variables('allMembers')",
"@null"
]
}
}
]
},
"actions": {
"Add_ObjectID": {
"type": "Foreach",
"foreach": "@outputs('Select_ObjectIds')['body']",
"actions": {
"Additions": {
"type": "If",
"expression": {
"and": [
{
"contains": [
"@variables('allMembers')",
"@items('Add_ObjectID')"
]
}
]
},
"actions": {},
"else": {
"actions": {
"Append_to_Additions": {
"type": "AppendToArrayVariable",
"inputs": {
"name": "addIds",
"value": "@items('Add_ObjectID')"
}
}
}
}
}
},
"runAfter": {
"Remove_ObjectID": [
"Succeeded"
]
}
},
"Remove_ObjectID": {
"type": "Foreach",
"foreach": "@outputs('Select_allMembers')['body']",
"actions": {
"Removals": {
"type": "If",
"expression": {
"and": [
{
"contains": [
"@body('Select_ObjectIds')",
"@items('Remove_ObjectID')"
]
}
]
},
"actions": {},
"else": {
"actions": {
"Append_to_Removals": {
"type": "AppendToArrayVariable",
"inputs": {
"name": "removeIds",
"value": "@items('Remove_ObjectID')"
}
}
}
}
}
}
}
},
"else": {
"actions": {
"Select_objectIds_Only": {
"type": "Select",
"inputs": {
"from": "@variables('ObjectIds')",
"select": {
"DeviceId": "@item()?['DeviceId']"
}
}
},
"For_each_objectid": {
"type": "Foreach",
"foreach": "@variables('DeviceIds')",
"actions": {
"Append_to_Additions_Only": {
"type": "AppendToArrayVariable",
"inputs": {
"name": "addIds",
"value": "item()?['DeviceId']"
}
}
},
"runAfter": {
"Select_objectIds_Only": [
"Succeeded"
]
}
}
}
},
"runAfter": {
"Select_ObjectIds": [
"Succeeded"
]
}
},
"Initialize_removeIds_Array": {
"type": "InitializeVariable",
"inputs": {
"variables": [
{
"name": "removeIds",
"type": "array"
}
]
},
"runAfter": {
"Group_Exists": [
"Succeeded"
],
"For_each_DeviceId_get_ObjectId": [
"Succeeded"
]
}
},
"Initialize_addIds_Array": {
"type": "InitializeVariable",
"inputs": {
"variables": [
{
"name": "addIds",
"type": "array"
}
]
},
"runAfter": {
"Initialize_removeIds_Array": [
"Succeeded"
],
"For_each_DeviceId_get_ObjectId": [
"Succeeded"
]
}
},
"Select_allMembers": {
"type": "Select",
"inputs": {
"from": "@variables('allMembers')",
"select": {
"DeviceId": "@item()?['deviceId']"
}
},
"runAfter": {
"Initialize_addIds_Array": [
"Succeeded"
],
"For_each_DeviceId_get_ObjectId": [
"Succeeded"
]
}
},
"Select_ObjectIds": {
"type": "Select",
"inputs": {
"from": "@variables('ObjectIds')",
"select": {
"DeviceId": "@item()?['deviceId']"
}
},
"runAfter": {
"For_each_DeviceId_get_ObjectId": [
"Succeeded"
],
"Select_allMembers": [
"Succeeded"
]
}
},
"View_Removals": {
"type": "Compose",
"inputs": "@variables('removeIds')",
"runAfter": {
"Previous_Members": [
"Succeeded"
]
}
},
"View_Additions": {
"type": "Compose",
"inputs": "@variables('addIds')",
"runAfter": {
"Previous_Members": [
"Succeeded"
]
}
},
"HTTP_Get_Final_Group_Members": {
"type": "Http",
"inputs": {
"uri": "https://graph.microsoft.com/v1.0/groups/@{body('HTTP_Check_Group')?['value'][0]['id']}/members?$top=999&$select=deviceId,displayName",
"method": "GET",
"authentication": {
"type": "ManagedServiceIdentity",
"audience": "https://graph.microsoft.com"
}
},
"runAfter": {
"If_removals": [
"Succeeded"
],
"If_additions": [
"Succeeded"
]
},
"runtimeConfiguration": {
"contentTransfer": {
"transferMode": "Chunked"
},
"paginationPolicy": {
"minimumItemCount": 500
}
}
},
"For_each_DeviceId_get_ObjectId": {
"type": "Foreach",
"foreach": "@variables('DeviceIds')",
"actions": {
"Get_ObjectIds": {
"type": "Http",
"inputs": {
"uri": "https://graph.microsoft.com/v1.0/devices?$filter=deviceId eq '@{item()?['deviceid']}'&$select=id,displayName",
"method": "GET",
"authentication": {
"type": "ManagedServiceIdentity",
"audience": "https://graph.microsoft.com"
}
},
"operationOptions": "DisableAsyncPattern",
"runtimeConfiguration": {
"contentTransfer": {
"transferMode": "Chunked"
}
}
},
"If_ObjectId_Found": {
"type": "If",
"expression": {
"and": [
{
"equals": [
"@outputs('Get_ObjectIds')?['statusCode']",
200
]
},
{
"greater": [
"@outputs('Check_for_null_value')",
0
]
}
]
},
"actions": {
"Append_to_ObjectIds": {
"type": "AppendToArrayVariable",
"inputs": {
"name": "ObjectIds",
"value": {
"DeviceId": "@{body('Get_ObjectIds')?['value'][0]['id']}",
"DeviceName": "@{body('Get_ObjectIds')?['value'][0]['displayName']}"
}
}
}
},
"else": {
"actions": {}
},
"runAfter": {
"Check_for_null_value": [
"Succeeded"
]
}
},
"Check_for_null_value": {
"type": "Compose",
"inputs": "@length(body('Get_ObjectIds')?['value'])",
"runAfter": {
"Get_ObjectIds": [
"Succeeded",
"Failed"
]
}
}
},
"runAfter": {
"Initialize_DeviceIDs_Array": [
"Succeeded"
],
"Initialize_GroupID_variable": [
"Succeeded"
],
"Initialize_allMembers_variable": [
"Succeeded"
],
"Initialize_LoopDone_variable": [
"Succeeded"
],
"Initialize_nextLink_Variable": [
"Succeeded"
],
"Initialize_ObjectIds": [
"Succeeded"
]
}
},
"Initialize_ObjectIds": {
"type": "InitializeVariable",
"inputs": {
"variables": [
{
"name": "ObjectIds",
"type": "array"
}
]
},
"runAfter": {
"HTTP_Check_Group": [
"Succeeded"
]
}
},
"If_removals": {
"type": "If",
"expression": {
"and": [
{
"greater": [
"@length(variables('removeIds'))",
0
]
}
]
},
"actions": {
"For_each_Removal": {
"type": "Foreach",
"foreach": "@variables('removeIds')",
"actions": {
"Remove_group_members": {
"type": "Http",
"inputs": {
"uri": "https://graph.microsoft.com/v1.0/groups/@{body('HTTP_Check_Group')?['value'][0]['id']}/members/@{item()?['DeviceId']}/$ref",
"method": "DELETE",
"headers": {
"Content-type": "application/json"
},
"authentication": {
"type": "ManagedServiceIdentity",
"audience": "https://graph.microsoft.com"
}
},
"runtimeConfiguration": {
"contentTransfer": {
"transferMode": "Chunked"
}
}
}
}
}
},
"else": {
"actions": {}
},
"runAfter": {
"View_Removals": [
"Succeeded"
]
}
},
"If_additions": {
"type": "If",
"expression": {
"and": [
{
"greater": [
"@length(variables('addIds'))",
0
]
}
]
},
"actions": {
"For_each_Addition": {
"type": "Foreach",
"foreach": "@variables('addIds')",
"actions": {
"Add_group_members": {
"type": "Http",
"inputs": {
"uri": "https://graph.microsoft.com/v1.0/groups/@{body('HTTP_Check_Group')?['value'][0]['id']}/members/$ref",
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"body": {
"@@odata.id": "https://graph.microsoft.com/v1.0/devices/@{item()?['DeviceId']}"
},
"authentication": {
"type": "ManagedServiceIdentity",
"audience": "https://graph.microsoft.com"
}
},
"runtimeConfiguration": {
"contentTransfer": {
"transferMode": "Chunked"
}
}
}
}
}
},
"else": {
"actions": {}
},
"runAfter": {
"View_Additions": [
"Succeeded"
]
}
},
"Send_an_email_(V2)": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['office365']['connectionId']"
}
},
"method": "post",
"body": {
"To": "me@mydomain.com",
"Subject": "Group @{triggerBody()?['groupName']}has been updated",
"Body": "<p class=\"editor-paragraph\">The group @{triggerBody()?['groupName']}was updated. The group now includes these members:</p><br><p class=\"editor-paragraph\">@{body('Join_DeviceNames')}</p><br>",
"Importance": "Normal"
},
"path": "/v2/Mail"
},
"runAfter": {
"Join_DeviceNames": [
"Succeeded"
]
}
},
"Select_Final_members": {
"type": "Select",
"inputs": {
"from": "@body('HTTP_GET_Final_Group_Members')?['value']",
"select": {
"DeviceName:": "@item()?['displayName']"
}
},
"runAfter": {
"HTTP_Get_Final_Group_Members": [
"Succeeded"
]
}
},
"Join_DeviceNames": {
"type": "Join",
"inputs": {
"from": "@body('Select_Final_members')",
"joinWith": "@join(body('Select_Final_Members'), '\\n')\r\n"
},
"runAfter": {
"Select_Final_members": [
"Succeeded"
]
}
}
},
"outputs": {},
"parameters": {
"$connections": {
"type": "Object",
"defaultValue": {}
}
}
},
"parameters": {
"$connections": {
"value": {
"office365": {
"id": "/subscriptions/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/providers/Microsoft.Web/locations/westus2/managedApis/office365",
"connectionId": "/subscriptions/XXXXXXXXXXXXXXXX/resourceGroups/Intune/providers/Microsoft.Web/connections/office365",
"connectionName": "office365"
}
}
}
}
This approach is a workaround, but it allows us to mimic ConfigMgr’s dynamic collections in Intune. For those who desire a more ConfigMgr like method of grouping assets, this method can significantly improve targeting and flexibility when managing devices with Intune.
PowerStacks customers can reach out for help implementing this solution. For others, I hope this guide sparks ideas for customizing Intune to better meet your organization’s needs.
Cookie | Duration | Description |
---|---|---|
cookielawinfo-checkbox-analytics | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics". |
cookielawinfo-checkbox-functional | 11 months | The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional". |
cookielawinfo-checkbox-necessary | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary". |
cookielawinfo-checkbox-others | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other. |
cookielawinfo-checkbox-performance | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance". |
viewed_cookie_policy | 11 months | The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data. |