Conditional Access (CA) policies are the backbone of identity security in Microsoft 365. Over time, tenants accumulate policies for pilot testing, temporary security enforcement, or legacy requirements. If these policies aren’t reviewed periodically, you can end up with:
This Graph PowerShell script pulls a complete Conditional Access policies summary, flattens key settings into a clean report, exports an Excel-safe CSV, and emails it to administrators or security stakeholders automatically.
Try the M365Corner Microsoft 365 Reporting Tool — your DIY pack with 20+ out-of-the-box M365 reports for Users, Groups, and Teams.
$SenderUPN = "admin@yourtenant.onmicrosoft.com"
$Recipients = @(
"admin@yourtenant.onmicrosoft.com",
"securityteam@yourtenant.onmicrosoft.com"
)
Connect-MgGraph -Scopes "Policy.Read.All","Mail.Send"
$Policies = Get-MgIdentityConditionalAccessPolicy -All
$Report = @()
foreach ($p in $Policies) {
$IncludeUsers = ($p.Conditions.Users.IncludeUsers -join ", ")
$ExcludeUsers = ($p.Conditions.Users.ExcludeUsers -join ", ")
$IncludeGroups = ($p.Conditions.Users.IncludeGroups -join ", ")
$ExcludeGroups = ($p.Conditions.Users.ExcludeGroups -join ", ")
$IncludeRoles = ($p.Conditions.Users.IncludeRoles -join ", ")
$ExcludeRoles = ($p.Conditions.Users.ExcludeRoles -join ", ")
$CloudApps = ($p.Conditions.Applications.IncludeApplications -join ", ")
$ClientApps = ($p.Conditions.ClientAppTypes -join ", ")
$GrantControls = ($p.GrantControls.BuiltInControls -join ", ")
$GrantOperator = $p.GrantControls.Operator
$SessionControls = @()
if ($p.SessionControls) {
$SessionControls = $p.SessionControls.PSObject.Properties |
Where-Object { $_.Value -ne $null } |
ForEach-Object { $_.Name }
}
$Report += [PSCustomObject]@{
"Policy Name" = $p.DisplayName
"State" = $p.State
"Created Date" = $p.CreatedDateTime
"Modified Date" = $p.ModifiedDateTime
"Include Users" = if ($IncludeUsers) { $IncludeUsers } else { "All / Not Specified" }
"Exclude Users" = if ($ExcludeUsers) { $ExcludeUsers } else { "-" }
"Include Groups" = if ($IncludeGroups) { $IncludeGroups } else { "-" }
"Exclude Groups" = if ($ExcludeGroups) { $ExcludeGroups } else { "-" }
"Include Roles" = if ($IncludeRoles) { $IncludeRoles } else { "-" }
"Exclude Roles" = if ($ExcludeRoles) { $ExcludeRoles } else { "-" }
"Cloud Apps" = if ($CloudApps) { $CloudApps } else { "All / Not Specified" }
"Client App Types" = if ($ClientApps) { $ClientApps } else { "-" }
"Grant Controls" = if ($GrantControls) { $GrantControls } else { "-" }
"Grant Operator" = if ($GrantOperator) { $GrantOperator } else { "-" }
"Session Controls" = if ($SessionControls.Count -gt 0) { $SessionControls -join ", " } else { "-" }
"Policy Id" = $p.Id
}
}
$ReportPath = "$env:TEMP\ConditionalAccessPolicies_Report.csv"
if ($Report.Count -gt 0) {
$Report | Sort-Object "Policy Name" |
Export-Csv -Path $ReportPath -NoTypeInformation -Encoding utf8
} else {
"No Conditional Access policies were found in the tenant." |
Set-Content -Path $ReportPath -Encoding utf8
}
$Bytes = [System.IO.File]::ReadAllBytes($ReportPath)
$Utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($ReportPath, [System.Text.Encoding]::UTF8.GetString($Bytes), $Utf8Bom)
$Count = $Report.Count
$Subject = "Conditional Access Policies Report — $(Get-Date -Format 'yyyy-MM-dd')"
$Body = @"
Hello Team,<br><br>
Attached is the <b>Conditional Access Policies Report</b> from the tenant.<br>
This report summarizes policy targeting and enforcement settings.<br><br>
Total policies found: <b>$Count</b><br><br>
Regards,<br>
Graph PowerShell Automation
"@
$AttachmentContent = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($ReportPath))
$Attachments = @(
@{
"@odata.type" = "#microsoft.graph.fileAttachment"
Name = "ConditionalAccessPolicies_Report.csv"
ContentBytes = $AttachmentContent
}
)
$ToRecipients = $Recipients | ForEach-Object {
@{ EmailAddress = @{ Address = $_ } }
}
$Message = @{
Message = @{
Subject = $Subject
Body = @{
ContentType = "HTML"
Content = $Body
}
ToRecipients = $ToRecipients
Attachments = $Attachments
}
SaveToSentItems = "true"
}
Send-MgUserMail -UserId $SenderUPN -BodyParameter $Message
Write-Host "Conditional Access policies report emailed successfully." -ForegroundColor Green
| Error | Cause | Solution |
|---|---|---|
| Authorization_RequestDenied / 403 | Missing Policy.Read.All permission or consent not granted. | Grant admin consent and reconnect with: Connect-MgGraph -Scopes "Policy.Read.All","Mail.Send" |
| Report has blank targeting fields | Policy targets “All Users” or “All Apps” without explicit include lists. | Valid behavior; script marks these as “All / Not Specified”. |
| Email sending fails | Sender UPN isn’t mailbox-enabled or Mail.Send missing. | Use a licensed mailbox account and confirm Mail.Send consent. |
Conditional Access policies drift over time, and without periodic review, even a secure tenant can develop blind spots. This Graph PowerShell script gives administrators a simple way to export and email a complete CA policy summary, making security governance transparent, trackable, and audit-ready.
Run it regularly to ensure policies remain aligned with tenant security standards.
© m365corner.com. All Rights Reserved. Design by HTML Codex