Automatically inform your users on outdated Windows devices

In this blog post, I’ll describe an example of how we can inform our end users when their device runs an outdated Windows operating system version.

Every month Microsoft releases the monthly security update for Windows to keep our devices secure. Most of the devices install this update without issues, but unfortunately on some devices, this isn’t the case, for whatever reason.

I recently got the question to automatically create a monthly export of all Windows devices, so local IT could filter out all devices not running the latest OS build. With this information local IT could inform the user of these devices (and request to trigger the Windows update manually or contact the service desk).

But why not automatically inform the users of these devices, so the whole process is automated?
That’s what we are going to do with this solution.

The solution in short

We first need to have information about the latest monthly security update, so we can match the Windows build of our devices with the latest build. We could check all Windows devices in our tenant and use the highest found build as standard and mark every device running a lower build as not up to date. But we only automatically deploy monthly security updates and if somebody installs the latest non-security update, this might result in a lot of outdated devices.

Fortunately, we can query the Windows update catalog in our Windows Update for Business environment via Microsoft Graph for the most recent security and non-security updates. With that information we have the correct Windows builds available per Windows version as long as we make sure we filter on security updates.

To automatically query this information, we use an Azure Logic Apps flow. We can schedule to run this flow based on an occurrence, for example, every fourth Tuesday of the month.

When we have the correct Windows build retrieved, we need to run another Graph query to retrieve all Windows devices.

Next, we filter out devices on OS Version. In my case, this means the flow splits up into Windows 10, 22H2, Windows 11 22H2, and Windows 11 23H2.

After this, we filter out all devices running an outdated Windows version. And the users of these outdated devices receive an email that is sent from a shared mailbox.

Requirements

To create this solution, we only have two requirements. The first one, to query Microsoft Graph and retrieve the latest Windows build and all Windows devices, we need to have permission to perform this job. There are different options to authenticate to MS Graph and assign permissions, but I prefer an Azure Managed Identity.
The required Graph (Application) permission we need is:
WindowsUpdates.ReadWrite.All
DeviceManagementManagedDevices.Read.All

As I use a shared mailbox to send out emails, I need that shared mailbox and a (service) account with the right permissions on that mailbox.

Setup the first part of the Logic App flow

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, and choose Recurrence.

Change the interval settings to your needs.

In my example, I’ll send out the notification on the fourth Tuesday of the week.

This can be done by using a Trigger condition to the recurrence trigger. Click on the three dots in the right corner and click Settings. Enter below trigger condition:
@and(less(int(utcNow(‘dd’)),21),greater(int(utcNow(‘dd’)),21))

I found the logic for this condition in this blog post, so I’m not explaining it further in this post.

Next, we add an Initialize variable action. This is later used to store the latest Windows builds supported in our environment.

Thus, we need to add this action for every Windows OS Version we support in our environment (in my case three times).

The first thing we need to do next is run a Graph query to retrieve the latest Windows update information. Let’s first look at the information we have available via Microsoft Graph and the information we need.

We can query Graph for the Windows quality updates to retrieve the released product revisions (1). Product revisions for quality updates are published twice a month and I’m only interested in the latest product revisions. Therefore I only query for the top 2 (2) so I always retrieve a nonsecurity (3) and a security Quality update.

This is the security update retrieved in the same query (1). As you can see it contains information for several Windows versions. For example information about Windows 10, version 22H2 (2), and Windows 11, version 22H2 (3). The ID shows us the latest build number, that we can use in our flow.

To query Graph for this information in our Logic Apps flow, we use an HTTP action.

Select GET as Method.
Enter below URI:

https://graph.microsoft.com/beta/admin/windows/updates/catalog/entries?$expand=microsoft.graph.windowsUpdates.qualityUpdateCatalogEntry/productRevisions&$orderby=releaseDateTime%20desc&$top=2

Next, choose Add Parameter and select Authentication.
As Authentication type select Managed identity.
Select your Managed identity from the list.
And add https://graph.microsoft.com as Audience.

This is our HTTP action.

Next, we need to add a Parse JSON action, which is a Data operations action. We parse the output of the HTTP action, to be able to use the values later on in the flow.
As Content, we select Body from the Dynamic content list that is from our HTTP action.

We don’t have to write the schema ourselves, we can generate it by adding a sample payload. We can get this example payload, by running the current flow and grabbing the output from the HTTP action. We can also grab the body when we run the same query via Graph Explorer.

We can paste that response information from the HTTP action or Graph Explorer into the example payload box.

Our schema is created and Parse JSON action is ready.

We now have retrieved information about two product revisions and only need the security update information. Therefore our next step is to filter out the nonSecurity revision.

We use a Filter array action for this, which is a Data operations action.

in the From field, we add Value, because the information on which we filter is in the value array. Value is available via Dynamic content (of the Parse JSON action).

