Recover Compromised Microsoft 365 tenant using PowerShell & Microsoft Graph

setting scene :

Let’s imagine a company that uses Microsoft 365 for all its day-to-day operations. One day, the global administrator account is hacked. The hacker blocks access to all other administrator accounts, including the “Break Glass Account” protection account. The company then loses full access to its Office 365 tenant. This is an extremely serious situation. Not only can the company no longer access its data and services, but the hacker also has access to all the company’s sensitive information.

For all this, a hacked Microsoft 365 global administrator account is extremely serious and can have disastrous consequences for an organization.

Regular steps to recover your Office 365 tenant:

  1. Contact Microsoft support (time)
  2. Identity verification: Microsoft will need to verify the identity of the person who reported the problem to ensure that they are associated with the company. (time)
  3. Investigation and recovery: Microsoft will investigate the incident and work to recover the tenant. (time)

Contacting Microsoft and recovering the account can take time, and in our case every minute counts.

What to do in this situation?

That’s what we’re going to discuss today.

Of course, to be able to recover a Microsoft 365 tenant, you need to prepare a backup access way. I’m not talking here about a “Break Glass Account”, because even that account can be blocked by the hacker after taking control and you can’t log in again.

The solution I’m going to present today is an Entra ID application managed from Microsoft Graph, this application will allow you to add Global Admin accounts at any time and recover your access and block the compromised account even if you’ve lost access to all the admin accounts of your Microsoft 365 tenant.

Steps to follow :

1 – Create an Entra ID application with specific rights for account creation and role assignment.

2 – Create a Self-Signed certificate and install it in the local certificate store, then keep the certificate in a safe place.

3 – Export the certificate to the Entra ID application and use it as a means of authentication.

4 – Connect to this application using PowerShell and Microsoft Graph, then perform a Global admin account creation test.

5 – Keep this application as a backup to recover an Office 365 tenant in case a Global Admin account is compromised.

Prerequisites

  • PowerShell 7 and later is the recommended PowerShell version for use with the Microsoft Graph PowerShell SDK on all platforms.
  • Installation of .NET Framework 4.7.2 or later.
  • The PowerShell script execution policy must be set to remotesigned or less restrictive. Use Get-ExecutionPolicy to determine the current execution policy.
  • Global Admin Access to Microsoft 365.

I – Create an Entra ID application with specific rights for account creation and role assignment.

1 – Installing and updating PowerShell Modules :

Open Microsoft ISE As Administrator from the windows button :

then copy paste this code :

 # Test if Microsoft.Graph Module is installed, if Not Install it
IF (-not (Get-Module -Name Microsoft.Graph -ListAvailable)) 
{
    Write-Host "BEGIN - Install Microsoft.Graph Module " -ForegroundColor Green
    install-Module Microsoft.Graph -Force -AllowClobber
    Write-Host "Module Microsoft.Graph Installed ....................................... OK" -ForegroundColor Green
}
else 
{
    <# Action when all if and elseif conditions are false #>
    Update-Module Microsoft.Graph -Force
    Write-Host "Module Microsoft.Graph Updated ....................................... OK" -ForegroundColor Green
}

# Test if MSAL.PS Module is installed, if Not Install it
IF (-not (Get-Module -Name MSAL.PS -ListAvailable)) 
{
    Write-Host "BEGIN - Install MSAL.PS Module " -ForegroundColor Green
    # Install module MSAL for powershell
    Install-Module MSAL.PS -Scope AllUsers -Force
    Write-Host "Module MSAL.PS Installed ....................................... OK" -ForegroundColor Green
}
else 
{
    <# Action when all if and elseif conditions are false #>
    Update-Module MSAL.PS -Force
    Write-Host "Module MSAL.PS Updated ....................................... OK" -ForegroundColor Green
}


# Import Module MSAL for Authentication
Import-Module MSAL.PS 

Execution Result :

2 – Connect to Microsoft Graph :

Still in the same PowerShell ISE window , copy paste this code :

#============================== Connect to MG ===========================
# Connect to microsoft graph
Connect-MgGraph -Scopes "Application.ReadWrite.All"

Check your Client ID + Tenant ID…etc

Still in the same PowerShell ISE window , copy paste this code :

# Get ClientID + TenantID + Scopes + Authtype +Account + ...
Get-MgContext

Switch to Profile v1.0 :

Still in the same PowerShell ISE window , copy paste this code :

# Switch to profile v1.0
Select-MgProfile -Name "v1.0"

