Alex - stock.adobe.com

Manage Microsoft 365 users and groups with Azure Functions

This Microsoft service performs large-scale admin jobs and also automatically reacts to certain conditions to execute predefined tasks with speed and consistency.

Microsoft 365 provides efficient management capabilities for most administrative needs, but specific complex scenarios require more advanced automation and customization approaches. Azure Functions is one option that extends the capabilities of Microsoft 365 administration beyond conventional boundaries.

Managing user attributes, such as job titles, departments or contact information, in large organizations can be difficult. Azure Functions is a Microsoft serverless computing service that Microsoft 365 administrators can use for a variety of management jobs related to users and groups on the cloud collaboration platform. With its versatile trigger functionality, Azure Functions provides flexibility that native Microsoft 365 tools can't match. You can use them to dynamically provision user accounts, manage group access with nuanced control or execute bulk attribute updates. This article explains how to deploy advanced automation routines for enhanced efficiency and customization in managing users and groups.

Automating Microsoft 365 user and group tasks with Azure Functions

Automating the process of adding and removing users in Microsoft 365 can significantly reduce the workload of administrators. By configuring Azure Functions to listen for events, such as updates from the HR system or specific email requests, you can automate the creation or deletion of user accounts. It ensures users have access when needed and maintains security on these accounts throughout the lifecycle.

Azure Functions is capable of dynamically managing group memberships and access controls. It can adjust group memberships based on factors such as user roles, project or location to align access levels with organizational policies and changes.

Integration with third-party products and services

Another benefit of Azure Functions is its ability to integrate Microsoft 365 management with third-party products and services. Azure Functions interacts with external APIs and services to create comprehensive management workflows that include external identity providers, security tools or productivity applications.

Deploying Azure Functions for Microsoft 365 management

The Azure Functions versatile serverless computing framework provides Microsoft 365 administrators an advanced toolkit for automating and customizing user and group management tasks. This section covers practical applications of Azure Functions to streamline the management of Microsoft 365 environments, focusing on dynamic provisioning, customized group management, bulk user attribute updates and advanced auditing.

Dynamic provisioning and deprovisioning

Dynamic provisioning and deprovisioning entail automatically creating or removing user accounts based on organizational needs, reducing manual work and enabling the operations team to be more efficient. This automation aligns user access rights with their status, such as employment changes, ensuring security and compliance.

To configure the trigger, set up either an HTTP trigger or a scheduled trigger in Azure Functions. The trigger listens for events that indicate a change in user status, such as updates in the HR system. Next, develop a PowerShell script that connects to the Microsoft Graph API to create or delete user accounts in Microsoft 365, depending on the trigger. The script should be versatile enough to handle several scenarios, such as new hires or terminations. After developing the script, test it thoroughly in a nonproduction environment to ensure it works as intended. Once the script is verified, deploy it to manage user accounts dynamically. Microsoft constantly updates its cloud-based products, so it's essential to run regular checks on code to stay current with Microsoft 365 APIs or organizational policies.

Customized group management and access control

Customizing group management and access control gives precise control over resource access, ensuring users have the necessary permissions based on their roles and responsibilities. This level of customization prevents unauthorized access to bolster security.

You can utilize Azure Functions with triggers based on user activity or admin requests to automate user access by modifying group memberships and access permissions. Moreover, you can use scripts to automatically update group memberships and access levels in Microsoft 365 based on project assignments, department changes or any other criteria.

Bulk user attribute management

Manually managing attributes for many users can be a daunting and time-consuming task. Without automation, it's easy to introduce errors. However, with bulk user attribute management through Azure Functions, automation can ensure consistency and accuracy across the user base. The general procedure for this task includes the following steps:

  1. Bulk update mechanism. Create an Azure Function triggered by an external event, such as a database update or a predetermined schedule.
  2. Script execution. The function executes a script to modify user attributes in bulk via the Microsoft Graph API. It could involve updating job titles, department names or contact information.
  3. Validation and rollout. Implement validation checks within the script to prevent incorrect data updates. After thorough testing, deploy the function for ongoing, automated user attribute management.

Advanced auditing and reporting

It's imperative to have in-depth auditing and customized reporting for security, compliance and operational oversight. With Azure Functions, you can automate report generation and alerts, providing real-time insights into user and group activities. These are the components for this type of automated task:

  • Data collection. Set up Azure Functions to collect data on user and group activities from various sources, including Microsoft 365, with triggers, such as HTTP requests or log file updates.
  • Report generation. Use scripts within Azure Functions to process this data and generate reports or dashboards for valuable insights into user behavior, group membership changes and access patterns.
  • Alerting mechanism. By incorporating logic to detect anomalies or compliance violations, Azure Functions can send real-time alerts to administrators or security teams for prompt action.

