Over time, Tenant Directories Become Cluttered with Guest Account Crud

It’s about two thousand days since the first guest account made its appearance in my tenant. At the time, I was preparing for an Ignite 2016 session on Office 365 Groups, and the advent of guest access was big news. Microsoft enabled the feature for the tenant to allow testing to proceed and I created the first guest account on 31 July 2016. Two thousand days later, that account is still in active use.

Since 2016, guest account usage within Microsoft 365 tenants is expanded greatly. My view is that two obvious reasons drive the growth:

The original use of guest accounts to allow external users to become members of Outlook-based Microsoft 365 groups is still valid. However, at this point, Teams is the predominant factor driving the creation of guest accounts. The number of guest accounts created for Teams access will probably moderate when Microsoft launches shared channels (due any day now), but it won’t do anything to control existing guest accounts.

Basic Guest Account Management

Since 2016, the functionality available to manage guest accounts has not evolved as I thought it might. The Microsoft 365 admin center Users section includes basic account management for guests (Figure 1). You can add a guest account, update the properties of a guest account (but not the account’s photo thumbnail), their group memberships, and delete a guest account.

Guest account management in the Microsoft 365 admin center
Figure 1: Guest account management in the Microsoft 365 admin center

Managing small numbers of guest accounts through the Microsoft 365 admin center is acceptable. It’s a good idea to review the accounts periodically and update missing properties to make the information easier to manage. Some organizations include the home organization for a guest account in its display name. Others update the mail user object for mail-enabled guest accounts with details of the current account status.

In terms of more advanced management, Microsoft’s attention is on Entra ID Account Reviews, which a tenant can use to force “sponsors” (usually the owners of a group or team) to validate that guest members should retain their membership for a further period. Account reviews automate checking of guest accounts, but only if you have Entra ID Premium P2 licenses.

Why Guest Accounts Go Bad

Many reasons exist why a guest account is perfectly good in March but useless in December. For example:

  • The guest account is used once to review a shared document and is not needed thereafter.
  • External people leave a team (or teams) and their guest account remains in Entra ID.
  • People leave an employer and move on to new challenges. Their old guest account is invalid because they can no longer authenticate using the Entra ID instance for the tenant of their old employer.
  • Projects come to a natural end and the associated teams and/or private channels are no longer needed.

Individual users can manage their membership in host tenants by removing guest accounts through the My Organizations page. A recent update to Teams makes it easier for users to manage the set of tenants where they have guest accounts. However, experience shows that users are not good at cleaning up guest accounts unless they are coached and prompted to do so. Depending on guest users to remove their accounts from your tenant is not a good strategy for cleaning up Entra ID.

Analyzing Guest Accounts

Any tenant that makes heavy use of Teams and SharePoint sharing is likely to accumulate many guest accounts over time. Automation detection of possibly inactive or unwanted accounts rather than individual review is a better and more effective approach. Using the PowerShell modules available for Microsoft 365 Groups (Exchange Online), Entra ID, and Teams, we can explore several options:

  • Create a report of user membership in Microsoft 365 Groups. This method tells you what groups (teams) a guest account belongs to, but it won’t process guest accounts created for SharePoint sharing.
  • Look for guest accounts older than a certain age (for instance, more than a year old) and check if they are a member of any groups. Old guest accounts which are not a member of any groups are likely to be good candidates for removal.
  • Check the activity of every guest in the tenant to see if audit records, Entra ID sign-in logs, or Exchange message trace data exists to show that their accounts are still active. An account that hasn’t recently signed in, received email, or performed another action like accessing a document using a sharing link, could be a good candidate for removal.

I wrote a script to analyze guest account activity some years ago and have revised it to improve how it works (you can download the script from GitHub). The script generates a report of guest user activity to help tenant administrators understand just what accounts are active and those which are not (Figure 2). Although it’s not the fastest script in the world, the information it produces is valuable.

Reviewing data about guest user accounts
Figure 2: Reviewing data about guest user accounts

In addition to information about individual guest accounts, the script generates some basic statistics about the 175 guest accounts in my tenant, including the percentage of inactive guests, the number of domains guests come from, and the domain with the largest set of guests. Unsurprisingly, I have many contacts with people in Microsoft.

Guest Accounts            175
Active Guests             85
Audit Record found        18
Active on Email           67
InActive Guests           90
Percent inactive guests   51.43%
Number of guest domains   79
Domain with most guests   microsoft.com (71)

