Use Power Automate or Logic Apps to keep an eye on your licenses

I guess we’ve all been there; you ran out of licenses in your Azure AD or Office 365 tenant. Despite you hang out in your admin portal every day, you were still taken by surprise when you discover an issue, caused by a license shortage. More often this is caused by the fact that the people who are responsible to buy these licenses, are not always IT admins. So it’s easy to run out of licenses. Time to get this fixed.

In this blog post, I am going to show you how you can keep an eye on your licenses, by alerting when a specific threshold is reached. In this example, I use a threshold of 95%, but depending on your use case, you can change your threshold.

What do we need for this?

To create these alerts, we use a flow in Power Automate, but you could also use Logic Apps for this. It’s up to you. Next, we’ll need an app registration in Azure AD with the right permissions to get a list of all the licenses, using Graph API. The HTTP function we use in Power Automate is a premium feature.

I am not going into much detail on how to create an app registration, or how the flow is built op. If you’re lost further on in this post, just take a look at my other post about Power Automate and Graph API.

App registration

Let’s start with creating an app registration in Azure AD. You can find your app registrations under Active Directory -> App registrations in the Azure portal. Give it a proper name, and leave the rest as is.

Next, make sure the app registration has the right permissions. Since we are using the List subscribedSkus API, you’ll need at least Organization.Read.All application permissions for Graph API. Don’t forget to configure the admin consent afterward.

Next, we’ll need a client secret for authentication. Create one and copy the secret to notepad. We need this in the next steps. Also, take note of the Application (client) ID en Directory (tenant) ID. So if you followed all the steps, you end up with:

  • An app registration with the right Graph API permissions
  • A client secret
  • The Application (client) ID
  • The Directory (tenant) ID

Create the flow

With all the previous steps in place, we can now go on and create a flow. Head over to you Power Automate portal, and start with an empty flow. Pick the scheduled flow to run this daily or weekly, depending on your needs.

Our first step is an HTTP request to pull out the license information from our tenant.

  1. Use the GET method
  2. Use the https://graph.microsoft.com/v1.0/subscribedSkus API
  3. Select Active Directory OAth as your authentication method
  4. Enter your tenant ID that you’ve captured in the previous chapter
  5. Use https://graph.microsoft.com as your audience url
  6. Enter your Client ID that you’ve captured in the previous chapter
  7. Enter your secret

This is a good moment to test your flow, to see if you have configured everything correctly. You should end up with the raw JSON data from the Graph API.

If everything works as expected, we can now go on and parse our data. Add a new step and select Parse JSON. Use the body of the HTTP request and use the output of your HTTP request as sample data to create the schema. Or you can use the sample data from the Microsoft documentation:

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#subscribedSkus",
    "value": [
        {
            "capabilityStatus": "Enabled",
            "consumedUnits": 14,
            "id": "48a80680-7326-48cd-9935-b556b81d3a4e_c7df2760-2c81-4ef7-b578-5b5392b571df",
            "prepaidUnits": {
                "enabled": 25,
                "suspended": 0,
                "warning": 0
            },
            "servicePlans": [
                {
                    "servicePlanId": "8c098270-9dd4-4350-9b30-ba4703f3b36b",
                    "servicePlanName": "ADALLOM_S_O365",
                    "provisioningStatus": "Success",
                    "appliesTo": "User"
                }
            ],
            "skuId": "c7df2760-2c81-4ef7-b578-5b5392b571df",
            "skuPartNumber": "ENTERPRISEPREMIUM",
            "appliesTo": "User"
        },
        {
            "capabilityStatus": "Suspended",
            "consumedUnits": 14,
            "id": "48a80680-7326-48cd-9935-b556b81d3a4e_d17b27af-3f49-4822-99f9-56a661538792",
            "prepaidUnits": {
                "enabled": 0,
                "suspended": 25,
                "warning": 0
            },
            "servicePlans": [
                {
                    "servicePlanId": "f9646fb2-e3b2-4309-95de-dc4833737456",
                    "servicePlanName": "CRMSTANDARD",
                    "provisioningStatus": "Disabled",
                    "appliesTo": "User"
                }
            ],
            "skuId": "d17b27af-3f49-4822-99f9-56a661538792",
            "skuPartNumber": "CRMSTANDARD",
            "appliesTo": "User"
        }
    ]
}

Your flow should now look like this. Run a test, to see if your data is parsed correctly.

There a couple of values that we need for our alerts:

skuPartNumber: the name of the SKU
enabled: this represents the total amount of installed licenses per SKU
consumedUnits: this is the number of licenses in use per SKU

In our next step, we are going to filter the array. I use this step to filter out licenses that have 0 active licenses. This is important because we are going to calculate the percentage later. If you leave them in, the math function, later on, will fail, as it cannot divide by zero. (neither can I 😉 )

You could also use this step, to filter for specific licenses. In some tenants, there are many different licenses, so you might want to monitor a specific license only.  To to that, just click " Edit in advanced mode" and add the following value: @and(greater(item()?['prepaidUnits']?['enabled'], 1),contains(item()?['skuPartNumber'], 'EMSPREMIUM'))
Replace 'EMSPREMIUM' with the SKU you're interested in.

Next, we are going to initialize four variables. First, we create the threshold variable to set the alert trigger. Next, we want to work with the total number of licenses and the active number of licenses, to calculate the percentage in use. Create the variables and select Float as the data type. In this example, I use 95 (%) as the threshold. You can adjust this to your own needs.

In the next step, create an Apply to each action and set the variables according to the figure below.

Make sure you select the body from the filtered array action in the output field, and not the unfiltered parsed data. 

In the calculate percentage variable we use an expression in combination with dynamic content. In this expression, I work with the math functions div and mul to calculate the percentage of the licenses in use. In my case, the expression is:

