☁️
Tminus365 Docs
  • 🚀Welcome to Tminus365 Docs
  • 🔐Security
    • Azure AD (Entra)
      • MFA Shall Be Required for All Users
      • MFA is enforced on accounts with Highly Privileged Roles
      • MFA is enforced for Azure Management
      • MFA registration and usage shall be periodically reviewed
      • Legacy Authentication shall be blocked
      • High Risk Users Shall Be Blocked
      • High Risk Sign-Ins Shall Be Blocked
      • Browser Sessions shall not be persistent for privileged users
      • MFA shall be required to enroll devices to Azure AD
      • Managed Devices shall be required for authentication
      • Guest User Access Shall be restricted
      • The number of users with highly privileged roles shall be limited
      • Users assigned highly privileged roles shall not have permanent permissions
      • Activation of privileged roles should be monitored and require approval
      • Highly privileged accounts shall be cloud-only
      • Highly privileged role assignments shall be periodically reviewed
      • Passwords shall not expire
      • Azure AD Logs shall be collected
      • Only Admins shall be allowed to register 3rd party applications
      • Non-admin users shall be prevented from providing consent to 3rd party applications
      • Authorized Applications shall be configured for Single Sign-On
      • Inactive accounts shall be blocked or deleted
    • Teams
      • Private Channels shall be utilized to restrict access to sensitive information
      • External Participants SHOULD NOT Be Enabled to Request Control of Shared Desktops or Windows in Meet
      • Anonymous Users SHALL NOT Be Enabled to Start Meetings
      • Automatic Admittance to Meetings SHOULD Be Restricted
      • External User Access SHALL Be Restricted
      • Unmanaged User Access SHALL Be Restricted
      • Contact with Skype Users SHALL Be Blocked
      • Teams Email Integration SHALL Be Disabled
      • Only Approved Apps SHOULD Be Installed
      • File Sharing and File Storage Options shall be blocked
      • Only the Meeting Organizer SHOULD Be Able to Record Live Events
      • Attachments SHOULD Be Scanned for Malware
      • Link Protection SHOULD Be Enabled
      • Restrict Users who can Create Teams Channels
      • Teams Channels shall have an expiration policy
      • Data Loss Prevention Solutions SHALL Be Enabled
    • Exchange
      • Automatic Forwarding to External Domains SHALL Be Disabled
      • Sender Policy Framework SHALL Be Enabled
      • DomainKeys Identified Mail SHOULD Be Enabled
      • Domain-Based Message Authentication, Reporting, and Conformance SHALL Be Enabled
      • Enable Email Encryption
      • Simple Mail Transfer Protocol Authentication SHALL Be Disabled
      • Calendar and Contact Sharing SHALL Be Restricted
      • External Sender Warnings SHALL Be Implemented
      • Data Loss Prevention Solutions SHALL Be Enabled
      • Emails SHALL Be Filtered by Attachment File Type
      • Zero-Hour Auto Purge for Malware SHOULD Be Enabled
      • Phishing Protections SHOULD Be Enabled
      • Inbound Anti-Spam Protections SHALL Be Enabled
      • Safe Link Policies SHOULD Be Enabled
      • Safe Attachments SHALL Be Enabled
      • IP Allow Lists SHOULD NOT be Implemented
      • Mailbox Auditing SHALL Be Enabled
      • Alerts SHALL Be Enabled
      • Audit Logging SHALL Be Enabled
      • Enhanced Filtering Shall be configured if a 3rd party email filtering tool is being used
    • SharePoint
      • File and Folder Links Default Sharing Settings SHALL Be Set to Specific People
      • External Sharing SHOULD be Set to “New and Existing Guests”
      • Sensitive SharePoint Sites SHOULD Adjust Their Default Sharing Settings
      • Expiration Times for Guest Access to a Site SHOULD Be Determined by specific needs
      • Users SHALL Be Prevented from Running Custom Scripts
    • OneDrive
      • Anyone Links SHOULD Be Turned Off
      • Expiration Date SHOULD Be Set for Anyone Links
      • Link Permissions SHOULD Be Set to Enabled Anyone Links to View
      • Windows and MacOS devices should be prevented from syncing the OneDrive Client on personal devices
      • Legacy Authentication SHALL Be Blocked
    • Intune
      • Personal Devices should be restricted from enrolling into the MDM solution
      • Devices shall be deleted that haven’t checked in for over 30 days
      • Devices compliance policies shall be configured for every supported device platform
      • Noncompliant devices shall be blocked from accessing corporate resources
      • MFA Shall be required for Intune Enrollment
      • Security Baselines should be configured for Windows Devices
      • Windows Update Rings shall be configured for Windows Devices
      • Update Policies shall be configured for Apple Devices
      • App Protection policies should be created for mobile devices
      • Mobile devices shall only be able to access corporate data through approved client apps
      • Lockout screen and password settings shall be configured for each device
      • Encryption shall be required on all devices
      • Windows Hello for Business should be configured where applicable
      • Authorized Applications should be deployed to managed devices
      • Device Use Shall be restricted until required applications are installed
      • Devices and Applications shall be wiped when a user leaves the organization or reports a lost/stolen
  • ⚙️Configurations
    • GDAP
      • My Automations Break with GDAP: The Fix!
      • Vendor Integrations Break with GDAP: The Fix!
      • Adding GDAP Relationships
      • Leveraging PIM with GDAP
      • GDAP Migration with Microsoft 365 Lighthouse
    • GoDaddy
      • Defederating GoDaddy 365
  • 🛡️CIS Controls
    • CIS Mapped to M365
  • 🔌Vendor Integrations
    • Pax8
      • Automating NCE subscription renewal notices
      • Leveraging the Pax8 API in Power Automate
    • IT Glue
      • Automating Intune Device Documentation in IT Glue
      • Automating Microsoft Documentation
    • Huntress
      • Leveraging the Huntress API in Power Automate
    • Syncro
      • Automating Microsoft 365 Documentation in Syncro
      • Custom Connector in Power Automate
      • Creating Tickets for Azure AD Risky Users