Examples of dynamic provisioning and deprovisioning

In this example, to start, you need to have created the function app and added an HTTP trigger. To learn how to do this, refer to the following documentation.

The next requirement is app registration for Microsoft Graph with the following permissions:

  • Directory.ReadWrite.All.
  • Group.ReadWrite.All.
  • GroupMember.ReadWrite.All.
  • Sites.FullControl.All.
  • Sites.Manage.All.
  • Sites.Read.All.
  • Sites.ReadWrite.All.
  • User.Read.
  • User.ReadWrite.AII.

The following code gives permission to send a JSON request to the Azure function app:

{

"displayName": "John Doe",

"mailNickname": "johndoe",

"userPrincipalName": "[email protected]",

"passwordProfile": {

"password": "Pass@word!"

},

"groupName": " Mark 8 Project Team"

}

The code for the HTTP trigger is:

using namespace System.Net

param($Request, $TriggerMetadata)

 

# For Testing Only

$clientId = "3dc8e6d6-e7f5-4e75"

$tenantId = "f5dba133-4a2b-40d4"

$clientSecret = "6328Q~em6eORoAMkQs1jFaDq~"

 

$securePassword = ConvertTo-SecureString $clientSecret `

-AsPlainText `

-Force

$credential = New-Object System.Management.Automation.PSCredential ($clientId, $securePassword)

 

Connect-MgGraph `

-TenantId $tenantId `

-ClientSecretCredential $credential `

-NoWelcome

 

$body = $Request.Body | ConvertFrom-Json | ConvertFrom-Json

 

# Retrieve the Project Group

$groupName = "Mark 8 Project Team"

$group = Get-MgGroup -Filter "displayName eq '$groupName'"

$groupId = $group.Id

 

try {

$newUser = New-MgUser -AccountEnabled `

-DisplayName $body.displayName `

-MailNickname $body.mailNickname `

-UserPrincipalName $body.userPrincipalName `

-PasswordProfile @{

ForceChangePasswordNextSignIn = $true;

Password = $body.passwordProfile.password

}

 

New-MgGroupMember -GroupId $groupId -DirectoryObjectId $newUser.Id

$result = @{

Id = $newUser.Id

Displayname = $newUser.Displayname

UserName = $newUser.UserPrincipalName

GroupName = $group.DisplayName

}

} catch {

$result = @{

Error = $_.Exception.Message

}

}

 

Disconnect-MgGraph

 

Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{

StatusCode = [HttpStatusCode]::OK

Body = $result

Headers = @{

"Content-Type" = "application/json"

}

})

When executed for testing within Azure Portal using Test/Run, it returns the ID, DisplayName and UserPrincipalName of the newly created Entra ID user account. It also adds the created user to the specified group. The function can also be called from PowerShell with the following approach:

$uri = 'https://func.azurewebsites.net/api/UserProvision?code=KQd1bEB6BlIu2zK-dDLYPNpzh72IGolua8lAzFuEl2A=='

 

$userProperties = @{

displayName = "John Doe"

mailNickname = "johndoe"

userPrincipalName = "[email protected]"

passwordProfile = @{

password = "Pass@word!!"

}

groupName = "Mark 8 Project Team"

}

 

$jsonBody = $userProperties | ConvertTo-Json | ConvertTo-Json

 

$headers = @{

"Content-Type" = "application/json"

}

 

$response = Invoke-RestMethod `

-Uri $uri `

-Method Post `