Guests found from domains  24x7itconnection.com, ableblue.com, activedir.org, andreestr.de, avantgardetechnologies.com.au, avepoint.com, babelmate.com, briandesmond.com, bricomp.com, c7solutions.com, capgemini.com, cgoosen.com, cliptraining.com, cloud.my, cloudway.no, cmportalsolutions.com, cobweb.com, coligo.se, contoso.com, datarumble.com, delimon.be, dominikhoefling.com, eastman.com, eightwone.com, emea.teams.ms, emptycubicle.com, exchangeguy.com, expertsinside.com, faqexchange.info, gavd.net, geeyouen.com, gmail.com, granikos.eu, gsoft.com, hbsoft.de, hotmail.com, inspark.nl, itechcs.onmicrosoft.com, izzy.org, jagott-it.de, keepit.com, klindt.org, mailmaster.se, mcsmlab.com, michev.info, microsoft.com, mikecrowley.us, modalitysystems.com, msdigest.net, msexchange.fr, msgdevelop.com, nbconsult.co.za, nudgeit.com, o365maestro.onmicrosoft.com, outlook.com, pacbell.net, quadrotech-it.com, quality-training.co.uk, quest.com, ravenswoodtechnology.com, robert-wille.de, robichaux.net, sas.com, scolesfamily.net, sembee.co.uk, skrutten.nu, smithcons.com, stalpaert.nl, stevieg.org, supertekboy.com, systemplus.gr, thecluelessguy.de, thecollective.eu, theguillets.com, vanhorenbeeck.be, voitanos.io, wesselius.info, yandex.com, yshvili.com

The output file containing detailed results is in c:\temp\GuestActivity.csv
A CSV file containing the User Principal Names of inactive guest accounts is in c:\InactiveGuests.csv

Of course, because the code is PowerShell, you can amend it to address the business requirements of your organization.

Removing Inactive Guest Accounts

The script also generates a CSV file containing details of inactive guest accounts. The intention is that administrators should review this data to decide which guest accounts should be deleted. After editing the CSV file to remove guest accounts to keep, the file can be an input to some simple PowerShell clean-up code. This version reads in the set of accounts from the CSV file, prompts for confirmation, and if given, calls the Remove-AzureADUser cmdlet to remove each guest account.

[array]$AccountsToDelete = Import-CSV c:\temp\InactiveGuests.CSV
$OKtoProceed = Read-Host "OK to go ahead to delete" $AccountsToDelete.Count "inactive guest accounts"
If ($OKtoProceed -eq "Y") {
   Write-Host "Deleting accounts..."
   ForEach ($Account in $AccountsToDelete) {
     Write-Host ("Removing account for {0} belonging to {1}" -f $Account.UPN, $Account.Name)
     Remove-AzureADUser -Object $Account.ObjectId } # End Foreach account
} # End if

If you make a mistake and delete a guest account in error, remember that you can recover deleted user accounts for up to 30 days through the Deleted Users section of the Microsoft 365 admin center. My annual reviews commonly remove 50% of the guest accounts identified as inactive. I keep the others because I think they might be used in future.

No Automatic Maintenance for Guest Accounts