Powered by GitBook
On this page
  • The Solution
  • Prerequisites
  • The Steps
  • The Full Tutorial
  1. Vendor Integrations
  2. Pax8

Automating NCE subscription renewal notices

PreviousPax8NextLeveraging the Pax8 API in Power Automate

Last updated 1 year ago

Microsoft New Commerce introduced a new level of liability because of their commitment terms and cancellation policies. On top of that they also introduced new operational complexity through subscription renewal dates which could be different for each customer in your environment if you did not move them to NCE on the same date.

Microsoft has introduced what they call “Coterminosity”. Essentially it allows you to align end dates for customers and one of those options now is by Calendar month charge cycle. This means you could potentially align subscriptions more so how they used to be on legacy and how you bill out with PSA. The problem is if you were an earlier adopter of NCE and work with a distributor, its likely they haven’t gotten around to updating your renewal dates to the calendar month. Another new reality is that because of the discounted rate, many of us are using annual contracts now which can also have variable renewal dates per customer.

Finally, because of the strict cancellation/decrement window, its important that you know when renewals are coming up so that you can adjust seat counts before being locked in again. For all of these reasons, I wanted to create some automation around NCE renewals across customers.

The Solution

Using the Pax8 API, Power Automate, and a custom connector for my PSA in Power Automate, I was able to create a flow that automatically creates a ticket in Syncro (example PSA) when a subscription is coming up for renewal.

You can get creative here, I personally want to drive action into a ticket so we can track time and document communications. Here are some other ideas you can think of:

  • Generating a Sales Order so reps can review with customer

  • Generating a Teams Message

  • Generating seat Approval Notices to Customers for upcoming renewals (via email, Teams, etc)

Example in Teams:

There are also more advanced solutions you can implement as well that I have implemented that require a bit more work:

  • Populating Available Counts vs Consumed Counts for the licenses into the Ticket (MS Graph API, what I did in the screenshot above)

  • Populating Assigned Users (MS Graph API)

  • Automatically adjusting the licensing at renewal (Meaning we leverage the Pax8 API and Graph API to reduce seat counts from Available Counts minus Consumed Counts automatically)

Ultimately, I am looking to reduce liability and make this process as touchless as possible.

Prerequisites

  • Pax8 API Client ID and Secret

  • Premium Power Platform License

The Steps

There are many steps in Power Automate to take. For the complete list, check out my video at the bottom of the page. Here is a summary of what I am doing:

1. Set up reoccurrence to happen daily

2. Set the date you want to be notified. You can use a couple of expressions in Power Automate to get the current date and then add days. This allows you to get notices of upcoming renewal dates when we loop through out Pax8 subscriptions

The formula: formatDateTime(addDays(utcNow(),<Insert Number of Days to Add>), ‘yyyy-MM-dd’)

4. Use the Secrets to create an HTTP request to get an Access token. (connector is HTTP). Parse the Access Token with the using the Pax8 API docs to generate the schema

5. Leverage the access token to call the Subscriptions endpoint. Here we are making this call to get the total pages that return. We will use this to loop through all pages. We are doing this because the Pax8 API uses pagination. Leverage their docs again to Generate the schema for the Parse JSON action.

6. Use the Variable connector to create variables for Total Pages and Current Page. This is what we will use to loop through all pages. Decrement the Total Page variable by one since the Pax8 API always returns a blank page on the final page.

7. Create two more variables for the CustomerName and Product Name. Make them both strings and leave their values blank. We will dynamically define them later in the flow.