-Body $jsonBody `

-Headers $headers

 

Write-Output $response

How to execute bulk user attribute management

To perform bulk updates, use the function app already created in the earlier example to generate a few users. The first is the user accounts:

$userAccounts = @(

@{

displayName = "John Doe"

mailNickname = "johnd"

userPrincipalName = "[email protected]"

passwordProfile = @{

password = "Pass@word1!!"

}

groupName = "Mark 8 Project Team"

},

@{

        displayName = "Jane Doe"

        mailNickname = "janed"

        userPrincipalName = "[email protected]"

        passwordProfile = @{

            password = "Pass@word2!!"

        }

        groupName = "Mark 8 Project Team"

    },

    @{

        displayName = "Alex Smith"

        mailNickname = "alexs"

        userPrincipalName = "[email protected]"

        passwordProfile = @{

            password = "Pass@word3!!"

        }

        groupName = "Mark 8 Project Team"

    },

    @{

        displayName = "Maria Garcia"

        mailNickname = "mariag"

        userPrincipalName = "[email protected]"

        passwordProfile = @{

            password = "Pass@word4!!"

        }

        groupName = "Mark 8 Project Team"

    },

    @{

        displayName = "James Johnson"

        mailNickname = "jamesj"

        userPrincipalName = "[email protected]"

        passwordProfile = @{

            password = "Pass@word5!!"

        }

        groupName = "Mark 8 Project Team"

    }

)

The following PowerShell code calls the HTTP trigger:

$uri = 'https://func.azurewebsites.net/api/UserProvision?code=KQd1bEB6BlIu2zK-dDLYPNpzh72IGolua8lAzFuEl2A=='

 

$headers = @{

"Content-Type" = "application/json"

}

 

foreach ($user in $userAccounts) {

$userForFunction = @{

displayName = $user.displayName

mailNickname = $user.mailNickname

userPrincipalName = $user.userPrincipalName

passwordProfile = $user.passwordProfile

}

 

$jsonBody = $userForFunction | ConvertTo-Json

 

try {

$response = Invoke-RestMethod `

-Uri $uri `

-Method Post `

-Headers $headers `

-Body $jsonBody

                Write-Host "Successfully created the user: $($user.displayName)"

     } catch {

           Write-Error "Failed to create the user: $($user.displayName). Error: $_"

     }

}

With the accounts added, create a new HTTP trigger, and use the following code to perform a bulk update of user properties:

using namespace System.Net

param($Request, $TriggerMetadata)

 

# For Testing Only

$clientId = "3dc8e6d6-e7f5-4e75"

$tenantId = "f5dba133-4a2b-40d4"

$clientSecret = "6328Q~em6eORoAMkQs1jFaDq~"

 

$securePassword = ConvertTo-SecureString $clientSecret -AsPlainText -Force

$credential = New-Object System.Management.Automation.PSCredential ($clientId, $securePassword)

 

Connect-MgGraph `

-TenantId $tenantId `

-ClientSecretCredential $credential `

-NoWelcome

 

$usersToUpdate = $Request.Body | ConvertFrom-Json | ConvertFrom-Json

 

foreach ($user in $usersToUpdate) {

    $userId = Get-MgUser `

-Filter "userprincipalname eq '$($user.userPrincipalName)'" | `

Select-Object Id

           Write-Output $userId

 

try {

                Update-MgUser -UserId $userId.Id `

                           -GivenName $user.firstName `

                           -Surname $user.lastName `

                           -JobTitle $user.jobTitle `

                           -Department $user.department `

                           -OfficeLocation $user.office

 

           Write-Output "Successfully updated user: $userId"

    } catch {

           Write-Error "Failed to update user: $userId"

    }

}

 

Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{

    StatusCode = [System.Net.HttpStatusCode]::OK

    Body = $userId

})

This code supports working with either a single account or a nested structure. For example, the following code updates the user attributes of all the accounts created in the earlier example:

$usersToUpdate = @"

[

    {

        "firstName": "John",

        "lastName": "Doe",

        "jobTitle": "Project Manager",

        "department": "Consulting",

        "office": "Chicago",

        "userPrincipalName": "[email protected]"

    },

    {

        "firstName": "Jane",

        "lastName": "Doe",

        "jobTitle": "Managing Director",

        "department": "Consulting",

        "office": "New York",

        "userPrincipalName": "[email protected]"

    }

]

"@

 

$uri = "https://func.azurewebsites.net/api/BulkAttributes?code=KQd1bEB6BlIu2zK-dDLYPNpzh72IGolua8lAzFuEl2A=="

 

$headers = @{

"Content-Type" = "application/json"

}

 

$jsonBody = $usersToUpdate | ConvertTo-Json

 

try {

$response = Invoke-RestMethod `

-Uri $uri `

-Method Post `

-Headers $headers `

-Body $jsonBody

Write-Host "Successfully updated user accounts."

     } catch {

Write-Error "Failed to update user accounts. Error: $_"

     }

}

The code updates the attributes of the users specified in the JSON object with the corresponding values within Entra ID.

Liam Cleary is founder and owner of SharePlicity, a technology consulting company that helps organizations with internal and external collaboration, document and records management, business process automation, automation tool deployment, and security controls and protection. Cleary's areas of expertise include security on the Microsoft 365 and Azure platforms, PowerShell automation and IT administration. Cleary is a Microsoft MVP and a Microsoft Certified Trainer.

Dig Deeper on IT operations and infrastructure management