PS >cloudkreise

Entra ID Pending Guests LifeCycle with Azure Runbook


Guest accounts in Entra ID are a bit of a double edged sword. They are perfect to invite external users in your tenant to work together in projects, keep collaborating easy and with the External Identities Cross Tenant Sync Feature there is little to do to keep your place clean, if the foreign tenant has a lifecycle management in place.

As always, where there is light, there is also shadow. What do you do with all the other guests after a project endet or the given access is not needed anymore. A whole bunch of topics regarding lifecycle management (e.g. Azure Access Reviews, Azure Identity Governance, …) which I want to cover in the future as some of them require additional funding in licensing.

For now we just want to clean our Entra ID from all that guests, that doesn’t even accepted their invitation weeks or even months ago. These accounts are still in our very tenant and can maybe in the future be part of a security incident.

For that matter I build a Azure Runbook with an System Managed Identity and assigned just the needed API Permissions. I didn’t want to assign a existing role like the User Administrator, because these are way too big for that needed purpose.

Nonetheless sadly there is no slim Graph API Permission to just scope the permission to guest accounts like e.g. the existing “Guest Inviter”-Role (95e79109-95c0-4d8e-aee3-d01accf2d47b) as of now. How about a “Guest Bouncer”-Role Microsoft? 😄

Creating a Automation Account

Create an automation account in your Azure environment. I would recommend, if not still present, a dedicated resource group for tasks like this and not use a shared one. In this case I will do it with the Azure Cloud Shell using the az-Modules.

# change the active subscription using the subscription ID
az account set --subscription "your-subscription-id"

# Creating new resource group
az group create --name rg-EntraIDGuestLifeCycle --location germanywestcentral

az automation account create --name "aa-EntraIDGuestLifeCycle" -l "germanywestcentral" --sku "free" --resource-group "rg-EntraIDGuestLifeCycle"

Activate the system assigned identity in the azure portal under that new automation account in Account Settings -> Identity and save the change.

Adding the needed Modules

We need the following two Modules for our purpose. I chose the latest versions from the gallery and used PowerShell 7.2 to run the code. To add the Modules to your Automation Account click on Modules in the Azure Portal and add the needed ones from the gallery, namely Microsoft.Graph.Authentication and Microsoft.Graph.Users.

Granting the needed permissions

To grant the new system assigned managed identity the necessary permissions to read and delete users, we have to assign the “User.ReadWrite.All” Graph API permission. We cannot do this in the azure portal yet. We can again just use the Azure Cloud Shell.

$SAMI = Get-MgServicePrincipal -Filter "displayname eq 'aa-EntraIDGuestLifecycle'"

$GraphAPI = Get-MgServicePrincipal -Filter "displayname eq 'Microsoft Graph'"

$Role = $GraphAPI.AppRoles | Where-Object {$_.value -eq "User.ReadWrite.All"} | select *

AllowedMemberTypes   : {Application}
Description          : Allows the app to read and update user profiles without a signed in user.
DisplayName          : Read and write all users' full profiles
Id                   : 741f803b-c850-494e-b5df-cde7c675a1ca
IsEnabled            : True
Origin               : Application
Value                : User.ReadWrite.All
AdditionalProperties : {[isPreAuthorizationRequired, False], [isPrivate, False]}

New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $SAMI.Id -PrincipalId $SAMI.Id -AppRoleId $Role.Id -ResourceId $GraphAPI.Id

Creating the Runbook

Now we have to create a new Runbook in our Automation Account. Click on Create a runbook, choose a name, Runbook Type is PowerShell and your desired PowerShell Version (mine is 7.2) and add a description. Enter the newly created runbook and navigate to the code editor (e.g. Edit -> Edit in portal) and paste the following code into it.

# Connect to the PowerShell Graph SDK with the acquired access token
Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.Users
Connect-Graph -Identity

# Get all guests in Entra ID with filter for UserType and ExternalUserState
$Guests = Get-MgUser -Filter {UserType eq 'Guest' and ExternalUserState eq 'PendingAcceptance' } -ConsistencyLevel eventual -all
# Filter for passed time since invite.
$PendingGuests = $Guests | Where-Object { $_.CreatedDateTime -lt $(Get-Date).AddDays(-60) }

# Show how many stale guests users will be deleted
Write-Output "There are $($PendingGuests.count) pending Guests older than 60 days."

# For Each Loop do delete every Guest that suits the criteria.
$PendingGuests | %{
    # Removing Pending Guest User
    Write-Output "Removing Guest User $($_.DisplayName) with Pending Acceptance since more than 60 days."
    Remove-MgUser -UserId $_.Id

To Dry-Run the Script you can put a hashtag in line 18, save it and start the job. After a little while you should see the output of the script and the amount of guests in would delete.

Running the Runbook

If everything looks good you can save and publish the Azure Runbook and start without the hashtag in line 18. The Output will looks similar to this.

In the first run we removed more than 600 guest accounts that never accepted their invitation and now the job runs on a regular basis to keep the amount of outstanding guests small.

To schedule the execution of that Azure Runbook you can create and link it to an Schedule within the automation account. I decided I want to run it every monday at 10:00 CET.

You can see the activity of the Azure Runbook in the Entra ID Audit Log. Simply filter to tie Managed Identity as the initiator.


What we do cover with that solution?

  • We automatically delete guest accounts that were invited to our Entra ID tenant but never accepted their invitation.
  • We did that with only limited permissions and with a system assigned managed identity.
  • The operations are logged in Entra ID Audit Log and in the Output of every Azure Runbook Job.

What we don’t cover with it?

  • We do not review the existing guest accounts, if the access to our Entra ID Tenant is still needed.
  • We do not review the permissions and access levels of the existing accounts.