Update 2021-08-06: To filter out events with an inProgress status I changed the first HTTP URI and added a Condition to the flow.
At my current employee, we are still imaging most of our devices with Configuration Manager. When such a task sequence to apply an image is finished (successfully or failed), a message is placed in a Teams channel with some data about the device and deployment. This triggered me to create such a solution, but for Windows Autopilot deployments.
For a couple of months, we have Windows Deployments available in the Microsoft Endpoint Management admin center. I thought these deployment reports would be a good start to get this solution in place as these events hold the information about our Windows Autopilot deployments we need for this solution. As most data for Intune is available via Microsoft Graph, I thought about creating a flow in Logic Apps, to pull this data out of the cloud. If we can pull this data via Graph into our Flow, we should be able to process this data and create a Teams message in a channel for every Autopilot enrollment job.
This blog post is part of the MEM (Intune) Monitoring series. An article with a short explanation of every MEM Monitoring flow I shared and links to the related articles can be found here.
The solution in short
Compared to the previous Logic App flows, this one is pretty simple. At least, the basics which I describe here.
I expanded the flow a little with extra steps, which can be found in part 2 of this post.
The flow starts with a Recurrence trigger. For now, it runs every hour.
Every hour Microsoft Graph is queried for the Autopilot Events, by using an HTTP action. At that moment it retrieves all Autopilot events from the previous hour. When this data is received, it’s parsed so we can further process the data and its variables.
After this step, for every retrieved Autopilot event, a message is posted in a Teams channel by using an Incoming Webhook.
As an optional action, I used a Get User action. From the Graph, we only receive the user ID. If you want to add the User (by Name or UPN), we can use that ID to retrieve some information about the user.
And to secure the HTTP action I use Azure Key Vault.
The complete flow can also be downloaded as an ARM Template from my GitHub repository.
Requirements
We have some requirements for this flow.
First of all, we need to have the appropriate permissions to run the Graph query. The HTTP action we use in the flow uses an Azure App Registration to authenticate to MS Graph. On this App Registration, we set the required permission we need for our Graph queries.
The minimum permissions are DeviceManagementManagedDevices.Read.All.
In this previous blog post, I described in detail how to create an App Registration.
To secure the HTTP actions I use an Azure Key Vault, which is described in this previous post.
To post a message to a Teams channel, I first used a Post Teams message action, but that requires a Teams license and you need to add the member to the Teams channel. We can also use an Incoming Webhook to get the job done. So make sure to add a webhook to your Teams channel and save the webhook URL, to use it in one of the actions in our flow.
And optional, when you want to use the Azure AD Get User action, we need rights to read the user properties. For this, an Enterprise Application is created when the first user in the tenant signs in to an Azure AD action in Logic Apps.
Setup the Logic Apps
Sign in to the Azure portal and open the Logic Apps service. Here create a new, blank Logic App.
In the search box for connections and triggers search for Schedule. Select Schedule and select Recurrence which functions as our flow trigger.
I choose to run this Logic Apps flow every hour.
The first action we add is an Azure Key Vault action to retrieve the secret for authentication of the upcoming HTTP action. Search for Azure Key Vault and select Get secret.
Enter at least the Vault name and click sign-in.
Enter the name of the secret.
Add an HTTP action.
In the URI we already use a filter with a date and time to get the events from the last hour, filtered on the deploymentEndDateTime.
As Method select GET.
As URI enter:
https://graph.microsoft.com/beta/deviceManagement/autopilotEvents?$filter=microsoft.graph.DeviceManagementAutopilotEvent/deploymentEndDateTime%20ge%20
Behind this URI we enter an expression. This expression enters the current time (in UTC), -1 hour, to the URI and sets the date and time in the right context.
Make sure the cursor is located at the end of the URI, on the right click expression.
Enter this expression and click OK.
formatDateTime(addHours(utcNow(),-1),'yyyy-MM-ddTHH:mm:ssZ')
If you’d like to run the flow once a day and want to retrieve all the Autopilot events from the previous day, use this expression.
formatDateTime(addDays(utcNow(),-1),'yyyy-MM-ddTHH:mm:ssZ')
Check Authentication and choose Active Directory OAuth as Authentication type. Enter the Tenant, Client ID (from the app registration) and Audience.
And make sure to select the Key Vault secret value from the Key Vault action.
This is how our HTTP action looks like.
Next, add a Parse JSON action.
As input, we use the Body (selected as Dynamic content) from the previous HTTP action.
We can fill the Schema, by using an example payload. This example payload is generated by running this flow or by running the same Graph query as from our HTTP action using Graph Explorer.
Copy the response in Graph Explorer, click Use sample payload to generate the schema, and paste the response in the text box.
Next, we’re adding a Condition to the flow, which is a Control action.
As even events which have a status of inProgress already get a deploymentEndDateTime (which is later changed to the real end date/ time), we need to filter these out so we only get notified of events with a real end state.
To do this we use the value deploymentState (on the left), choose is not equal to, and enter inProgress as the value on the right.
When an event deploymentState is not equal to inProgress, the result is true. Under true we place the next action. When the result is false, nothing is done and the flow stops running.
To post a message in a Teams channel, I use a webhook. We can post messages via the webhook by using an HTTP action.
Add an HTTP action under True.
As Method select POST.
As URI enter the webhook URL.
As Headers enter Content-Type – application/json
In the body, we add the text and title in JSON format. Below is the JSON format we need to use for this, without our variables:
{
"text": "WRITE YOUR MESSAGE TEXT HERE",
"title": "WRITE YOUR TITLE HERE"
}
We can use dynamic content (variables) from the previous PARSE JSON action and enter our own text.
Use \n\n in the text to create new lines in the text message, otherwise, the message consists of one long line of text.
And I used ** to get some text in bold.
This is the JSON from my above example:
{
"text": "**Device name:** @{items('For_each')?['managedDeviceName']}\n\n **Device serial nr:** @{items('For_each')?['deviceSerialNumber']}\n\n **OS Version:** @{items('For_each')?['osVersion']}\n\n **Autopilot Deployment profile:** @{items('For_each')?['windowsAutopilotDeploymentProfileDisplayName']}\n\n **Deployment Status:** @{items('For_each')?['deploymentState']}\n\n **Deployment start time:** @{items('For_each')?['deploymentStartDateTime']}\n\n **Deployment end time:** @{items('For_each')?['deploymentEndDateTime']}\n\n **Deployment duration:** @{items('For_each')?['deploymentDuration']}",
"title": "Windows Autopilot deployment finished for @{items('For_each')?['managedDeviceName']} with status @{items('For_each')?['deploymentState']}"
}
Optional, we can add a Get user action to our flow before the HTTP Post action. The Graph GET query only returns the user ID, but with the user ID we can retrieve for example the User Principal Name of the user who enrolled the device. Use the userPrincipalName from the Parse JSON action as input for the Get user action.
If you are the first one who uses an Azure AD action in Logic Apps, an Enterprise Application is created in Azure to grant permissions to Logic apps to run the actions. It depends on your tenant design how this permission flow runs, but the end result is a new Enterprise Application with these permissions.
If you use the Get user action, you can use the output from that action, in your HTTP Post action.
And this is our flow.
The end result
For the end result, I can be short. All the Windows Autopilot deployment events are automatically added to the Teams channel.
Thanks for reading! And if you have questions or suggestions, leave a comment!
14 Comments
This is really great, unfortunately i just cannot get any data back when running this. Would be really handy as rolling out Autopilot to a 1k+ devices
Body in the HTTP output just has no values even using the one looking over the previous day but i know for a fact there are have been quite a few autopilot deployments. If you have an idea or can assist me in the right direction that would be great!
What do you get when you use https://graph.microsoft.com/beta/deviceManagement/autopilotEvents?$top=100
This should return the last 100 AutopilotEvents
And what if you use this expression instead the one from the article (both expressions work fine for me):
addhours(utcNow(‘yyyy-MM-ddTHH:mm:ssZ’),-1)
For some reason this gives me an invalid filter clause error.
Whenever I try to run the query in graph explorer graph explorer crashes..
Bit stuck here aLSO the top=00 doesn’t give me any results.
To add to this: just querying https://graph.microsoft.com/beta/deviceManagement/autopilotEvents
Gives me an empty array. Is that expected behaviour?
Bedankt!
https://graph.microsoft.com/beta/deviceManagement/autopilotEvents should give the events which you also see in the MEM admin center via https://endpoint.microsoft.com/#blade/Microsoft_Intune_DeviceSettings/DevicesMonitorMenu/autopilotDeployments
Do you see Autopilot events in de MEM Admin center?
No permission errors when using Graph Explorer?
Okay, I was finally able to make it work.
Bit of rookie mistake I suppose but I tried to query in graph explorer using the formatedatetime expression. That always gave me an “wrong filter clause” error. It did work in the HTTP request though.
I came to this solution by simplifying my query and just querying for https://graph.microsoft.com/beta/deviceManagement/autopilotEvents?$filter=enrollmentStartDateTime ge 2021-06-01 in graph explorer.
Hope this helps anyone.
The following query worked for mw with the ‘formatDateTime’ expression. I think this is perfect for our needs. Love having the Team notifications
https://graph.microsoft.com/beta/deviceManagement/AutoPilotEvents?$filter=enrollmentStartDateTime%20ge%20
Hello,
here you can find the proper query (put filter arguments under the brackets):
https://graph.microsoft.com/beta/deviceManagement/autopilotEvents?$filter=(microsoft.graph.DeviceManagementAutopilotEvent/deploymentEndDateTime%20ge%20@{formatDateTime(addHours(utcNow(),-1),’yyyy-MM-ddTHH:mm:ssZ’)
})
That doesn’t seem to work either. With the first one from the guide only get Unknown Error. Your query cant even be added.
I implemented the version 2 using managed identity, everything appears to be working but I am not getting user information back – anyone else having this problem? If I query the graph for autopilotEvents or windowsAutopilotDeviceIdentities, userPrincipalName is blank there, however the MEM AutoPilot report under monitoring shows user information properly.
My results on the posted webhook always show the device name as the azure ad device ID. Any idea why that is?
Same for me, most of the time. This has been fine for a while, but unfortunately the last few months more often the device name isn’t reported back via Graph.
The following URI seem to work for me
https://graph.microsoft.com/beta/deviceManagement/autopilotEvents?’$filter=microsoft.graph.DeviceManagementAutopilotEvent/deploymentEndDateTime%20ge%20′