In the left field, we add qualityUpdateClassification of the Parse JSON action. Choose is equal to from the drop-down list and add the text security in the right field.

As the information we need about the latest updates is stored in the productRevisions Array, which is not available as dynamic content yet, we first need to use a Compose action (also a Data operations action). With the Compose action, we take out that part of the information (that array) from the whole query output to later Parse it again. After these two actions, we have information available as a variable for further usage, like the display name and OS version of the Windows updates.

In the Inputs field add productRevisions (of the Filter array action, not the Parse JSON action).
This will automatically add the Compose action in a For each action.

As described, we need to parse this retrieved information. Save the flow and run the flow once. Open the runs history and open the latest run. Open the Compose action and copy the Outputs from this action. We can use this to create the schema for the Parse JSON action.

Add Outputs of the Compose action in the Content field. Create the schema via use sample payload (add the previously copied Compose output), to generate the schema.

As written before every product revision contains information on multiple Windows OS Versions. We can easily identify the different OS versions using the displayname values.

I want to store the OS build for every supported OS in one of the previously created Variables (for which we initialized three variables at the beginning of the flow). Therefore, I use Filter array actions again to only retrieve information about one particular OS version. And save that in the three variables.

Add a Filter array action and add Body (of the latest Parse JSON) in the From field.

Add displayName in the left field. Choose starts with from the drop-down list. And add Windows 10, version 22H2 in the right field.

We are going to add two more Filter array actions to our flow, but we use parallel branches for that. With parallel branches, we can run actions simultaneously.

Click on the + sign above the Filter action and click on Add a parallel branch.

Add the same dynamic content in the From and left fields. But make sure to add the correct information of the other OS versions in the right fields.

In my case, these are my three Filter array actions.

Under each of these Filter actions, we are storing the latest OS build in a variable action. Therefore, add a Set variable action under each of the Filter array actions.

In the Name field select the correct matching variable and in the Value field add id (from the dynamic content list).

This is what it looks like.

So far, we have created our Logic Apps flow and stored the latest OS build per OS Version in a variable.

Setup the second part of the Logic App flow

In the second part, we query Microsoft Graph to get all our Windows devices. We split the flow into three parts, as I have three different Windows OS versions in my environment. We then match the OS build for every device with the previously retrieved latest version. As last, we send an email to every primary user of a device that runs an outdated version.

To retrieve all Windows devices, we use an HTTP action again.

Select GET as Method.
Enter below URI:

https://graph.microsoft.com/beta/deviceManagement/managedDevices?$filter=(deviceType%20eq%20%27windowsRT%27)&$select=deviceName,osVersion,userDisplayName,emailAddress

Next, choose Add Parameter and select Authentication.
As Authentication type select Managed identity.
Select your Managed identity from the list.
And add https://graph.microsoft.com as Audience.

The HTTP action is followed by a Parse JSON action. I assume you now know what to fill in, in this action.

We need to create three parallel branches, for every Windows OS Version one.
In every branch, we add a Filter array action.
In the From field, we add Value (from the last Parse JSON action).
Add osVersion in the left field. Choose starts with from the drop-down list. And add 10.0.19045 in the right field as that is first part of the version of Windows 10 22H2.

Repeat this for the other two OS versions.

Under every branch, we add a Condition action (which is a Control action). With a condition action, we can filter on a particular value. In this case on osVersion. When the osVersion of the device is less than the osVersion of the variable we previously stored, the outcome is True. Under True we will add the action to send an email. When the outcome is False, the device runs the latest version and we do nothing.

Add the Condition action and add osVersion (of the latest Filter array action) in the left field. This adds the Condition action in a For each.
Select is less than from the drop-down list. And add the corresponding variable in the right field.

We now should have three branches, like below.

We can now add the action to send an email from a shared mailbox, thus the next step is optional.
I like to add a picture to the email I send, as that makes the email a bit more recognizable for the end user. Therefore, I want to send an HTML format email, but unfortunately, I can’t add HTML directly to the send email action. We can work around that by first adding the HTML to a Compose action and adding the output of this action to the email action.

We can add values from the previous action to the Compose action (or email action), to add for example the display name of the primary user, or the device name.

To send out the email, we add a Send an email from a shared mailbox action. We first need to authenticate with our (service) account.

Enter the email address of the original address. To add the primary user email, we select the variable from the dynamic content (from the filter array action).
We can add our text to the Body of the action. Or add the Outputs of the compose action to the body if you also use the Compose action.

And by adding the send email action, we have finished our Logic Apps flow!

The end result

The end result is that every month our end users that have an outdated Windows device, receive an email to inform them.

That’s it for this blog post.
Thank you for reading and leave a comment when you have any questions.

Be the first to comment

Leave a Reply

Your email address will not be published.


*