Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124


Have you ever been locked out of your Microsoft 365 tenant due to a misconfigured Conditional Access policy ?
If yes, you probably know how painful it is to rely on Microsoft support to regain access especially when time is critical.
Traditionally, the answer has been to use a Break Glass account. While this is still a recommended best practice, Break Glass accounts come with their own set of risks and challenges:
But what if there was a modern, automated solution that restores access to your tenant with a single click ?
In this article, I’ll show you how to build a fully automated Azure Runbook that can :
Let’s get started 👇
Watch technical steps video from here :
https://www.youtube.com/watch?v=hV__pEY2v9M
Get my PowerShell Script repository Link from here : https://github.com/aymenjaz/PowerShell/blob/main/1%20-%20Microsoft%20365/Azure/Automation%20Account/Exclude%20user%20from%20All%20Conditional%20Access%20Policies.md
We’ll build an Azure Automation Runbook that :



Let’s move to Client secret creation.


Keep in mind you should replace the secret every 2 years.
Let’s Grant Microsoft Graph API Permissions now.

Select and add these roles :





Let’s create the runbook now.



# User to exclude from all CA Policies
param(
[object]$WebhookData
)
# Extraire JSON body from Webhook request
if ($WebhookData -ne $null) {
$body = $WebhookData.RequestBody | ConvertFrom-Json
# Get UserToExclude
$UserToExclude = $body.UserToExclude
Write-Output "User to exclude : $UserToExclude"
}
else {
Write-Error "WebhookData is null"
exit
}
# ----------------------------
# Define your app credentials
# ----------------------------
$TenantId = "xxxxxx-xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxx" # entra tenant ID
$ClientId = "xxxxxx-xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxx" # entra application ID
$ClientSecret = "xxxxxx-xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxx" # entra application secret
$Scope = "https://graph.microsoft.com/.default"
$AuthUrl = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
# ========== Build Body ==========
$Body = @{
client_id = $ClientId
scope = $Scope
client_secret = $ClientSecret
grant_type = "client_credentials"
}
# ========== Request Access Token ==========
$TokenResponse = Invoke-RestMethod -Method POST -Uri $AuthUrl -Body $Body -ContentType "application/x-www-form-urlencoded"
$AccessToken = $TokenResponse.access_token
# ========== Use Access Token in API Call ==========
$Headers = @{
Authorization = "Bearer $AccessToken"
}
# ===============================
# GET USER OBJECT ID
# ===============================
$UserResponse = Invoke-RestMethod -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$UserToExclude" -Headers $Headers
$UserId = $UserResponse.id
# ===============================
# GET ALL CONDITIONAL ACCESS POLICIES
# ===============================
$PoliciesResponse = Invoke-RestMethod -Method GET -Uri "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" -Headers $Headers
$Policies = $PoliciesResponse.value
# ===============================
# EXCLUDE USER FROM ALL CONDITIONAL ACCESS POLICIES
# ===============================
foreach ($Policy in $Policies) {
Write-Output "Processing policy: $($Policy.displayName)"
$Conditions = $Policy.conditions
if (-not $Conditions.users) {
Write-Output "Policy has no user condition, skipping..."
continue
}
$IncludeUsers = $Conditions.users.includeUsers
$ExcludeUsers = $Conditions.users.excludeUsers
if ($ExcludeUsers -contains $UserId) {
Write-Output "User already excluded, skipping..."
continue
}
# Add the user to the exclusion list
$NewExcludeList = $ExcludeUsers + $UserId
# Reconstruct the conditions object
$NewConditions = @{
users = @{
includeUsers = $IncludeUsers
excludeUsers = $NewExcludeList
}
}
# Update the policy
$UpdateUri = "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/$($Policy.id)"
$Body = @{
conditions = $NewConditions
} | ConvertTo-Json -Depth 10
Invoke-RestMethod -Method PATCH -Uri $UpdateUri `
-Headers @{
Authorization = "Bearer $AccessToken"
"Content-Type" = "application/json"
} `
-Body $Body
Write-Output "User excluded from policy: $($Policy.displayName)"
}
# ===============================
# REMOVE TOKEN
# ===============================
Remove-Variable AccessToken, ClientSecret, TokenResponse, Headers, PatchHeaders -ErrorAction SilentlyContinue




You can now trigger the recovery from any HTTP tool (Postman, Power Automate, custom portal) by calling:
# Define User to Exclude from Conditional Access
$UserToExclude = "User@domain.com"
# Replace this with your actual webhook URL
$webhookUrl = "https://690328.webhook.yq.azure-automation.net/webhooks?"
# Send HTTP POST request
Invoke-RestMethod -Uri $webhookUrl -Method Post -Body (@{ UserToExclude = $($UserToExclude) } | ConvertTo-Json) -ContentType 'application/json'

The Runbook will then:
Here is the result :

This solution brings up an important question:
Do we still need a Break Glass account ?
While Break Glass accounts are a best practice, they require:
In contrast, the solution above is :
💡 Why not have both and let this solution act as a modern, managed safety net for Conditional Access recovery?
With this approach, you can:
Thanks