Automate Microsoft Entra User Offboarding with Microsoft Graph API and PowerShell

Key Takeaways

  • Automate Microsoft Entra User Offboarding with Microsoft Graph API and PowerShell
  • Proper user offboarding is essential to maintain security, compliance, and effective resource management
  • Automated offboarding ensures that former employees and test accounts no longer have access to corporate data
  • Manual offboarding processes are time-consuming and prone to human error
  • PowerShell automation helps standardise and speed up the offboarding workflow
  • Microsoft Graph API enables centralised management of Microsoft 365 and Entra ID resources

Automating the removal of Microsoft Entra users is a critical yet often complex responsibility for IT teams. Offboarding goes far beyond simply disabling an account – it requires a careful, methodical approach to revoke access across multiple services. This includes removing licenses, relinquishing assigned roles, and reassigning application ownership to ensure that no dependencies or responsibilities are left unmanaged.

Without a structured process, organisations face significant risks. Orphaned resources, lingering permissions, or overlooked entitlements can create vulnerabilities that compromise both security and compliance. These gaps not only expose sensitive data but also increase administrative overhead when unmanaged accounts or assets surface later. To mitigate these risks, automation becomes a necessity rather than a convenience.

By harnessing the capabilities of Microsoft Graph API and PowerShell, IT teams can design workflows that streamline user offboarding. Automation enforces consistency, reduces human error, and ensures that every step of deprovisioning is executed reliably and consistently. This approach transforms what was once a manual, error-prone process into a predictable and repeatable system that scales with organisational needs.

Ultimately, a well‑designed automated workflow delivers more than efficiency – it strengthens the overall security posture of the organisation. Departing users are fully deprovisioned, all access is revoked, and resources are safeguarded in a manner that is both reliable and auditable. In doing so, IT teams not only save valuable time but also reinforce trust in their systems, ensuring that offboarding is handled with precision and confidence.

Patch My PC
Table of Content

Strengthening Security Through Automated User Offboarding in Microsoft Entra

Microsoft Entra is Microsoft’s suite of identity and network access solutions, built to secure access to apps, resources, and services across cloud and on‑premises environments. It delivers key capabilities, such as single sign-on, multi-factor authentication, and Zero Trust security, for employees, customers, and partners.

User offboarding is one of the most critical yet often overlooked aspects of identity and access management. When employees, contractors, or partners leave an organisation, their accounts must be carefully deprovisioned to prevent lingering access. Simply disabling a user is not enough; licenses, roles, and application ownership must be reassigned to ensure no orphaned resources remain. Without a structured offboarding process, organisations risk exposing sensitive data, leaving behind unused assets, or creating compliance gaps.

These oversights can lead to costly breaches and undermine trust in IT systems. By treating offboarding as a security priority rather than an administrative task, organisations can close potential vulnerabilities before they are exploited.

Permissions Required for User Offboarding in Microsoft Entra

You need certain permissions to offboard users in Entra. There are two types of API permissions: Application permissions and Delegated permissions. The Application permissions run without a signed-in user present and require admin consent, and Delegated permissions run on behalf of a signed-in user. We have used application permissions in this automation. Here’s a detailed table explaining the Microsoft Graph permissions used in your offboarding script:

PermissionScopeWhat It AllowsUsed For in ScriptWhy Required
User.ReadWrite.AllApplicationRead and write all users’ full profiles• Get user information
• Block user sign-in (accountEnabled: false)
• Revoke user sessions
• Remove user licenses
Essential for user account management – needed to disable the user account and manage their profile
Group.ReadWrite.AllApplicationRead and write all groups• Get user’s group memberships
• Remove user from all groups
• Read group details (name, type)
Required to remove the user from all security groups and distribution lists to revoke group-based access
Directory.ReadWrite.AllApplicationRead and write directory data• Get the user’s group memberships
• Remove user from all groups
• Read group details (name, type)
Needed to check and display what administrative roles the user has in Entra ID (Azure AD)
AppRoleAssignment.ReadWrite.AllApplicationManage app role assignments for all users• Get the user’s enterprise app assignments
• Remove app role assignments
• Read service principal information
Critical for removing the user’s access to enterprise applications (SSO apps)
Automate Microsoft Entra User Offboarding with Microsoft Graph API and PowerShell. Table-01

Key Activities Performed by the User Offboarding Automation

We’ve already discussed the permissions required to execute the code. Now, let’s explore the activities performed by the offboarding automation. This section will give you a clear idea of the tasks that can be automated and how they streamline the user offboarding process in Microsoft Entra.

When a user’s offboarding process is triggered, the script begins by identifying the target account and immediately blocks sign-in access to prevent any further activity. It then revokes all active sessions, ensuring that the user is forcibly signed out across all devices and applications. This step is crucial for cutting off real-time access and mitigating any potential security risks from lingering sessions.

