Discover Who Creates Guest Accounts in Office 365 Applications

So Many Guest Accounts but No Idea How They Are Created

At a recent conference, a discussion took place about the number of guest user accounts now being created by applications in the directories of Office 365 tenants. The accounts are created through Azure B2B collaboration (by applications that generate invitations to join Office 365 Groups, like Teams and Planner) or SharePoint sharing invitations for documents or folders. It’s one thing to invite people outside your tenant to collaborate; it’s a horse of a different color to manage the resulting guest accounts.

No Control Over Who Creates Guests

In this case, the focus was on knowing who took the actions that led to Office 365 creating guest accounts in the tenant. The point was firmly made that there is no out-of-the-box way for a tenant administrator to know what guests are being invited. Some level of control is available with the Azure B2B Collaboration policy, which allows tenants to blacklist domains they won’t accept guests from (or a whitelist to restrict the set of domains guests can come from), but once a guest email address complies with the policy, they can be invited.

Admin Consoles and Guests

It is possible for tenant administrators to go to the Guest Users section of the Office 365 Admin Center and review the set of guests shown there. This is easy with 20 or so guests, more difficult with 100, and impossible thereafter. The coverage for Guest Users in the Office 365 admin center is bare-boned and doesn’t even allow you to download the set of guests to make it easier to analyze where guests come from and so on.

The Azure Active Directory portal does includes the option to download details of guest users (in preview) to a CSV file, a process that takes much longer than expected. A couple of lines of PowerShell can create a CSV file with details of guest accounts much faster! But at least the option exists so we should be grateful for small mercies.

Azure Active Directory Portal Guest Users
Figure 1: Azure Active Directory portal lists guest users (image credit: Tony Redmond)

The Answer Lies in the Audit Log

Office 365 won’t tell you who did what to create a new guest account, but the answer is available if you go looking in the Office 365 audit log. The audit log holds a lot of information gathered from many different parts of Office 365. The trick in finding what we’re looking for is to focus in on the actions people take that result in a new guest account being created.

The first action we need to look for is when a guest is added to an Office 365 group in an application like Outlook, Teams, Planner, or Yammer. When this happens, Azure B2B collaboration creates and sends an invitation to the guest to notify them that they have been granted access to an application. If the guest account doesn’t already exist in Azure Active Directory, it is created.

Tracking Add to Group Events

An “Add User” event is captured when a new Azure Active Directory account is created. This event tells us the name of the guest user and who created them, but in this case, I want to do more because I want to know what Office 365 Group the new guest joined. Fortunately, the ‘Add member to group” event is available, and when we dig into the AuditData JSON-formatted payload containing details of the action, we can find the name of the group the guest joined.

The only other complication is deciding whether the guest is a new or existing account. My solution was to check the RefreshTokensValidFromDateTime attribute of the guest account because if it holds the same timestamp as when the user joined the group, it’s reasonable to assume that the account was created at the time to add to the group. The Azure AD PowerShell module doesn’t report a WhenCreated value for accounts. Using the refresh token time is a reasonable compromise as the attribute seems to hold the creation timestamp for guest accounts.

$EndDate = (Get-Date).AddDays(1); $StartDate = (Get-Date).AddDays(-90); $NewGuests = 0
$Records = (Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -Operations "Add Member to Group" -ResultSize 2000 -Formatted)
If ($Records.Count -eq 0) {
   Write-Host "No Group Add Member records found." }
 Else {
   Write-Host "Processing" $Records.Count "audit records..."
   $Report = [System.Collections.Generic.List[Object]]::new()
   ForEach ($Rec in $Records) {
      $AuditData = ConvertFrom-Json $Rec.Auditdata
      # Only process the additions of guest users to groups
      If ($AuditData.ObjectId -Like "*#EXT#*") {
         $TimeStamp = Get-Date $Rec.CreationDate -format g
         # Try and find the timestamp when the Guest account was created in AAD
         Try {$AADCheck = (Get-Date(Get-AzureADUser -ObjectId $AuditData.ObjectId).RefreshTokensValidFromDateTime -format g) }
           Catch {Write-Host "Azure Active Directory record for" $AuditData.ObjectId "no longer exists" }
         If ($TimeStamp -eq $AADCheck) { # It's a new record, so let's write it out
            $NewGuests++
            $ReportLine = [PSCustomObject]@{
              TimeStamp   = $TimeStamp
              User        = $AuditData.UserId
              Action      = $AuditData.Operation
              GroupName   = $AuditData.modifiedproperties.newvalue[1]
              Guest       = $AuditData.ObjectId }      
           $Report.Add($ReportLine) }}
      }}