Even if you use Entra ID Account Reviews, some manual effort is needed to ensure that the set of guest accounts in a tenant doesn’t continually accumulate and become unmanageable. You can adopt a “who cares” attitude because obsolete guest accounts don’t do much if any harm to a tenant and more important tasks exist to occupy time and effort. I can understand the argument on this basis. My simple rebuttal is that clogging up the directory with obsolete guest accounts will slow down PowerShell and Graph API queries. Cleaning out inactive guests annually seems like a good idea.

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 Practical365.com, Tony also writes at Office365itpros.com to support the development of the eBook. He has been a Microsoft MVP since 2004.


  1. Costin

    Hello Tony,

    i got the following error

    FullyQualifiedErrorId : Request_BadRequest,Microsoft.Graph.PowerShell.Cmdlets.UpdateMgUser_UpdateExpanded
    Update-MgUser : Unable to update the specified properties for objects that have originated within an external service.
    Status: 400 (BadRequest)
    ErrorCode: Request_BadRequest
    Date: 2024-04-29T10:21:11
    Transfer-Encoding : chunked
    Vary : Accept-Encoding
    Strict-Transport-Security : max-age=31536000
    what is trying to update on the object?

  2. Melvin

    HI @Tony, script is not available on the gitup page anymore using the link available on this page.

  3. Zied

    Thank you sir for your script

    Im facing a problem of latency when i excecute the script and also there are some limitations of azure ad : sign in and audit logs are available for 30 days only : getting blank cells in the csv reports

    The main problem is when i execute the script to audit 3000 guest user , it take a lot of time without results and errors like : error reading jtoken ..

  4. Tech13568

    What would cause all of the guest accounts to have the Inactve flag show up as False, yet they have 0 email count, no -sign-ins, no audit record, no last audit action and no group membership, while some accounts do reflect correct information?

    1. Avatar photo
      Tony Redmond

      No idea. I can’t see your data. There might not be sign in information available if the guest haven’t signed in recently.

      Select a guest account that you don’t see data for and try running the code to see what comes back.

  5. Alan Birch

    Maybe you could add a – If not exist C:\temp then create it – line in the code. I ran the script and it took 2 hours but there was no output. I created the C:\temp folder manually then re-ran just the 2 output lines to get my csv files, but I was a little disheartened for a while. Great result in the end though. Thanks.

  6. Benni

    Hi Tony,
    thank you very much for that script! Just tried it out, I got three of 74 guests where last sign-in’s output is “no recent sign in records found” but in Azure AD GUI it indeed says logged in a few days ago.
    Main reason why I am writing: What is this EMailCount about? Which mails are counted here? I did not get that I must confess.
    Best regards

    1. Avatar photo
      Tony Redmond

      The script depends on a query against the Azure AD sign-in logs to return the last sign in date. I don’t know why the Azure AD admin center would show a different value. But anyway, the intention of the script is to give admins more information about guest accounts. It’s then up to the admin to decide how to use the data.

      As to Email count, it’s the count of messages received by a guest account in the last 7 days.

  7. Victor

    Hi Tony,
    Great article. I just got this assignment for a tenant I work with. The script seems to work just fine, but it’s a big tenant and a cleanup has never been done as far as I can tell, so the guest accounts are almost up to 9000.. Is there a way to speed up this process of checking? Takes about 1-2 minutes per user right now.


    1. Avatar photo
      Tony Redmond

      One way you could speed things up is to:

      Scan Azure AD for guest accounts.
      Write out different sections of the guest accounts to CSV files. For example, guests A-C in one file, D-F in another, and so on.
      Process each file in a separate PowerShell session (or maybe submitted as runbooks to Azure Automation)
      Combine the results of each run in an overall report

      It would be very crude parallel processing…

      The reason why the script is slow is that it does a lot of work to validate if a guest account is active. Think of how long it would take for a human to check, and then be happy that automation is available…

      1. Victor

        Thanks alot for your reply, I will take this under consideration.

        Have a wonderful day

  8. Brenkster

    You could add this to the top of the script:

    1. Avatar photo
      Tony Redmond

      The check at the top of the script now says:

      $ModulesLoaded = Get-Module | Select Name
      If (!($ModulesLoaded -match “ExchangeOnlineManagement”)) {Write-Host “Please connect to the Exchange Online Management module and then restart the script”; break}
      If (!($ModulesLoaded -match “AzureADPreview”)) {Write-Host “Please connect to the Azure AD module and then restart the script”; break}
      # OK, we seem to be fully connected to Exchange Online and Azure AD

      Of course, this is PowerShell, so you can do what you want to load modules etc.

  9. BA

    I get an error on every account when running this script, it say Get-AzureADAuditSignInLogs is not recognized. I have installed the latest Azure AD module with the Install-Module AzureAD -Force command. I run Connect-ExchangeOnline and Connect-AzureAD before running the script. Any advice?

    1. Avatar photo
      Tony Redmond

      You need AzureADPreview to use the Get-AzureADAuditSignInLogs cmdlet.

      Cmdlet Get-AzureADAuditSignInLogs AzureADPreview

      I’ve updated the script in GitHub to make this clear.

      1. BA

        Thanks for the quick reply! That did the trick 🙂 This script will be very helpful. I took over an Azure environment that has never had any cleanup done. Over 3,200 guest accounts!

  10. Dinesh Silva

    Great article, always appreciate your support to the community.

Leave a Reply