Next, the automation systematically removes the user from all assigned groups, including security and Microsoft 365 groups, and strips access to enterprise applications. It also checks for and clears any app registrations owned by the user, preventing orphaned resources or lingering permissions. Licenses are unassigned to free up resources and ensure that no paid subscriptions remain tied to the offboarded account.

Finally, the script performs a comprehensive access audit to confirm that the user holds no directory roles, group memberships, application access, or license entitlements. The account is then disabled, and a detailed checklist is generated to validate that the offboarding is complete and compliant. This end-to-end automation ensures a secure, scalable, and non-destructive offboarding process, reducing manual effort while maintaining full visibility and control.

NOTE! The automation will not DELETE the user from Microsoft Entra

PowerShell Script for User Offboarding

You have learned the permissions that you required to automate the Microsoft Entra User Offboarding and key the Activities Performed by the User Offboarding Automation. Now, it’s time to dive into the automation. Remember, you must install the Microsoft Graph Module before you run this automation, and open the PowerShell ISE or Visual Studio Code as Administrator.


##########################################################################

#EntraUser-OffBoarding.ps1

#Author: Sujin Nelladath

#LinkedIn : https://www.linkedin.com/in/sujin-nelladath-8911968a/

############################################################################

#Set-ExecutionPolicy

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser


# ===============================

# ENTRA ID- FULL OFFBOARDING

# ===============================

# Define mandatory parameter for user UPN

param(
    [Parameter(Mandatory = $true)]
    [string]$userUPN
)
if (-not $userUPN) 
{
    $userUPN = Read-Host "Enter the user UPN to offboard"
}


# Connect to Microsoft Graph

Connect-MgGraph -Scopes  "User.ReadWrite.All", "Group.ReadWrite.All", "Directory.ReadWrite.All", "AppRoleAssignment.ReadWrite.All"
 

# -------------------------------

# USER TO OFFBOARD

# -------------------------------


 

# Get user with error handling
try

{
    $UserEndpoint = "https://graph.microsoft.com/v1.0/users/$userUPN"
    $user = Invoke-MgGraphRequest -Method GET -Uri $UserEndpoint
    
    Write-Host "Found user: $($user.displayName)" -ForegroundColor Green
}
catch

{
    Write-Host "Error: User not found - $userUPN" -ForegroundColor Red
    exit 1
}

 

Write-Host "Offboarding user:" $user.displayName -ForegroundColor Cyan

 

# -------------------------------

# 1. BLOCK SIGN-IN

# -------------------------------

# Block user sign-in using Graph API
$updateEndpoint = "https://graph.microsoft.com/v1.0/users/$($user.id)"
$updateBody = @{
    accountEnabled = $false
} | ConvertTo-Json

Invoke-MgGraphRequest -Method PATCH -Uri $updateEndpoint -Body $updateBody -ContentType "application/json"

Write-Host "User sign-in blocked"

 

# -------------------------------

# 2. REVOKE ACTIVE SESSIONS

# -------------------------------

# Revoke user sessions using Graph API
$revokeEndpoint = "https://graph.microsoft.com/v1.0/users/$($user.id)/revokeSignInSessions"
Invoke-MgGraphRequest -Method POST -Uri $revokeEndpoint

Write-Host "Sessions revoked"

 

# -------------------------------

# 3. REMOVE FROM ALL GROUPS

# -------------------------------

# Get user group memberships using Graph API
$groupsEndpoint = "https://graph.microsoft.com/v1.0/users/$($user.id)/memberOf"
$groupsResponse = Invoke-MgGraphRequest -Method GET -Uri $groupsEndpoint
$groups = $groupsResponse.value

 

foreach ($group in $groups) 

{
    try 
    
    {
        # Remove user from group using Graph API
        $removeGroupEndpoint = "https://graph.microsoft.com/v1.0/groups/$($group.id)/members/$($user.id)/`$ref"
        Invoke-MgGraphRequest -Method DELETE -Uri $removeGroupEndpoint -ErrorAction Stop
        Write-Host "Removed from group: $($group.displayName)" -ForegroundColor Yellow
    }
    catch 
    
    {
        Write-Host "  Warning: Could not remove from group $($group.id) - $($_.Exception.Message)" -ForegroundColor Yellow
    }
}

 

Write-Host "Removed from all groups"

 

# -------------------------------

# 4. REMOVE ALL APP ASSIGNMENTS

# -------------------------------

# Get user app role assignments using Graph API
$appAssignmentsEndpoint = "https://graph.microsoft.com/v1.0/users/$($user.id)/appRoleAssignments"
$appAssignmentsResponse = Invoke-MgGraphRequest -Method GET -Uri $appAssignmentsEndpoint
$appAssignments = $appAssignmentsResponse.value

 

