Close Menu
Peter Klapwijk – In The Cloud 24-7Peter Klapwijk – In The Cloud 24-7
    Facebook X (Twitter) Instagram
    Peter Klapwijk – In The Cloud 24-7Peter Klapwijk – In The Cloud 24-7
    • Home
    • Intune
    • Windows
      • Modern Workplace
    • macOS
    • Android
    • iOS
    • Automation
      • Logic Apps
      • Intune Monitoring
      • GitHub
    • Security
      • Passwordless
      • Security
    • Speaking
    • About me
    Peter Klapwijk – In The Cloud 24-7Peter Klapwijk – In The Cloud 24-7
    Home»Automation»Monitor your Microsoft 365 licenses with Logic Apps
    Automation

    Monitor your Microsoft 365 licenses with Logic Apps

    Peter KlapwijkBy Peter KlapwijkMarch 5, 2022Updated:February 14, 202557 Mins Read

    Today a blog post in which we use a Logic Apps flow to monitor our Microsoft 365 licenses, such as Office 365 and Intune licenses. With this flow, we’ll receive an email when the number of available licenses gets below a certain minimum number.

    The solution

    The solution is pretty simple and mostly relies on Graph API queries. Via Grap API we can query all our Microsoft 365 licenses. The information we get back holds the number of enabled and consumed licenses, with which we can calculate the number of available licenses. I also added an additional part in the flow, to get notified on an expired license, which is still assigned to user accounts and still might be needed although the license is expired.

    This is what the complete flow looks like.

    The flow is also available on my GitHub repository as an ARM template.
    Luise Freese contributed to the solution and now you can also deploy it with Azure Bicep and use an Azure Managed Identity instead of a client secret (not described in this article, but available on GitHub).

    The requirements

    To get this Logic Apps flow up and running we have a few requirements.

    Via an HTTP action, we’re going to query Graph API for the information we need. This HTTP action uses an Azure App Registration, which is our first requirement. The App Registration is used for authentication in the HTTP actions and also for granting the required permissions.
    The minimum required (Application) permission for this Logic App is shown below:
    Organization.Read.All

    To secure the HTTP action I use Azure Key Vault, which is described in this previous post. The Key Vault holds the client secret and keeps this secure in the flow. I also added the tenant id and client id to the vault, so I only have to query the Key vault for this information.
    In every HTTP action, the tenant and client id need to be added. By storing the information in a variable or in a Key Vault, we don’t have to copy/ paste the ids in every HTTP action.

    Depending on how you want to receive the notifications, permissions on a (shared) mailbox or a Teams webhook connector are needed.

    Setup the Logic Apps flow

    Let’s configure the Logic Apps flow!

    Sign in to the Azure portal and open the Logic App service. I created a blank Logic App of type Consumption.

    When the creation of the Logic App is finished, open the flow. A few templates are shown, 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 a week.

    The first action we’re going to add is the Get secret action, which is an Azure Key Vault action. With this action, we retrieve the tenant id, which we use in the HTTP action.
    Search on Azure Key Vault and select Get secret.

    Enter the name of the Vault and select your tenant. Next, click Sign in to authenticate to the Key Vault.

    Choose tenant-id from the drop-down list.

    Repeat the Key Vault steps to also retrieve the client id and client secret.

    The next step we’re going to add is an HTTP Action, to query Graph for the licenses.

    Choose GET as Method.
    As URI enter:

    https://graph.microsoft.com/v1.0/subscribedSkus

    Choose Add new parameter and check Authentication. Select Active Directory OAuth as authentication type.
    As tenant, client id, and secret, we add the Dynamic content Value. Make sure you pick the value from the correct Key Vault action.
    Enter https://graph.microsoft.com as Audience.

    This is our HTTP action.

    To use the information we received (by executing this HTTP action) in the upcoming actions, we need to parse the received information with a Parse JSON action.

    To fill the schema with the correct information, we can add an example payload. The information to use as an example payload can be ‘generated’ by running the flow. Save the flow and hit Run trigger to start the flow.

    Open the Runs history from the flow and open the HTTP action. Copy the body.

    Add a Parse JSON action, which is a Data operation action.
    Add Body of the HTTP action in the Content field.
    Click Use sample payload in the Parse JSON action, past the information which we copied from the runs history in the new pop-up, and click Done.

    This is our Parse JSON action.

    The next action is an HTTP action. With this action, we run a query to retrieve information for a single license, for which we use the ID of the license. Because we retrieved multiple license IDs, the HTTP action is automatically placed in a For each action, as soon as we add the Dynamic content ID (from the Parse JSON action).

    I query the licenses one by one, although we already have all the information from the first HTTP action, to make sure the output from all the licenses doesn’t get mixed and so I’m sure the numbers I use later in the actions are correct.

    Choose GET as Method.
    As URI enter:

    https://graph.microsoft.com/v1.0/subscribedSkus/[ID]

    Replace [ID] with the ID found via Dynamic content.

    Further, fill in the Audience and values for tenant id, client id, and client secret.

    We need to parse the output from the HTTP action again with a Parse JSON action.

    Next, I wanted to use a Variable action to calculate the number of available licenses and use that variable in the coming actions. Although this calculation itself within a variable works fine, for some reason the usage of the variable in the upcoming Control action gave me inconsistent results. That’s why I don’t use a variable, but I do the calculation in the Control action itself.

    Add a Condition action, which is a Control action.

    I want to make sure that only the Enabled licenses are used in the upcoming action, that’s why I use a condition Enabled is greater than 0. That’s because we can also have a number of enabled licenses, although the license itself has another status (warning for example), which I process in another part of this flow.
    Enabled is used from the latest Parse JSON action.

    We are going to subtract the Enabled and consumptedUnits numbers from the latest Parse JSON action, the output is our number of available licenses.

    Click Add and add a new row.
    In the left field, we are going to add the subtraction. Open the Expression tab and enter sub().

    Open the Dynamic content tab, make sure the cursor is located between the parentheses (), and choose Enabled from the list (from the latest Parse JSON action).
    Place the cursor between the comma and closing parenthesis and add consumedUnits.
    Click OK.

    Choose is less than and in the right field enter the number under which you want to receive a warning.

    You can also copy and paste the below as Expression, instead of adding the above subtraction manually. But make sure you replace Parse_JSON_GET_one_Sku_enabled (twice) with the name of your own Parse JSON action, and replace the spaces with a hyphen.

    sub(body('Parse_JSON_GET_one_Sku_enabled')?['prepaidUnits']?['enabled'],body('Parse_JSON_GET_one_Sku_enabled')?['consumedUnits'])

    Under True, we can add our action to get notified. In my case, I used an action to send an email from a shared mailbox.
    We can use the information from the latest Parse JSON action and the subtraction, to put this information in the notification.

    The above actions are enough to get notified when you’re running out of licenses. But I’ve also seen licenses that are already expired (which get a status of Warning instead of Enabled) but are still assigned and used. To get also notified of these licenses, we can add a parallel branch in our flow, which runs next to the above created flow.

    To create this parallel branch, hit the plus sign between the first Parse JSON action and For each action and choose Add a parallel branch.

    Like in the other branch, we are querying Graph for the licenses with the license ID, with an HTTP action.

    And we also need to add a Parse JSON action.

    Next, we need to add a Condition action.

    I want to get notified when the number of licenses with a status of Warning is greater than 0 and if we also have consumed licenses. Therefore we add Enabled and consumedUnits in the left field (add a second row).

    Under True, we can add our notification.

    That,s it! Our license monitoring system is ready!

    Flow Graph Graph API Intune Logic Apps MEM Microsoft 365 Microsoft Endpoint Manager Power Automate PowerApps Windows 10 Windows Autopilot
    Share. Facebook Twitter LinkedIn Email WhatsApp
    Peter Klapwijk
    • Website
    • X (Twitter)
    • LinkedIn

    Peter is a Security (Intune) MVP since 2020 and is working as Modern Workplace Engineer at Wortell in The Netherlands. He has more than 15 years of experience in IT, with a strong focus on Microsoft technologies like Microsoft Intune, Windows, and (low-code) automation.

    Related Posts

    Re-import Autopilot information from Azure Storage on-demand with a Logic Apps flow

    July 9, 2022

    Export Endpoint Analytics Proactive remediation data with Logic Apps

    April 19, 2021

    MEM Monitoring: Get your Windows Autopilot deployment events in a Teams channel with Logic Apps – Part 1

    April 16, 2021
    View 5 Comments

    5 Comments

    1. christos0010 on March 7, 2022 08:11

      Hello, very promising app but i am getting this error:

      “InvalidTemplate. Unable to process template language expressions for action ‘Condition_2’ at line ‘0’ and column ‘0’: ‘The template language function ‘greater’ expects all of its parameters to be either integer or decimal numbers. Found invalid parameter types: ‘Null’.’.”

      Why i am getting Null and not an integer?

      Reply
      • Audun Levin on October 4, 2022 10:21

        Did you find an answer to this? I get the same result, Found invalid parameter types; null.

        Reply
        • Ina Shabani on March 6, 2024 15:57

          Hey, did you manage to fix the integer problem? i tried converting it but no luck

          Reply
    2. NC on May 9, 2022 21:49

      Is there an easy way to have this (and your other logic apps) send a singular email containing all the info vs an individual email for each license? Looking at posting this in teams vs email, but either way getting multiple emails is inefficient but i am struggling to find a way to group all the info and send as one message.

      Reply
    3. Erwin on February 27, 2024 12:02

      Hello,

      Is there a way to read the license expire date? Like send mail 1 week before license will expire and how many there are not assignd and assigned.

      Is that possible to create? Rest is working fine for me.

      Reply
    Leave A Reply Cancel Reply

    Peter Klapwijk

    Hi! Welcome to my blog post.
    I hope you enjoy reading my articles.

    Hit the About Me button to get in contact with me or leave a comment.

    Awards
    Sponsor
    Latest Posts

    Create deployment ring groups for Microsoft Intune

    June 27, 2025

    Update Windows Defender during Windows Autopilot enrollments

    May 16, 2025

    Hide the “Turn on an ad privacy feature” pop-up in Chrome with Microsoft Intune

    April 19, 2025

    How to set Google as default search provider with Microsoft Intune

    April 18, 2025
    follow me
    • Twitter 4.8K
    • LinkedIn 6.1K
    • YouTube
    • Bluesky 1.5K
    Tags
    Administrative Templates Android Automation Autopilot Azure Azure AD Browser Conditional Access Edge EMS Exchange Online Feitian FIDO2 Flow Google Chrome Graph Graph API Identity Management Intune Intune Monitoring iOS KIOSK Logic Apps macOS MEM MEMMonitoring Microsoft 365 Microsoft Edge Microsoft Endpoint Manager Modern Workplace Office 365 OneDrive for Business Outlook Passwordless PowerApps Power Automate Security SharePoint Online Teams Windows Windows 10 Windows10 Windows 11 Windows Autopilot Windows Update
    Copy right

    This information is provided “AS IS” with no warranties, confers no rights and is not supported by the authors, or In The Cloud 24-7.

     

    Copyright © 2025 by In The Cloud 24-7/ Peter Klapwijk. All rights reserved, No part of the information on this web site may be reproduced or posted in any form or by any means without the prior written permission of the publisher.

    Shorthand; Don’t pass off my work as yours, it’s not nice.

    Recent Comments
    • Parth Savjadiya on Using Visual Studio with Microsoft Endpoint Privilege Management, some notes
    • Chris Johnson on Assign Deny Local Log On user right to an (Azure) AD group by using Microsoft Intune
    • Northernsky on Automatically wipe a Windows 10 device after a number of authentication failures
    • Henrik on Intune Driver update for Windows – Get applicable devices
    • Adam on Get notified on expiring Azure App Registration client secrets
    most popular

    Application installation issues; Download pending

    October 1, 2024

    Restrict which users can logon into a Windows 10 device with Microsoft Intune

    April 11, 2020

    How to change the Windows 11 language with Intune

    November 11, 2022

    Update Microsoft Edge during Windows Autopilot enrollments

    July 9, 2024
    Peter Klapwijk – In The Cloud 24-7
    X (Twitter) LinkedIn YouTube RSS Bluesky
    © 2025 ThemeSphere. Designed by ThemeSphere.

    Type above and press Enter to search. Press Esc to cancel.

    Manage Cookie Consent
    To provide the best experiences, we use technologies like cookies to store and/or access device information. Consenting to these technologies will allow us to process data such as browsing behavior or unique IDs on this site. Not consenting or withdrawing consent, may adversely affect certain features and functions.
    Functional Always active
    The technical storage or access is strictly necessary for the legitimate purpose of enabling the use of a specific service explicitly requested by the subscriber or user, or for the sole purpose of carrying out the transmission of a communication over an electronic communications network.
    Preferences
    The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.
    Statistics
    The technical storage or access that is used exclusively for statistical purposes. The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.
    Marketing
    The technical storage or access is required to create user profiles to send advertising, or to track the user on a website or across several websites for similar marketing purposes.
    Manage options Manage services Manage {vendor_count} vendors Read more about these purposes
    View preferences
    {title} {title} {title}