Yes, again a blog post related to a Logic Apps flow!
That’s because I needed to have an Azure AD group filled with Windows devices, based on if a certain application is installed on the device. I have the information on which device, which applications are installed available in an Azure Log Analytics workspace, as I have the Enhance Intune Inventory data with Proactive Remediations and Log Analytics solution running from the MsEndpointMgr team. Although that script runs in my environment once a day, group membership isn’t changed instantly, but that isn’t an issue for me. It is fine to change group membership just once a day.
With this information available in Log Analytics (LA), it should be possible to query this information with a Log Analytics flow and via Microsoft Graph to add or remove devices to an Azure AD group.
So, maybe for somebody else, this is helpful. So let’s start again with an automation-related blog post.
The solution
The flow consists of two parallel branches. In the first one, we query the LA workspace via the API with an HTTP action. When a device is retrieved on which the application is installed, we query Azure AD to get the Object ID of that device.
With the Object ID, we are able to determine if the device is already a member of the Azure AD group, or not. When the device is not a member of the group, we add the device to the group and this part of the flow is finished.
In the second parallel, we query the Azure AD group first via Microsoft Graph. For every group member, we query the LA workspace, to determine if the application is still installed on the device. If the application wasn’t determined anymore on the device, based on the latest inventory, the device is removed from the Azure AD group.
This will be our complete flow, with the two parallel branches.
The flow will be added later to my GitHub repository.
Requirements
The flow mostly runs HTTP actions, so besides an Azure AD group, we only need to have a Managed Identity in place with the needed permissions.
To query the Log Analytics workspace, we assign an Azure role to the Managed Identity. It is enough to assign the Log Analytics Reader role to the identity.
And these permissions are needed to query Azure AD for the device details and add/ remove devices from/ to the AAD group:
Device.Read.All
GroupMember.ReadWrite.All
Setup the Logic Apps flow – part 1
Let’s configure the Logic Apps flow. In the first part we set up the flow itself and configure the part of the flow, which queries Log analytics if the application is installed or not. When the application is detected on the device, the device is added to an AAD group.
Sign in to the Azure portal and open the Logic App service. I created a blank Logic App of type Consumption.
When the flow is created, click on the name of the flow at the top of the screen, open the Identity section, and on the tab User assigned add your Managed Identity.
Open the Overview tab, which shows a few templates, choose Recurrence.
The Logic App designer is opened and the recurrence trigger is added. Choose the recurrence settings of your choice; once a day, once an hour.
I first initialize two variables, which is optional. As we need the Log Analytics Workspace ID and AAD Group ID in multiple upcoming actions, I find it handy to initialize these at the beginning of the flow. When I need one of them, I now simply pick the one I need from the dynamic content list, instead of entering the ID every time I need it.
Add an Initialize variable action, twice.
In the first action, enter WorkspaceID as Name, select String as type, and enter the Log Analytics Workspace ID as value.
In the second action, enter GroupObjectID as Name, select String as type, and add the Object ID of the Azure AD group (to which the devices are added).
next, add an HTTP action to the flow to query Log Analytics.
Choose GET as Method.
As URI enter:
https://api.loganalytics.io/v1/workspaces/[WorkspaceID]/query
Replace [WorkspaceID] with the WorkspaceID from the Dynamic content list.
Choose Add new parameter and check Authentication. Select Managed Identity as authentication type. Choose the previously added Managed Identity and enter https://api.loganalytics.io as Audience.
At the Queries row, enter this KQL query in the left textbox and enter your query in the right text box.
This is my example query for Chrome:
AppInventory_CL
| where AppName_s contains "Chrome"
| where TimeGenerated > ago(1d)
| project TimeGenerated,ComputerName_s,AppName_s,ManagedDeviceID_g
| summarize arg_max(TimeGenerated, *) by ComputerName_s
This query is the same one, which you could use directly in the Log Analytics workspace.
This is what the HTTP action looks like.
Usually, we can simply use a Parse JSON action, to process the output of the HTTP action, so we can use the variables from the output later in the flow. But the output of this HTTP action is in rows. That is where I got stuck, as I wasn’t able to grab the device name from the output. I was able to grab the array with the information, but I only needed the device name. But luckily Luise is awesome with everything Power platform related and solved my issue (thanks again!).
We again add two Initialize variable actions to the flow. One to grab the Object (Table) from the output of the HTTP action, and the second one to grab the Array (Row) from that Object.
This is the raw output from an HTTP action, showing the table and a row.
Add an Initialize variable action and add AppResults as Name. Select Object as Type and select Body from the Dynamic content list as Value.
Add another Initialize variable action and add AppresultsHelper as Name. Select Array as Type and as value we add an Expression:
variables('AppResults')?['tables'][0]?['rows']
This is the Initialize variable action.
Now that we have the arrays, we add a For each action, to loop through each array (Row). We use the output from the latest Initialize variables action.
Next, add a Compose action, which is a Data operations action. With this action we loop through the computers and next from the output of this action, we grab the device name (in another compose action).
As Input add Current item from the Dynamic content list.
When we run this part of the flow, it provides us with all the rows separate as you can see in this example.
Add a second Compose action. Enter below Expression as Input, which returns the first object from the Array, which is the device name.
outputs('Compose_loop_over_all_computers')[0]
We are now going to use Microsoft Graph to query Azure AD for some device details by using the device name, as we need the Object ID later in the flow. The Object ID is returned by this query as ID.
Add an HTTP Action.
Choose GET as Method.
As URI enter:
https://graph.microsoft.com/v1.0/devices?$filter=(displayName eq '[OUTPUTS]')&$select=id,deviceId,displayName
Replace [OUTPUTS] with the Outputs from the last compose action, which can be selected from the Dynamic contents list.
Choose Add new parameter and check Authentication. Select Managed Identity as authentication type. Choose the previously added Managed Identity and enter https://graph.microsoft.com as Audience.
Add a Parse JSON action.
As content select Body from the latest HTTP action.
As Schema you can enter:
{
"properties": {
"@@odata.context": {
"type": "string"
},
"value": {
"items": {
"properties": {
"deviceId": {
"type": "string"
},
"displayName": {
"type": "string"
},
"id": {
"type": "string"
}
},
"required": [
"id",
"deviceId",
"displayName"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
Or run the flow, copy the body from the HTTP action and enter that as example payload.
With the next HTTP action, we check if the device is already a member of the AAD group. If we would skip this step, just try to add the device to the group, but if the device is already member of the group, this would end in an error and a flow that fails.
Choose GET as Method.
As URI enter:
https://graph.microsoft.com/v1.0/devices/[ID]/memberOf/[GroupObjectID]
Replace [ID] with the ID from the HTTP output and replace [GroupObjectID] with the Group Object ID variable, both from the Dynamic content list.
The HTTP action will be placed in a For each.
Don’t forget to enter the Authentication.
Add a Condition action, which is a Control action.
As value in the left box, we add StatusCode from the last HTTP action.
Choose is equal to and enter 404 in the right box.
As the HTTP action results in an error when the device is not a member of the group, we need to change the Run after setting of the Condition otherwise the flow will fail.
For this, click on the three dots to open the settings of the condition, choose Configure run after. Select has failed and click Done.
Under True we add the HTTP action to add the device as member of the AAD group.
Choose POST as Method.
As URI enter:
https://graph.microsoft.com/v1.0/groups/[GroupObjectID]/members/$ref
Replace [GroupObjectID] with the Group Object ID variable.
As Body enter:
{
"@@odata.id": "https://graph.microsoft.com/v1.0/devices/[ID]"
}
Replace [ID] with the ID from the Parse JSON action.
Finish the HTTP action by entering the authentication settings.
This part of the flow is ready!
Setup the Logic Apps flow – part 2
In this part of the flow, we query Azure AD for the AAD group members. And next, we check if the group members still have the application installed, by querying Log analytics.
Click the Plus sign between the second Initialize variable and first HTTP action and select Add a parallel branch.
Add these two Initialize variable actions, like in the other parallel branch, but this time leave the values empty.
First, we query Azure AD for the group members of the AAD group with an HTTP Action.
Choose GET as Method.
As URI enter:
https://graph.microsoft.com/v1.0/groups/[GroupObjectID]/members?&$select=displayName,id
Replace [GroupObjectID] with the Group Object ID variable.
Add a Parse JSON action with this schema:
{
"properties": {
"@@odata.context": {
"type": "string"
},
"value": {
"items": {
"properties": {
"@@odata.type": {
"type": "string"
},
"displayName": {
"type": "string"
},
"id": {
"type": "string"
}
},
"required": [
"@@odata.type",
"displayName",
"id"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
With the next HTTP Action, we query Log analytics with the device name of all group members we just retrieved. The HTTP action will be (automatically) placed in a For each action.
Choose GET as Method.
As URI enter:
https://api.loganalytics.io/v1/workspaces/[WorkspaceID]/query
Replace [WorkspaceID] with your Workspace ID.
At the Queries row, enter query in the left textbox and enter your query in the right text box.
This is my example query for Chrome:
AppInventory_CL
| where AppName_s contains "Chrome" and ComputerName_s contains "[displayName]"
| project TimeGenerated,ComputerName_s,AppName_s,ManagedDeviceID_g
| summarize arg_max(TimeGenerated, *) by ComputerName_s
Replace [displayName] with the displayName found in the Dynamic content list.
We use the variables again to grab the Object and Array.
Add an Initialize variable action and select DeviceAppResults. As value enter the Body from the last HTTP action.
Add a second Initialize variable action and select DeviceAppResultsHelper.
Add this expression as value:
variables('DeviceAppResults')?['tables'][0]?['rows']
We don’t use the Compose actions here. As the application isn’t detected anymore, the array will be empty for that particular device. We use a Condition to filter on the empty array.
Add this expression to the left text box of the Condition action:
length(body('Set_variable_DeviceAppResultsHelper')?['value'])
Select is equal to and add 0 to the right box.
Under True, we add the HTTP action to remove the device from the AAD group.
As URI enter:
https://graph.microsoft.com/v1.0/groups/[GroupObjectID]/members/[ID]/$ref
Replace [GroupObjectID] with the Group Object ID variable and replace [ID] with the ID found in the Dynamic content list.
And this is the last action of our Logic Apps flow.
Thanks for reading this post. I hope it is helpful for somebody 🙂
4 Comments
Im struggling to find where to obtain the Log Analytics Workspace ID from ? Are we supposed to create a Log analytics workspace?
Yes, you should.
This flow pulls data from the workspace. That data is stored in the workspace because we implemented this solution first https://msendpointmgr.com/2021/04/12/enhance-intune-inventory-data-with-proactive-remediations-and-log-analytics/
I’m trying to build this but am getting invalid JSON on the initial query. Trying to get results for Zoom but not being overly familiar with JSON it’s just not working for me. Any pointers would be massively appreciated
Hi Andy,
Do you mean on the first HTTP action? If that’s the case, do you have the Log Analytics Workspace created and filled with some data by setting up the solution of msendpointmgr?
Then you could first try to execute the (KQL) query directly in the Logs section of the Log Analytics Workspace. That should provide you some info if it fails.