Write-Host $NewGuests "new guest records found..."
$Report | Sort GroupName, Timestamp | Get-Unique -AsString | Format-Table Timestamp, Groupname, Guest

Running the script gives me a nice output showing the names of guest users added to each group. The report also includes details of who added the guest in the User property (not shown in Figure 2).

Reporting Guest Users Added to Groups
Figure 2: Guests joining Office 365 Groups (image credit: Tony Redmond)

The output shown in Figure 2 is from a relatively small tenant. In larger tenants, it’s probably better to export the data to a CSV file and analyze it with Excel or in Power BI. This is easily done by adding code like:

$Report | Export-CSV -NoTypeInformation c:\temp\GuestsAddedByGroupOwners.csv

Tracking SharePoint Sharing

SharePoint Online and OneDrive for Business have embraced Azure B2B Collaboration as the preferred mechanism for sharing documents and folders. The result is that Office 365 tenants will see guest accounts created when users share with people outside the tenant.

Once again, the Office 365 audit log helps us understand how and when guest accounts are created. In this instance, we need to look for events captured when sharing happens externally. After perusing the audit log and comparing captured events against known activities, the SharingInvitationCreated event proved the most useful. This event occurs when a user creates a new sharing invitation, so we simply find the events, filter the ones associated with guest accounts, and check if each account found is new. Here’s the code:

$EndDate = (Get-Date).AddDays(1); $StartDate = (Get-Date).AddDays(-90); $NewGuests = 0
$Records = (Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -Operations "SharingInvitationCreated" -ResultSize 2000 -Formatted)
If ($Records.Count -eq 0) {
   Write-Host "No Sharing Invitations records found." }
 Else {
   Write-Host "Processing" $Records.Count "audit records..."
   $Report = @()
   ForEach ($Rec in $Records) {
      $AuditData = ConvertFrom-Json $Rec.Auditdata
      # Only process the additions of guest users to groups
      If ($AuditData.TargetUserOrGroupName -Like "*#EXT#*") {
         $TimeStamp = Get-Date $Rec.CreationDate -format g
         # Try and find the timestamp when the invitation for the Guest user account was accepted from AAD object
         Try {$AADCheck = (Get-Date(Get-AzureADUser -ObjectId $AuditData.TargetUserOrGroupName).RefreshTokensValidFromDateTime -format g) }
           Catch {Write-Host "Azure Active Directory record for" $AuditData.UserId "no longer exists" }
          If ($TimeStamp -eq $AADCheck) { # It's a new record, so let's write it out 
            $NewGuests++
            $ReportLine = [PSCustomObject][Ordered]@{
              TimeStamp    = $TimeStamp
              InvitingUser = $AuditData.UserId
              Action       = $AuditData.Operation
              URL          = $AuditData.ObjectId
              Site         = $AuditData.SiteUrl
              Document     = $AuditData.SourceFileName
              Guest        = $AuditData.TargetUserOrGroupName }      
           $Report += $ReportLine }}
      }}
$Report | Format-Table TimeStamp, Guest, Document -AutoSize

The script generates a hash table holding the user principal names of guest accounts, when they were created, and details of the documents shared in the invitations (Figure 3). The InvitingUser property (not shown in Figure 3) holds the user principal name of the user who extended the sharing invitation, making it easy to find out the prolific sharers in a tenant.

Reporting Guest Users Creating for Sharing
Figure 3: Listing sharing invitations generated for guests (image credit: Tony Redmond)

Office 365 Audit Log is a Goldmine

There’s no doubt that the Office 365 audit log holds a lot of very useful information that can be mined to answer questions like why have guest accounts appeared in my tenant. It’s good that the audit log exists; it’s not so good that so little use is made of the data. Hopefully these examples help you understand the kind of information that’s in the log and creates some inspiration to exploit the data for your own purposes.