Finding and Removing Large Quantities of Azure AD Accounts

After covering topics like how to create Azure AD accounts, we should consider the question of how to bulk delete Azure AD accounts at the other end of their lifecycle. Removing a single account isn’t worth discussing because it’s so easy to do through the Microsoft 365 admin center, Microsoft Entra admin center, or the Remove-MgUser cmdlet in the Microsoft Graph PowerShell SDK.

Bulk deletion is more interesting. The Microsoft 365 admin center and Microsoft Entra admin center both support the ability to remove multiple accounts in one operation (Figure 1), and that approach works to remove a relatively small number of accounts. However, once the number of accounts to remove increases, a programmatic approach is more efficient.

Bulk deletion of Azure AD accounts in the Microsoft 365 admin center

Bulk delete Azure AD accounts
Figure 1: Bulk deletion of Azure AD accounts in the Microsoft 365 admin center

You’ll find many articles that solemnly document how to read in the names of accounts for removal from a CSV file before calling a cmdlet from the AzureAD or MSOL modules, both due for deprecation, to remove the accounts. Reading in names and other account information from a CSV file creates an array of accounts for processing. The point I argue here is that more interesting methods exist to find accounts for deletion.

Removing Students at the End of the Academic Year

For instance, take the situation that many educational establishments find themselves in at the end of the academic year. Many student accounts expire when they complete their courses and need to be removed to free up licenses for assignments to incoming students. The first issue is how to identify the relevant accounts.

In another article, I cover how to use filters to find Azure AD accounts with PowerShell. Among the methods is using the fifteen Exchange custom extension attributes to store information intended to locate accounts. For students, you might use custom extension attributes to store the following for students:

  • Course number.
  • Registration number.
  • Registration expiration date.

In this instance, we’ll use a custom extension attribute storing the registration expiration date to find student accounts at the end of the academic year.

The Exchange Online Get-ExoMailbox cmdlet is more flexible when checking against data held in custom attributes. This example shows how to create an array of student accounts that have elapsed. In this case, CustomAttribute1 stores values like 20230601 to represent June 1, 2023. You could store the information in other date formats, but this is convenient for the kind of check we need to do.

[string]$EndAcademicYear = (Get-Date -format yyyyMMdd)
[array]$StudentAccounts = Get-ExoMailbox -Filter "CustomAttribute1 -le '$EndAcademicYear'" -Properties CustomAttribute1
If (!($StudentAccounts)) { Write-Host "No expired student accounts found!" ; break }

Remember that Exchange Online returns information like the user principal name and the account identifier (in the ExternalDirectoryObjectId property) that can be used with the Microsoft Graph PowerShell SDK cmdlets to interact with user accounts.

Removing Temporary Employees

Another example of where bulk account deletion might be necessary is to remove accounts for temporary employees after their contracted period of employment ceases. Azure AD includes some convenient account properties to hold employment information, including employment type.

Unfortunately, the Get-MgUser cmdlet doesn’t support filtering against the EmployeeType property, so we end up doing something like this to fetch user account information and process it to determine if temporary employees exceed their one-year employment period.