foreach ($app in $appAssignments)

 {
    try
    
    {
        # Remove app role assignment using Graph API
        $removeAppEndpoint = "https://graph.microsoft.com/v1.0/users/$($user.id)/appRoleAssignments/$($app.id)"
        Invoke-MgGraphRequest -Method DELETE -Uri $removeAppEndpoint -ErrorAction Stop
        Write-Host "  Removed app assignment: $($app.resourceDisplayName)" -ForegroundColor Yellow
    }
    catch 
    
    {
        Write-Host "  Warning: Could not remove app assignment $($app.id) - $($_.Exception.Message)" -ForegroundColor Yellow
    }
}

 

Write-Host "Application access removed"

 

# -------------------------------

# 5. REMOVE ALL LICENSES

# -------------------------------

# Get user licenses using Graph API
$licensesEndpoint = "https://graph.microsoft.com/v1.0/users/$($user.id)/licenseDetails"
$licensesResponse = Invoke-MgGraphRequest -Method GET -Uri $licensesEndpoint
$licenses = $licensesResponse.value | ForEach-Object { $_.skuId }

 

if ($licenses.Count -gt 0)

 {
    try 
    
    {
        # Remove licenses using Graph API
        $assignLicenseEndpoint = "https://graph.microsoft.com/v1.0/users/$($user.id)/assignLicense"
        $licenseBody = @{
            addLicenses    = @()
            removeLicenses = $licenses
        } | ConvertTo-Json -Depth 3
        Invoke-MgGraphRequest -Method POST -Uri $assignLicenseEndpoint -Body $licenseBody -ContentType "application/json" -ErrorAction Stop
        Write-Host "Licenses removed" -ForegroundColor Yellow
    }
    catch 
    
    {
        Write-Host "Warning: Could not remove licenses - $($_.Exception.Message)" -ForegroundColor Yellow
    }
}
else 

{
    Write-Host "No licenses assigned"
}

 

# -------------------------------

# OFFBOARDING COMPLETE

# -------------------------------

Write-Host "User has NO remaining access" -ForegroundColor Green

# USER ACCESS INVENTORY (Console Output Only)
# Covers:
# Directory (Entra ID) roles
# Group memberships
# Enterprise applications (SSO access)
# App registrations owned

# ================================

# USER ACCESS INVENTORY- CONSOLE

# ================================

 



 

# -------------------------------

# Connect (Read-only scopes)

# -------------------------------


$user = Invoke-MgGraphRequest -Method GET -Uri $UserEndpoint

 

Write-Host "`n===============================" -ForegroundColor Cyan

Write-Host " USER ACCESS CHECKLIST" -ForegroundColor Cyan

Write-Host "===============================" -ForegroundColor Cyan

Write-Host "User        : $($user.displayName)"

Write-Host "UPN         : $($user.userPrincipalName)"

Write-Host "Account     : $(if ($user.accountEnabled) {'Enabled'} else {'Disabled'})"

Write-Host "--------------------------------"

 

# ===============================

# 1. DIRECTORY (ENTRA ID) ROLES

# ===============================

Write-Host "`n[1] DIRECTORY ROLES" -ForegroundColor Yellow

 

# Get directory role assignments using Graph API
$directoryRolesEndpoint = "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments?`$filter=principalId eq '$($user.id)'"
$directoryRolesResponse = Invoke-MgGraphRequest -Method GET -Uri $directoryRolesEndpoint
$directoryRoles = $directoryRolesResponse.value

 

if ($directoryRoles) 

{

    foreach ($role in $directoryRoles) 
    
    {

        # Get role definition using Graph API
        $roleDefEndpoint = "https://graph.microsoft.com/v1.0/roleManagement/directory/roleDefinitions/$($role.roleDefinitionId)"
        $roleDef = Invoke-MgGraphRequest -Method GET -Uri $roleDefEndpoint
        Write-Host " - $($roleDef.displayName)"

    }

}
else 

{

    Write-Host " - None"

}

 

# ===============================

# 2. GROUP MEMBERSHIPS

# ===============================

Write-Host "`n[2] GROUP MEMBERSHIPS" -ForegroundColor Yellow

 

# Get user group memberships using Graph API
$groupsEndpoint = "https://graph.microsoft.com/v1.0/users/$($user.id)/memberOf"
$groupsResponse = Invoke-MgGraphRequest -Method GET -Uri $groupsEndpoint
$groups = $groupsResponse.value

 

if ($groups)

 {

    foreach ($group in $groups) 
    
    {

        # Get group details using Graph API
        $groupEndpoint = "https://graph.microsoft.com/v1.0/groups/$($group.id)"
        $g = Invoke-MgGraphRequest -Method GET -Uri $groupEndpoint -ErrorAction SilentlyContinue
        if ($g) {
            $type = if ($g.securityEnabled) { "Security" } else { "M365 / Distribution" }
            Write-Host " - $($g.displayName) [$type]"
        }

    }

} 

