Recently, I've begun playing around with Azure Functions and Azure Automation Runbooks to accomplish various tasks automatically. While playing around automating a license count by domain report for a partner tenant, I suddenly had an idea;

Why not automate our whole MFA appointment enrollment process?

To give you a little bit of context, we have quite a few legacy customers where we have a full "managed" relationship where they still don't have multifactor authentication enabled on their Office 365 tenants. Our current process involves getting a list of employees from a contact at the customer, verifying if those employees have MFA enabled or not and then one tech takes a "project manager" type of role where they send an email to employees with a Calendly link to book an appointment where we can guide them through the process.

This approach has a few issues:

  • It's hard and time-consuming to keep track of who has an appointment booked and when
  • It's very time-consuming to organize and track the progress of MFA enrollment. We estimate it takes about 8 hours of man-time per 50 person customer on scheduling alone.
  • If we don't constantly follow up with employees that don't make appointments, they never activate it and we have to enforce it.

Now, of course, we could just enforce it for everyone and call it a day, but that generates a pretty hostile view of IT and we prefer to give one-on-one experiences to customers.

Considering all of this, I came up with a solution that works extremely well. This solution is all 100% PowerShell running in Azure functions. Of course, it's a bit hacky and while storing tens of megabytes of information in an Azure Automation Variable is probably not recommended, it absolutely works.

The solution requires a few things:

  • A licensed account in your MSP tenant that has permissions to access and modify "delegated" tenants. MFA for this account must be disabled but you can put a 128 characters password.
  • A Calendly account with all your techs enrolled with a Pro subscription. Any less subscription level will not work as this tool uses the Calendly API.
  • Some motivation to deploy this and make it work, it's very much an alpha kind of thing right now, but I sure aim to make it simple.

By the way, I called this the MFARunner. It handles almost everything. It has a few components:

  • MFARunner-Main, which is the main loop that collects information about partner tenants.
  • MFARunner-Emailer, which sends customized "book your appointment now" emails to users
  • MFARunner-CalendlyHook, which receives event bookings from users and adds them to user-specific data

I'll try to explain the general concept first, then I'll go into specifics.

This whole thing is split into two parts: Partners and Users

Partners have the following properties:

  • Is it managed?
  • Should MFA be enabled? (Automatically yes if more than 50% of users have it enabled)
  • Is the "script" enabled for this tenant?
  • How many users have MFA Enabled vs how many do not
  • The tenant domains
  • Statistics
  • (There's more but it's a good start)

Users have the following properties:

  • User email
  • MFA Enabled?
  • Is an appointment scheduled?
  • What is the last time we sent him an email?
  • What date is the appointment scheduled?
  • What date was MFA enabled?
  • Is the user exempt from MFA enrollment?

Now that you know of the basic properties, I can explain how this all works.

Every other day at 6 AM, the emailer runs. It does through every user where:

  • They are not scheduled
  • MFA should be enabled for the tenant (MFAEnabled, Managed and Enabled properties)
  • The user is not exempt
  • It is not the weekend

If all those are OK, then it sends an email just like this one:

There's also a French version that is sent, the email contains both. Of course, you can easily customize the interval for emails.

Censored, you'll find the user's full name, our phone number and our email. I'll bring your attention to two things: the display name that's automatically filled by the {{$DisplayName}} tag in the email and the cut-off date that's filled by the {{$CutOffDate}} tag in the email.

The link redirects to a Calendly page where the user books an appointment, this uses a webhook to call the CalendlyHook component which will do the following:

  • Call the Calendly API to get event data
  • Find the user by the email domain the user has entered into Calendly
  • Set the user IsScheduled properties to True, which will prevent further emails to be sent
  • Add the appointment to the user ScheduleHistory, so if it is cancelled IsScheduled is set to False.

The hook also accepts cancellations and will modify user properties accordingly.

Now, every day at 00:00 AM, the MFARunner-Main script runs and it does a lot.

First, it will loop through existing partner tenants (the ones it knows about) and will do the following:

  • Get all licensed users that do not have a * domain and get overall MFA status
  • It will save the previous state of those values inside a "statistics" variable and replace the current one with the current value
  • If a partner tenant has been marked as MFAEnabled & Managed, it will add every user to the user "table" and populate relevant values.

Then, for each partner tenant it will do the following for each user:

  • If the user does not have a "CutOffDate", it will add one on the condition that the user does not have MFA enabled and the partner tenant properties assert that it should be enabled
  • If the user is scheduled for an appointment, it will check if the appointment date + 1 day was in the past compared to the current date. If it is greater, it sets IsScheduled to false. As such, if the user ghosted his appointment he will start getting appointment emails again.
  • Update the MFAEnabled property for the user with the current state
  • Force enable MFA if we've reached the cut-off date and the feature isn't disabled.

Of course, it will also add new users.


There are a few issues still with this that must be corrected for you to really use it:

  • The PowerShell module to interact with it must be cleaned up -> Progress has been made on this
  • We need to be able to generate nice reports
  • It needs to be easy to deploy -> A lot of progress has been made on this
  • There are quite a few bugs to fix
  • We need to be able to handle deleted users
  • More partner-by-partner customization
  • A guide on how to use it
  • It needs to handle new partner tenants

In any case, you can find the source for this project right here, the relevant Azure Automation Runbooks are in the Azure folder.

There will be a few more parts to this blog series, where I'll go into details on how to deploy it and how to customize it as well as how the actual scripts work.

Also, here's what the calendly scheduling page looked like 1 day after enabling this tool for 1 customer: