Create Exchange Administrators in all customer Office 365 tenants using Azure Functions and Delegated Administration

This is the second in a 3 part series on integrating Azure Functions, Microsoft Flow and Office 365 to automate Office 365 administration and reporting across multiple Office 365 tenants. See part 1 here on how to retrieve Office 365 user details using Azure Functions, Microsoft Flow and SharePoint Online.
In this post we’ll be creating Exchange Service Administrators in each one of our Customers Office 365 tenants using delegated administration and Azure functions.
To manage these Exchange Administrators, we’ll create three separate Azure Functions.
- Function 1 will create the Exchange Administrators with blocked credentials
- Function 2 will unblock a specific Exchange Administrator when it’s required
- Function 3 will block any unblocked admins when they’re not required by another function
Function 1 Overview: Creating Exchange Administrators in all delegated tenants
Timer Triggered - PowerShell - Azure Function
We’ll be able to use these Exchange Admins in later posts to run tasks that don’t work under an Office 365 delegated admin account.
To prevent unauthorised usage of these Exchange Administrators, the accounts are created with blocked credentials and are unblocked when required by another Azure function. Each day when this function is run, it performs the following actions:
- Check whether the Exchange Administrator exists, and if it doesn’t, create it and block it’s credentials.
- If it does exist, check whether the credentials are still blocked and that it’s still an Exchange admin.
- If credentials aren’t blocked, we wait a few minutes to give any running functions enough time to complete, then block the credentials again.
We’ll also set up another Azure Function that can be used for…
Function 2 Overview: Unblocking an Exchange Administrator when it’s required
HTTP Triggered - PowerShell - Azure Function
We want to be able to unblock our Exchange Administrators on demand. In our case, we’ll be using these admins for a few tasks, so we’re unblocking them with a separate function that we can call from other functions or flows.
This function can be called via a HTTP request from Microsoft Flow. It’ll unblock the reporting user, then update an Azure Storage Table with a time thirty minutes in the future (TimeToBlock). The reason for this is that we may be requiring this user multiple times in quick succession. If the function automatically blocks the user on a timer, it’ll interrupt the running of any following functions that require the user too.
Each time the function is called, the time that the user will be blocked is updated to the current time plus thirty minutes.
This brings us to our third Azure Function
Function 3 Overview: Check for unblocked reporting users
Timer Triggered - PowerShell - Azure Function
We don’t want these Exchange admins to remain unblocked for long. Every thirty minutes, this function will check the Azure Storage Table that the previous function updated. If the TimeToBlock value is in the past, this function will block the user and update the table.
Building Function 1: Creating Exchange Administrators in all delegated tenants
Each of the Azure functions we’ll be creating will need to connect to Office 365.
Follow the instructions in our knowledge base article here to create an Azure function with the following properties:
- Name it whatever you like - ours is called CreateReportingUsersOnTimer
- Set it to run once each weekday using the following CRON schedule:
0 30 9 * * 1-5
Create and encrypt credentials for your Exchange Administrators
Retired October 2026: These steps set one shared password for a new Exchange administrator account in every customer tenant and stored it in the function app next to the key that decrypts it, so anyone with access to the function app could read it. We no longer recommend shared admin accounts in customer tenants. Today, use GDAP roles assigned to named technicians with MFA, and a managed identity with Key Vault references for automation.
Create Azure Storage Account for status tracking
We’ll also be logging the status of each delegated admin in Azure Table Storage. To do this, we’ll create an Azure Storage account, then add the details to your Azure Function’s application settings.
You can follow the instructions on this page to create a storage account, retrieve the account key and secret, and add them to your Azure Function application settings. Once you’ve completed this, return to this article. Your Applications Settings values should look something like this:
Retired October 2026: This script created an Exchange administrator account with the same password in every customer tenant, signing in with a stored delegated admin password that cannot use MFA. Today, use GDAP roles assigned to named technicians with MFA, and Connect-ExchangeOnline -DelegatedOrganization for customer Exchange tasks, with no standing shared admin accounts.
Building Function 2 - Unblock a reporting user on demand
This function will be used to unblock a specific reporting user when required. You can call it from a Microsoft flow or another function via it’s Function URL. This function will also update the Azure Storage table we created earlier with the block status and WhenToBlock value for each reporting user.
Create a new HTTP triggered Azure Function with the following properties:
- Call it whatever you like. This example is called UnblockReportingUser
- Retrieve the function URL from the top right of the function and save it somewhere.
Retired October 2026: This script unblocked a shared, password-only Exchange administrator account in a customer tenant whenever its function address was called. Today, use GDAP roles assigned to named technicians with MFA, and for scheduled tasks use an app with certificate-based sign-in instead of shared admin accounts.
Building Function 3 - Check Reporting User Status
This function will run every 30 minutes to check for unblocked reporting users. It connects to your Azure Storage table and looks for unblocked users whose WhenToBlock value is in the past. If it finds one, it’ll block the user and update the storage table.
Create a new Timer Triggered PowerShell function with the following values:
- Name it whatever you like, I called ours CheckReportingUserStatus
- Set it to run every 30 minutes using the following CRON schedule:
0 */30 * * * *
Retired October 2026: This script blocked the shared Exchange administrator accounts again on a timer, and to do that it decrypted the stored delegated admin password using the key kept beside it in the function app. If any of these accounts still exist, block or delete them in each customer tenant, and delete the stored password, key and function app. We no longer recommend standing shared admin accounts. Today, use GDAP roles assigned to named technicians with MFA.
The next post in the original series used these shared Exchange admin accounts to offboard Office 365 users. We no longer recommend creating these accounts. Offboard users with GDAP roles assigned to named technicians, or with Microsoft Graph PowerShell and the Exchange Online PowerShell module.