8. Next we will use the Do Until Control to loop through out pages.

9. We will make another HTTP request to get the subscriptions but setting the query parameter for pages to the current page variable since we will be looping through each page. Use the following for the Parse JSON schema:

{
    "type": "object",
    "properties": {
        "content": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "companyId": {
                        "type": "string"
                    },
                    "productId": {
                        "type": "string"
                    },
                    "quantity": {
                        "type": [
                            "integer",
                            "number"
                        ]
                    },
                    "startDate": {
                        "type": "string"
                    },
                    "createdDate": {
                        "type": "string"
                    },
                    "billingStart": {
                        "type": "string"
                    },
                    "status": {
                        "type": "string"
                    },
                    "price": {
                        "type": [
                            "integer",
                            "number"
                        ]
                    },
                    "billingTerm": {
                        "type": "string"
                    },
                    "commitment": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "term": {
                                "type": "string"
                            },
                            "endDate": {
                                "type": "string"
                            }
                        }
                    },
                    "endDate": {
                        "type": "string"
                    }
                },
                "required": [
                    "id",
                    "companyId",
                    "productId",
                    "quantity",
                    "startDate",
                    "createdDate",
                    "billingStart",
                    "status",
                    "price",
                    "billingTerm"
                ]
            }
        },
        "page": {
            "type": "object",
            "properties": {
                "size": {
                    "type": "integer"
                },
                "totalElements": {
                    "type": "integer"
                },
                "totalPages": {
                    "type": "integer"
                },
                "number": {
                    "type": "integer"
                }
            }
        }
    }
}

10. Next we will add an Apply to Each Control and use the dynamic content coming in from the parse we just did on the Company HTTP request. We will then add a Condition Control in which we will populate the endDate that is equal to outputs (from out Get Date compose operation earlier in the flow). *Note* If you see two dynamic endDates, choose the first one. Here we are basically saying we want to find a subscription record renewal date (aka the endDate) that is equal to the date we defined earlier. If no for this condition will be set to blank since we do not need other actions to occur if no match is found.

11. In the If yes section of the condition, we will make a couple more HTTP request both for the company record and product record. Here we are dynamically populating the record IDs from the subscription record. We are then using Parse JSON on both to get the content.

12. We use the set variable action under the Variable controls to set our Product Name and Company Name values that we initialized earlier in the flow.

13. Next, we are using our Custom Connector with Syncro to leverage their search endpoint. In it we will populate the CustomerName Variable. We will then parse the json again. Leverage the schema I provide below:

{
    "type": "object",
    "properties": {
        "quick_result": {},
        "results": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "table": {
                        "type": "object",
                        "properties": {
                            "_id": {
                                "type": "integer"
                            },
                            "_type": {
                                "type": "string"
                            },
                            "_index": {
                                "type": "string"
                            },
                            "_source": {
                                "type": "object",
                                "properties": {
                                    "table": {
                                        "type": "object",
                                        "properties": {
                                            "firstname": {
                                                "type": "string"
                                            },
                                            "lastname": {
                                                "type": "string"
                                            },
                                            "email": {
                                                "type": "string"
                                            },
                                            "business_name": {
                                                "type": "string"
                                            },
                                            "phones": {
                                                "type": "array"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "required": [
                    "table"
                ]
            }
        },
        "error": {}
    }
}

14. We create another Condition action and we determine if our search came back with a value or not. For this, we use the native expression empty(), and we populate it with the dynamic value of the search results

15. If the value is empty, I chose to post about it in Teams using the message connector. If it does find a value (i.e. If No), we are using the Scope connector to enforce a try catch block. (Using two Scope actions)

16. In the Try block, we are first creating a Compose operation like we did for the Get Date earlier. Here we are defining an expression using the following formula:

outputs(‘Parse_Search’)[‘body’][‘results’][0][‘table’][‘_id’]

We are using this formula to traverse the array that is returned as part of our Search endpoint with Syncro in order to get our customer Id(we will need this to create the Ticket)

The reason why (the array we are traversing)

17. Finally we are using the custom connector to create a ticket. We are using the output we got from the previous step to define the customer ID. We are using the expression utcNot() for the startAt variable. Lastly, we are dynamically populating values that are coming from variables and JSON outputs above. Keep in mind, the entries available to me here are coming from what I created when I set up the custom connector.

18. You can decide what to put into the second Scope (Catch) block. Think of this like error handling if the API request fails. You may want to create another notice in Teams or something of the like. Make sure you configure the settings as follows:

The Full Tutorial

Questions Post any questions about this to the video!

(Both of the above I outline in my previous post on )

Custom Connector for PSA (like what I with Synco)

3. Get ClientID and Secret for Pax8 API from Azure Key Vault connector. (Showed this in my )

🔌
Leveraging the Pax8 API
show here
previous article