else

{

    Write-Host " - None"

}

 

# ===============================

# 3. ENTERPRISE APPLICATION ACCESS

# ===============================

Write-Host "`n[3] ENTERPRISE APPLICATION ACCESS" -ForegroundColor Yellow

 

# Get user app role assignments using Graph API
$appAssignmentsEndpoint = "https://graph.microsoft.com/v1.0/users/$($user.id)/appRoleAssignments"
$appAssignmentsResponse = Invoke-MgGraphRequest -Method GET -Uri $appAssignmentsEndpoint
$appAssignments = $appAssignmentsResponse.value

 

if ($appAssignments)

{
    foreach ($app in $appAssignments) 
    
    {
        # Get service principal using Graph API
        $spEndpoint = "https://graph.microsoft.com/v1.0/servicePrincipals/$($app.resourceId)"
        $sp = Invoke-MgGraphRequest -Method GET -Uri $spEndpoint -ErrorAction SilentlyContinue
        if ($sp)
        {
            Write-Host " - $($sp.displayName)"
        }
    }
}

else
{
    Write-Host " - None"
}

 

# ===============================

# 4. APP REGISTRATIONS OWNED

# ===============================

Write-Host "`n[4] APP REGISTRATIONS OWNED" -ForegroundColor Yellow

 

# Get user owned objects using Graph API
$ownedObjectsEndpoint = "https://graph.microsoft.com/v1.0/users/$($user.id)/ownedObjects"
$ownedObjectsResponse = Invoke-MgGraphRequest -Method GET -Uri $ownedObjectsEndpoint
$ownedApps = $ownedObjectsResponse.value | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.application' }

 

if ($ownedApps) 
{
    foreach ($app in $ownedApps) 
    {
        Write-Host " - $($app.displayName)"
    }
}
else 
{
    Write-Host " - None"
}

 

# ===============================



# ===============================


Write-Host "`n===============================" -ForegroundColor Green

Write-Host " CHECKLIST COMPLETE" -ForegroundColor Green

Write-Host "==============================="

I’ve included the Git repository link for downloading the code. The repository also contains a detailed README.md file with instructions and documentation.

Automate Microsoft Entra User Offboarding with Microsoft Graph API and PowerShell-Fig-02
Automate Microsoft Entra User Offboarding with Microsoft Graph API and PowerShell-Fig-02
Download : EntraUser-OffBoarding.PS1

End Result – Output

As I mentioned, run the above script as administrator. The script will ask you to authenticate with your account. The account should have enough permissions. Also, you must enter the user UPN; it’s a mandatory parameter.

Automate Microsoft Entra User Offboarding with Microsoft Graph API and PowerShell 1
Automate Microsoft Entra User Offboarding with Microsoft Graph API and PowerShell-Fig-03

In this example, I am offboarding a test user: john.doe@r3fx.onmicrosoft.com. When prompted, enter the user UPN and press Enter. The script will then execute, performing the complete offboarding process and finishing within seconds. Please refer to the below PowerShell output.

Found user: John Doe
Offboarding user: John Doe
User sign-in blocked

Name                           Value                                                                                                                   
----                           -----                                                                                                                   
@odata.context                 https://graph.microsoft.com/v1.0/$metadata#Edm.Boolean                                                                  
value                          True                                                                                                                    
Sessions revoked
Removed from group: Nelladath
Removed from all groups
Application access removed
No licenses assigned
User has NO remaining access

===============================
 USER ACCESS CHECKLIST
===============================
User        : John Doe
UPN         : john.doe@r3fx.onmicrosoft.com
Account     : Disabled
--------------------------------

[1] DIRECTORY ROLES
 - None

[2] GROUP MEMBERSHIPS
 - None

[3] ENTERPRISE APPLICATION ACCESS
 - None

[4] APP REGISTRATIONS OWNED
 - None

===============================
 CHECKLIST COMPLETE
===============================
Automate Microsoft Entra User Offboarding with Microsoft Graph API and PowerShell-Fig-04
Automate Microsoft Entra User Offboarding with Microsoft Graph API and PowerShell-Fig-04

I trust that this article will significantly benefit you and your organisation. I appreciate your patience in reading this post. I look forward to seeing you in the next post. Keep supporting the HTMD Community.

Need Further Assistance or Have Technical Questions?

Join the LinkedIn Page and Telegram group to get the latest step-by-step guides and news updates. Join our Meetup Page to participate in User group meetings. Also, Join the WhatsApp Community to get the latest news on Microsoft Technologies. We are there on Reddit as well.

Author

About the Author: Sujin Nelladath, Microsoft Graph MVP with over 11 years of experience in SCCM device management and Automation solutions, writes and shares his experiences with Microsoft device management technologies, Azure, DevOps and PowerShell automation.

Leave a Comment