3 – Create Entra ID application :

In our case, I’m going to call the application “RecoveryApp1”. You can use any other name to disguise your application.

Still in the same PowerShell ISE window , copy paste this code :

# Create New Az Application
$NewAzApp = @{
    DisplayName = "RecoveryApp1"
    Web = @{
        RedirectUris = "https://localhost:8080"
        ImplicitGrantSettings = @{
            EnableAccessTokenIssuance   = $true
            EnableIdTokenIssuance       = $true;
        }
    }
}
New-MgApplication @NewAzApp

Execution Result , application created successfully :

You can find out new application directly in ENTRA ID :

From Entra ID -> App Registration -> RecoveryApp1

4 – Generate Secret for new Entra ID Application :

Still in the same PowerShell ISE window , copy paste this code :

# 1 - Get new Az App created recently
$AzApp = Get-MgApplication -Filter "DisplayName eq 'RecoveryApp1'"
$AzApp

# 2 - Create Secret for Az App
$AzAppSecret = Add-MgApplicationPassword -ApplicationId $AzApp.Id

Execution Result :

You can Check created Secret from here :

Entra ID -> App Registration -> RecoveryApp1 -> Certificate & Secrets -> Client Secrets

5 – assign the following roles to our RecoveryApp1 application :

from Microsoft Azure open the “RecoveryApp1” application then go to the “API Permissions” section

Select “Microsoft Graph

Select “Application Permission

Select to Add this roles to your App :

  • Directory.ReadWrite.All
  • User.ReadWrite.All
  • RoleManagement.ReadWrite.Directory

Now we need to authorize these rights for our Application.

Select “Grant admin consent” buton the confirm with “YES”

Now you should see granted permissions like this :

6 – Create Self SIgned Certificate and upload it to Entra ID RecoveryApp1 :

Still in the same PowerShell ISE window , copy paste this code :

#splatting for human readability
$CertParam = @{
    'KeyAlgorithm'      = 'RSA'
    'KeyLength'         = 2048
    'KeyExportPolicy'   = 'NonExportable'
    'DnsName'           = 'localhost'
    'FriendlyName'      = 'GraphApi-BackupApp1'
    'CertStoreLocation' = 'Cert:\CurrentUser\My\'
    'NotAfter'          = (Get-Date).AddYears(10)
}
#Creating self signed cert with parameters from above.
$Cert = New-SelfSignedCertificate @CertParam 

Here I have Created Temp Dir on “C:\Temp

Still in the same PowerShell ISE window , copy paste this code :

# Export Certificate to Desktop the upload it to Azure AD :
Export-Certificate -Cert $Cert -FilePath C:\Temp\GraphApi-BackupApp1.cer

These steps will guide you efficiently through the process of downloading a certificate for your Entra ID application on Microsoft Azure.

To download an Entra ID application certificate, follow these red-numbered steps:

  1. Go to the “Certificates & secrets” section under “Manage” in the left-hand sidebar.
  2. Select “Certificates (0)
  3. Click on the “Upload certificate” button.
  4. In the “Upload certificate” pop-up window, upload a certificate (public key) with one of the following file types: .cer, .pem or .crt and Fill in the description field.
  5. Click on the “Add” button to complete the download.

Keep in mind, after using Self-Signed Certificate, you need to store it in secure location like an Password Manager or flash drive which can be used in an emergency (I recommand to use Two copy on two flash drive in case the first one doesn’t work properly)… Same thing with Tenant-ID and Application-ID

For flash drive you can use anyone with write Lock protection, I’m using this one (You can find it on Amazon or any other market):

Kanguru SS3™ Flash Drive w/ Physical Write Protect Switch

7 – Disconnect from Microsoft Graph :

Still in the same PowerShell ISE window , copy paste this code :

# Disconnect from Microsoft Graph
Disconnect-MgGraph

We’ve now finished preparing the Graph application for our Microsoft 365 tenant.

We’re now going to test the connection by creating a Global admin account.

II – Connect to Microsoft ENTRA ID application and create Global Admin Account :

After preparing the Entra ID application, the following code is the most important one to save with all the information I’m about to mention.

All the previous part won’t be used anymore, but you’ll need this code every time you’ve lost access, so keep it in a safe place with the certificate you exported in the previous part if you ever change PC, install the certificate again and run the following code.

This step is usually used after you’ve lost access to your tenant, but we’ll do a test run now to make sure our script works. You can log in infinitely and create so many global admin accounts each time.

