Learn What IT Pros Need to Know About Windows 11 - August 24th at 1 PM ET! Learn What IT Pros Need to Know About Windows 11 - August 24th at 1 PM ET!
Azure Active Directory|Microsoft Teams|Office|Office 365

Identifying Obsolete Guest User Accounts in an Office 365 Tenant

The Need to Remove Lingering Guests

After writing an article about discovering who creates guest accounts inside Office 365 tenants, a reader asked how they should clean up old guest accounts that might be lingering in a state of disuse. Microsoft’s response might be to push responsibility for guest accounts down to group owners through Azure Active Directory access reviews or Entitlement Management. Both are potential solutions to the problem, but neither gives a tenant-wide view of just how many old guest accounts are out there.

The Age-Based Approach

I covered this topic recently on Office365itpros.com where I discuss a PowerShell script to look for guest accounts older than a set age and consider them for removal, especially if the accounts are not members of Office 365 Groups. Age-based reviews are good, and the script is pretty simple, but so’s the outcome.

The problem with age-based removal is that a guest account might be in your tenant for years and appear dormant when it’s in heavy use. Take the example of a guest member of an Outlook group. Unlike Teams, where guests connect to channels to participate in conversations, the conversations in an Outlook group are email-based and guests never need to sign in. Instead, they receive copies of topics and replies via email.

Activity-Based Reviews are Better

As the engineering team behind the Azure Active Directory group expiration policy discovered, making a decision to remove a group based on activity is better than removing it just because it’s reached a certain age. The same is true for guest accounts. If they’re active, leave them alone. If they’re not, remove them.

Sponsored Content

Read the Best Personal and Business Tech without Ads

Staying updated on what is happening in the technology sector is important to your career and your personal life but ads can make reading news, distracting. With Thurrott Premium, you can enjoy the best coverage in tech without the annoying ads.

The question then becomes how to figure out if a guest account is active. Fortunately, the Office 365 audit log is a rich source of information to interrogate for actions taken by guests within the tenant while the message trace data gathered by Exchange Online can tell us about their email activity.

Checking Activity in PowerShell

Now that we know where to get the data, we can write some PowerShell to find out if our tenant hosts any obsolete guests. The script below uses cmdlets in the Exchange Online and Azure Active Directory modules to do the following:

  • Find all guest accounts with the Get-AzureADUser
  • For each user, runs the Search-UnifiedAuditLog cmdlet to check what recent activity can be found. The script looks for three operations, but you could add more. We’re happy if we find one of the selected set over the last 90 days.
  • Runs the Get-MessageTrace cmdlet to look for any recent email activity. We can only go back 10 days because that’s what Office 365 keeps online.
  • Capture usage data in an array.
  • After processing all the guests, writes the usage data out into a CSV file.

# Script to find guest accounts that are inactive
$Guests = (Get-AzureADUser -Filter "UserType eq 'Guest'" -All $True| Select Displayname, UserPrincipalName, Mail, RefreshTokensValidFromDateTime)
Write-Host $Guests.Count "guest accounts found. Checking their recent activity..."
$StartDate = (Get-Date).AddDays(-90) #For audit log
$StartDate2 = (Get-Date).AddDays(-10) #For message trace
$EndDate = (Get-Date); $Active = 0; $EmailActive = 0; $Inactive = 0; $AuditRec = 0
$Report = [System.Collections.Generic.List[Object]]::new() # Create output file for report
ForEach ($G in $Guests) {
    Write-Host "Checking" $G.DisplayName  
    $LastAuditAction = $Null; $LastAuditRecord = $Null
    # Search for audit records for this user
    $Recs = (Search-UnifiedAuditLog -UserIds $G.Mail, $G.UserPrincipalName -Operations UserLoggedIn, SecureLinkUsed, TeamsSessionStarted -StartDate $StartDate -EndDate $EndDate -ResultSize 1)
    If ($Recs.CreationDate -ne $Null) { # We found some audit logs
       $LastAuditRecord = $Recs[0].CreationDate; $LastAuditAction = $Recs[0].Operations; $AuditRec++
       Write-Host "Last audit record for" $G.DisplayName "on" $LastAuditRecord "for" $LastAuditAction -Foregroundcolor Green }
    Else { Write-Host "No audit records found in the last 90 days for" $G.DisplayName "; account created on" $G.RefreshTokensValidFromDateTime -Foregroundcolor Red } 
    # Check message trace data because guests might receive email through membership of Outlook Groups. Email address must be valid for the check to work
    If ($G.Mail -ne $Null) {
       $EmailRecs = (Get-MessageTrace –StartDate $StartDate2 –EndDate $EndDate -Recipient $G.Mail)            
       If ($EmailRecs.Count -gt 0) {
           Write-Host "Email traffic found for" $G.DisplayName "at" $EmailRecs[0].Received -foregroundcolor Yellow
           $EmailActive++ }}
     # Write out report line     
     $ReportLine = [PSCustomObject]@{ 
          Guest            = $G.Mail
          Name             = $G.DisplayName
          Created          = $G.RefreshTokensValidFromDateTime 
          EmailCount       = $EmailRecs.Count
          LastConnectOn    = $LastAuditRecord
          LastConnect      = $LastAuditAction} 
       $Report.Add($ReportLine)  }          
$Active = $AuditRec + $EmailActive
$Report | Export-CSV -NoTypeInformation c:\temp\GuestActivity.csv      
Write-Host ""
Write-Host "Statistics"
Write-Host "----------"
Write-Host "Guest Accounts          " $Guests.Count
Write-Host "Active Guests           " $Active
Write-Host "Audit Record foun       " $AuditRec
Write-Host "Active on Email         " $EmailActive
Write-Host "InActive Guests         " ($Guests.Count - $Active)

Figure 1 shows what you can expect to see as the script runs. I’ve obscured the names of guests in my tenant.

Figure 1: Examining the activity of guest accounts with PowerShell (image credit: Tony Redmond)

The normal caveat of needing to check any script carefully before running code in a production environment exists. Also, there’s not much error checking in the code, which is designed to illustrate a point rather than being a production-quality script.

Another Gap Bridged

You can’t expect Microsoft to deliver every possible feature in the Office 365 administrative interfaces. In this case, a gap in thinking about how the guest lifecycle could be managed exists between the folks who think about guest accounts (the Azure Active Directory developers) and those who consume the accounts (Office 365). Thankfully, PowerShell can bridge the gap.



Don't have a login but want to join the conversation? Sign up for a Petri Account

Comments (2)

2 responses to “Identifying Obsolete Guest User Accounts in an Office 365 Tenant”

  1. davecober

    This is a good starting point, but I don't think it's quite accurate. I have at least one guest user that shows no activity using this script, but shows up when I search the audit log via the GUI.

  2. jonmiller724

    Can you show me how to add in the username / display name of the person who created the guest account?

Leave a Reply

Tony Redmond has written thousands of articles about Microsoft technology since 1996. He covers Office 365 and associated technologies for Petri.com and is also the lead author for the Office 365 for IT Pros eBook, updated monthly to keep pace with change in the cloud.

Register for Advanced Microsoft 365 Day!

GET-IT: Advanced Microsoft 365 1-Day Virtual Conference - Live August 24th!

Join us on Tuesday, August 24th and hear from Microsoft MVPs and industry experts about how to take advantage of Microsoft 365 at a technical level and dive deep into the features and functionality that will make your environment more secure and compliant.


Sponsored By