mul(div(variables('ActiveLicense'),variables('TotalLicense')),100)

This can be a little tricky to set up. Be aware that you use the correct name of the variables. Make sure that you understand what we are doing here: we divide the active licenses by the total licenses and multiply the outcome with 100 to get the percentage. So we are using a ‘div’ function within a ‘mul’ function.

You might see this, and think: this can be done in a much easier way! Please, let me know in the comments. I just figured out how to use math functions and dynamic content in a single expression, but I am aware that this might not be the most convenient method 😉

The last step is to create a condition with an if-statement. If the threshold is reached, take action. In this case, we send an email to Adele, the license admin of Contoso. If the outcome is false, no further action is taken.

The flow will look like this:

Let’s wrap up

Now you can go on and schedule this flow to run whenever you like. I would suggest running this flow daily. Depending on how you set it up, your email will look something like this:

You can also create other actions, like messages on Teams, service tickets, or text messages for example.

Variations

In this example, I use a percentage to be my threshold, but you can be creative, and adjust this to your needs. You might have just one or two licenses, and want to get alerted when you’ve got 10 licenses left. In that case, you adjust the threshold to 10 and then use the expression with the math function to calculate the free licenses:

sub(variables('TotalLicense'),variables('ActiveLicense'))

You can now use this outcome in your if-statement to trigger the alert when the licenses are less than your threshold.

Be creative!

Some useful reference

Reference guide to using functions in expressions for Azure Logic Apps and Power Automate

Power Automate documentation

Microsoft Graph REST API v1.0 reference

20 thoughts on “Use Power Automate or Logic Apps to keep an eye on your licenses”

  1. Pingback: Use Power Automate or Logic Apps to keep an eye on your licenses – 365ForAll

  2. Pingback: Use Power Automate as your Conditional Access Police Department - JanBakker.tech

  3. This is awesome, great work using automation like it should! Thanks for the idea, will implement tomorrow 😊

  4. This is great, but how do I modify this to only return SkuPartNumber of SPE_3 and SPE_5? We have many different license types, but are only concerned about Microsoft 365 Enterprise E3 and E5 license counts. THANKS!

    1. Yes, that is realy easy to do. Just edit the Fitler array step in advanced mode and change the value to @and(greater(item()?[‘prepaidUnits’]?[‘enabled’], 1),contains(item()?[‘skuPartNumber’], ‘EMSPREMIUM’)) where you replace ‘EMSPREMIUM’ with the SKU name you want to filter.

      So, what we are doing is to filter the array to only process the SKU’s with more than 1 license and with a specific name. You can add multiple SKU’s if needed, but in your case, it would look something like this: @and(greater(item()?[‘prepaidUnits’]?[‘enabled’], 1),contains(item()?[‘skuPartNumber’], ‘SPE_’))

  5. Thank you for the quick response, that worked! One more thing – the license accounts in the email notification for each SKU come out identical, even though the actual counts are different between SKUs. Is that because I need to define additional variables for each SKU? And is there a way to round off the percentage to a whole number?

    SPE_E5
    Below is an overview of the current licenses.
    Total Licenses:16363
    In Use: 16199
    98.9977388009534 % of the licenses are in use.

    SPE_E3
    Below is an overview of the current licenses.
    Total Licenses:16363
    In Use: 16199
    98.9977388009534 % of the licenses are in use.

    1. Make sure you selected the body of the filter array in the Apply to each action. From that point, all the steps need to be within the apply to each.
      I have not figured out to round the number. In your case, you could make 2 flows, each for 1 license. Instead of a percentage, you could just calculate the free licenses sub(variables(‘TotalLicense’),variables(‘ActiveLicense’)) like I described in the last section of the blog. Hope that helps.

  6. I can’t get past this error when testing powerflow: Insufficient privileges to complete the operation.
    My app reg has the following permissions:
    App – Group read all
    Delegated: Org read all, and user read
    Not sure what I’m missing

  7. how would i add multiple skus to the output
    @and(greater(item()?[‘prepaidUnits’]?[‘enabled’], 1),contains(item()?[‘skuPartNumber’], ‘EMSPREMIUM’)

    do use comma between each sku?

  8. Hi really cool this, it worked very simple.

    Is it also possible to get the names which are for example visible in the O365 admin Portal for the licenses in the email. So persons understand about which license it is, some persons dont know the skus.

    1. Hi mate, I like things simple and easy. So from the top of my head:

      1. Add a legend to your email to explain the SKU’s, like “RIGHTSMANAGEMENT” = “Azure Information Protection Plan 1” and so on.
      2. Add a Control->Switch action in your final step and use the skuPartNumber. Then make different switches for each SKU and hardcode the text in your mail. Don’t use the dynamic content.
      3. I’m sure there is something possible with the replace expression, but I have no experience with that.

      Hope that helped you out!

      1. How would one go about to create that switch? The reports works great and I greatly appreciate the guide you’ve put together. But I have a certain feeling that some people at my workplace will just get confused when they see names like “ENTERPRISEPREMIUM” or “STANDARDWOFFPACK_FACULTY”

        1. Hi Andrew, I fully agree. Adding a more user-friendly description would make much sense. As stated before, you can add a legend to your email explaining the different SKUs. That is the easiest approach. Building the switch will take more effort, both to build and maintain. It’s up to you.

  9. Pingback: Office 365 License Alerts – Send alerts to a Teams channel with adaptive cards – Whack Whack

  10. Pingback: License on-demand with Power Automate and Azure AD - JanBakker.tech

  11. Hi Jan, what power automate license is required for this? I have M365 E5 but I think that is more targeted at user area (i.e. users automating things around their service instances like EOL, Sharepoint etc.)

Leave a Reply

Your email address will not be published. Required fields are marked *