Before you start, you’ll need to prepare a few items of information:

  1. Tenant ID
  2. Application ID
  3. New user DisplayName
  4. New user MailNickName
  5. New user UserPrincipalName
  6. New user Password

To retrieve the tenant ID and the application ID, you can go directly to the Entra ID application and copy/Paste it.

Here you need to replace variables on the first section with your own informations, then you can execute the code :

# ======================= First Section ==========================================
# Complete Tenant ID + Application ID
$TenantId       = "wae9b7bd-q237-787f-eae9b7bd"
$AppId          = "eae54326-8787-4f7f-2124rt45"

# Replace Account Name + Domaine
$DisplayName = "Backup Admin"
$MailNickName = "Backup.Admin"
$UserPrincipalName = "Backup.Admin@domaine.onmicrosoft.com"
$Passwd = "P@ssw0rd@!"

# ======================== Second Section =========================================

# Connect to MG Application registred earlyer using loacl generated certificate 


# Get full path Certificate with Thumbprint 
$CertificateThumb = "Cert:\CurrentUser\My\" + (Get-ChildItem "Cert:\CurrentUser\My\" | Where-Object FriendlyName -EQ 'GraphApi-BackupApp1').Thumbprint
# Get certificate that we gonna use for connection
$Certificate  = Get-ChildItem $CertificateThumb  # PSChildName

#Connect to Graph using access token
Connect-MgGraph -TenantId $TenantId -AppId $AppId -Certificate $Certificate


#============================== MG Create New User ======================= 

$PasswordProfile = @{
    ForceChangePasswordNextSignIn         = $false
    ForceChangePasswordNextSignInWithMfa  = $true
    Password                              = $Passwd
}

New-MgUser -AccountEnabled -DisplayName $DisplayName -MailNickname $MailNickName -UserPrincipalName $UserPrincipalName -PasswordProfile $PasswordProfile

#============================== Assign Global Admin Role to New User =====================

$userUPN = $UserPrincipalName
$roleName = "Global Administrator"

$role = Get-MgDirectoryRole | Where-Object {$_.displayName -eq $roleName}
if ($null -eq $role) 
{
    $roleTemplate = (Get-MgDirectoryRoleTemplate | Where-Object {$_.displayName -eq $roleName}).id
    New-MgDirectoryRole -DisplayName $roleName -RoleTemplateId $roleTemplate
    $role = Get-MgDirectoryRole | Where-Object {$_.displayName -eq $roleName}
}
$userId = (Get-MgUser -Filter "userPrincipalName eq '$userUPN'").Id
$newRoleMember =@{
    "@odata.id"= "https://graph.microsoft.com/v1.0/users/$userId"
    }
New-MgDirectoryRoleMemberByRef -DirectoryRoleId $role.Id -BodyParameter $newRoleMember

# Get all properties for New Microsoft Graph User
Get-MgUser | Where-Object UserPrincipalName -EQ $userUPN | Select-Object *

Here is the result of my code execution, new user created :

We can check our new Global Admin User directly from Azure, keep in mind in case of recovery you won’t have access to Azure.

III – Access your tenant using the new “Global Admin” account :

1 – Open new in-private browser and go to : https://login.microsoftonline.com/

2 – Type new account credantials, in my case : Backup Admin

you can then block the compromised account to recover it later after changing the password.

Conclusion :

Losing access to an Microsoft 365 tenant as a result of a hack is a very serious situation that can have disastrous consequences for a business. It is essential to take preventive measures to secure administrator accounts and to put in place contingency plans to respond to such incidents. the procedure we’ve seen allows us to avoid waiting times with Microsoft and surveys that can take up a lot of your time.

Finally, this incident underlines the importance of good security hygiene, including the use of two-factor authentication, regular employee training and the constant updating of security policies.

Thanks

Aymen EL JAZIRI (Microsoft MVP)
Aymen EL JAZIRI (Microsoft MVP)

Hi, I’m Aymen El Jaziri , a passionate System Administrator and Microsoft MVP, with years of hands-on experience in managing and securing modern IT infrastructures.
This blog is where I share technical guides, automation scripts, product reviews, and real-world solutions that help IT professionals simplify their day-to-day work and stay ahead in a fast-evolving cloud ecosystem.
Whether you’re here to troubleshoot an issue, improve your automation game, or learn new best practices , welcome in my blog !
Let’s build a stronger, smarter IT community together.
Feel free to connect with me on LinkedIn for more content, discussions, or collaboration opportunities.

Thanks

Aymen

Articles: 154