[datetime]$ExpirationDate = Get-Date
[array]$Employees = Get-MgUser -filter "userType eq 'Member' and EmployeeId ge ' '" -Property Id, displayname, userprincipalname, employeeid, employeehiredate, employeetype
If ($Employees) {
   $Employees = $Employees | Where-Object {$_.EmployeeType -eq 'Temporary’} 
} Else {
   Write-Host "No employees found" ; break
$AccountsforDeletion = [System.Collections.Generic.List[Object]]::new() 
ForEach ($Employee in $Employees) {
    [datetime]$EmployeeExpirationDate = (Get-Date $Employee.EmployeeHireDate).AddDays(365)
    If ($EmployeeExpirationDate -lt $ExpirationDate) {
       $ReportLine = [PSCustomObject]@{
         EmployeeUserId = $Employee.Id
         EmployeeUPN    = $Employee.UserPrincipalName
         EmployeeId     = $Employee.EmployeeId
         EmployeeName   = $Employee.displayName
         EmployeeDate   = $Employee.EmployeeHireDate
         EmployeeType   = $Employee.EmployeeType
         ExpiryDate     = $EmployeeExpirationDate }  

You get the idea. Before we can bulk delete Azure AD accounts, we need to know the set of accounts to remove and provide that information in a form that PowerShell can process (a list or array). And before any deletions happen, it’s always a good idea for an extra set of eyes to review the set of accounts to make sure that it’s OK to proceed.

Bulk Deleting Azure AD Accounts

Deleting a set of Azure AD accounts is a matter of looping through the set and calling Remove-MgUser to remove each account. The sole prerequisite is that the set must contain a property to allow Azure AD to identify each account. This can be the account’s user principal name or object identifier. In the case of temporary employees, each item in the set looks like this:

EmployeeUserId : 6fd89e40-665a-4efa-9691-da07849cae91
EmployeeUPN    :
EmployeeId     : 111888
EmployeeName   : Rene Artois
EmployeeDate   : 08/03/2022 21:19:10
EmployeeType   : Temporary
ExpiryDate     : 08/03/2023 21:19:10

The code to delete the accounts is very simple:

ForEach ($Account in $AccountsForDeletion) {
   Write-Host ("Deleting account {0}" -f $Account.EmployeeName
   Remove-MgUser -UserId $Account.EmployeeUserId }

Notice that the Remove-MgUser cmdlet doesn’t require confirmation before it deletes an account. Deleted accounts go into the Azure AD account recycle bin and remain there for 30 days. During that time, you can restore an account by running the Restore-MgDirectoryDeletedItem cmdlet (see this article for more information). If you want to permanently remove deleted accounts from Azure AD before their 30-day retention period finishes, you can do that by running the Remove-MgDirectoryDeletedItem cmdlet.

Retaining Data

Accounts with Office 365 E3 licenses can retain mailbox and OneDrive for Business content owned by deleted accounts. The easiest method is to deploy a general retention policy for all accounts to retain content for a short period (say, six months). A separate retention policy can cover accounts with more pressing retention requirements, like executives or those liable for audit. The principles of retention used by Microsoft Purview mean that the longest retention period applies, so data belonging to an account covered by multiple retention policies will remain until the longest retention period lapses. It’s, therefore, quite safe to apply a retention policy with a short retention period as a backstop.

Some experts advocate applying a litigation hold to each mailbox before deleting it’s owning account instead. A litigation (or legal) hold has the same effect as a retention policy on a mailbox but ignores the other personal data held in OneDrive for Business accounts. Overall, I consider a retention policy to be the better option.

Directory Accuracy is Key

It’s easy to bulk delete Azure AD accounts. The key to the operation is being able to accurately identify the target accounts. Azure AD must hold accurate information before filters work. If the Azure AD data is inaccurate, you might as well go back to using a CSV file to drive bulk account deletion operations. Of course, there’s no reason to assume that the contents of a CSV file are accurate, but at least if they’re not, you can blame whoever created the file.

About the Author

Tony Redmond

Tony Redmond has written thousands of articles about Microsoft technology since 1996. He is the lead author for the Office 365 for IT Pros eBook, the only book covering Office 365 that is updated monthly to keep pace with change in the cloud. Apart from contributing to, Tony also writes at to support the development of the eBook. He has been a Microsoft MVP since 2004.


  1. Andy Woodward

    Hi Tony, I’ve always recommending applying litigation-hold (LH) even though retention policies may also be in use. This is because LH also preserved mailbox items that may have been manipulated, i.e. individual email messages whereby a user has intentionally ‘edited’ a received (or sent) email to change the content. With LH enabled on the mailbox, a copy of the original as well as amended item is preserved. If just a retention policy is enabled then only the amended item is retained, not the original. I discovered this in the course of my role when running through some advanced support with Microsoft. I’ve no reason to believe this functionality has changed, so I find it a good idea to apply LH to mailboxes with licenses that support this functionality from as soon the time the mailbox is provisioned.

    1. Avatar photo
      Tony Redmond

      Hmmm… I accept what you say, but I assumed that retention did what LH does in terms of the original message… I need to talk to someone in the compliance group.

    2. Avatar photo
      Tony Redmond

      From Microsoft (head of retention development).

      Hey Tony, this is incorrect, retention policies does exactly the same preservation in case of an edited email.

      We do not recommend use of litigation hold when preserving content, it is a blunt instrument that will essentially not let anything move from the mailbox whether it’s interesting to the retention/hold or not (so system data, etc.), this is a problem as customers end up with quota issues, etc.

Leave a Reply