Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 57 additions & 48 deletions EntraID/ConditionalAccess/Export-EntraCAPoliciesReport.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,55 @@
.SYNOPSIS
Export Conditional Access (CA) policies from Microsoft Entra ID (Azure AD) to a structured CSV file.
.DESCRIPTION
This script connects to Microsoft Graph (Beta) to retrieve Conditional Access policy configurations
from Entra ID and exports them to a timestamped CSV file.

This script uses Microsoft Graph (beta) to extract Conditional Access policy configurations into a timestamped CSV report
for audit, compliance, and operational insight.
Key Features:
• Filters: export only active, disabled, report-only, recently created, or recently modified policies
• Authentication: supports both delegated (interactive) and app-based (certificate) authentication
• Output: CSV with 30+ attributes; supports exclusion of empty columns
• Structure: modular functions, progress indicators, clean output formatting
• Hygiene: disconnects from Graph, suppresses output, respects automation use
• Standards: compliant with PowerShell 7.2+, Graph SDK, and internal scripting conventions

• Filters: Active, Disabled, Report-Only, recently created or modified
• Output: CSV file with 30+ core CA policy attributes
• Column handling: Optional exclusion of empty columns
• Authentication: Supports interactive and certificate-based Graph auth
• Progress: Includes progress bar with per-policy feedback (safe for divide-by-zero cases)
• Performance: Caches display names and uses optimized object creation
• Reliability: Verifies module presence and avoids redundant imports
• Hygiene: Disconnects from Graph after execution and suppresses disconnect output
• Verbose Mode: Uses [CmdletBinding()] with Write-Verbose for optional detailed output
• Standards: Aligns with PowerShell approved verbs, coding standards, and internal compliance rules
.PARAMETER ActiveCAPoliciesOnly
Export only policies where State = Enabled.
Only include policies whose State is Enabled.
.PARAMETER DisabledCAPoliciesOnly
Export only policies where State = Disabled.
Only include policies whose State is Disabled.
.PARAMETER ReportOnlyMode
Export only policies where State = EnabledForReportingButNotEnforced.
Only include policies in report-only mode.
.PARAMETER RecentlyCreatedCAPolicies
Export policies created within the last N days.
Include only policies created within the past N days.
.PARAMETER RecentlyModifiedCAPolicies
Export policies modified within the last N days.
Include only policies modified within the past N days.
.PARAMETER CreateSession
Disconnect any existing Graph session before connecting.
Force disconnection and re-authentication to Microsoft Graph.
.PARAMETER TenantId
Azure AD tenant ID (GUID); used for app-only authentication.
Directory (tenant) ID for Graph auth (used with ClientId and CertificateThumbprint).
.PARAMETER ClientId
Application (client) ID for Graph auth.
Application (client) ID for certificate-based Graph auth.
.PARAMETER CertificateThumbprint
Certificate thumbprint used for app-only authentication.
Thumbprint of the certificate used for app-only authentication.
.PARAMETER OutputDirectory
Folder where the CSV file will be saved (default: $PSScriptRoot\Output).
Directory path for the generated CSV file. Default: "$PSScriptRoot\Output"
.PARAMETER OutputFileName
File name for the exported report (default includes timestamp).
File name for the output. Default: "CA_Policies_Report_<timestamp>.csv"
.PARAMETER IncludeEmptyColumns
Include columns that have no data in any row.
Switch to include columns that are empty across all results.
.PARAMETER Verbose
Enables detailed console output using Write-Verbose. Available because the script uses [CmdletBinding()].
Use -Verbose to turn on; default is off.
.EXAMPLE
.\Export-EntraCAPoliciesReport.ps1
Exports all CA policies with default settings and minimal console output.
.EXAMPLE
.\Export-EntraCAPoliciesReport.ps1 -ReportOnlyMode -Verbose
Exports only report-only policies and emits detailed progress and status messages.
.EXAMPLE
.\Export-EntraCAPoliciesReport.ps1 -OutputDirectory 'D:\Reports' -OutputFileName 'CA_Policies.csv' -IncludeEmptyColumns
Exports to a custom path and includes columns that are empty across all rows.
.NOTES
Author: Travis McDade
Last Updated: 08/08/2025
Expand All @@ -45,7 +59,9 @@
Author: Kashyap Patel
URL : https://github.com/RapidScripter/export-conditional-access-policies
Revision History:
1.0.0 – 08/08/2025 – Finalized for production: cleanup, refactor, export modularization, best practices
1.0.0 – 08/08/2025 – Production-ready version with CmdletBinding(), Write-Verbose conversion,
module enforcement, property name corrections, improved progress handling,
and Graph session cleanup.
0.4.0 – 08/08/2025 – Refactor for efficiency, object creation, join-logic, header handling
0.3.0 – 08/07/2025 – Column pruning and ordered header logic
0.2.0 – 08/06/2025 – Progress integration and parameter enhancements
Expand All @@ -55,6 +71,7 @@ Revision History:
#Requires -Version 7.2

#region Parameters
[CmdletBinding()]
param
(
[switch]$ActiveCAPoliciesOnly,
Expand All @@ -81,7 +98,7 @@ foreach ($mod in $RequiredModules) {
Write-Host "Module '$mod' not found. Installing..." -ForegroundColor Yellow
Install-Module -Name $mod -Scope CurrentUser -Force -ErrorAction Stop -Confirm:$false
} else {
Write-Host "Module '$mod' is already installed and available."
Write-Verbose "Module '$mod' is already installed and available."
}
}

Expand All @@ -103,9 +120,9 @@ foreach ($sub in $RequiredSubmodules) {
#endregion

#region Global Hash Caches
$global:DirectoryObjsHash = @{}
$global:ServicePrincipalsHash = @{}
$global:NamedLocationHash = @{}
$script:DirectoryObjsHash = @{}
$script:ServicePrincipalsHash = @{}
$script:NamedLocationHash = @{}

#endregion

Expand All @@ -116,7 +133,7 @@ function Connect-MgGraphSession {
Disconnect-MgGraph -ErrorAction SilentlyContinue
}

Write-Host "Connecting to Microsoft Graph..."
Write-Verbose "Connecting to Microsoft Graph..."

if ($TenantId -and $ClientId -and $CertificateThumbprint) {
Connect-MgGraph -TenantId $TenantId -AppId $ClientId -CertificateThumbprint $CertificateThumbprint -NoWelcome
Expand Down Expand Up @@ -230,7 +247,7 @@ function Export-CaPolicyReport {
}
$Results | Sort-Object 'DisplayName' | Select-Object -Property ($Headers | Where-Object { $nonEmptyProps -contains $_ }) | Export-Csv -Path $Path -NoTypeInformation
} else {
$Results | Export-Csv -Path $Path -NoTypeInformation
$Results | Sort-Object 'DisplayName' | Select-Object -Property $Headers | Export-Csv -Path $Path -NoTypeInformation
}
Write-Progress -Activity "Exporting Conditional Access Policies" -Completed
}
Expand All @@ -249,13 +266,13 @@ $Results = @()

#region Service Principal and Location Lookup
$ProcessedCount = 0
$OutputCount = 0
#Get all service principals
Write-Progress -Activity "Initializing" -Status "Retrieving service principals..." -PercentComplete 10
$ServicePrincipalsHash = Get-MgBetaServicePrincipal -All | Group-Object -Property AppId -AsHashTable
Write-Progress -Activity "Initializing" -Status "Retrieving named locations..." -PercentComplete 20
$NamedLocationHash = Get-MgBetaIdentityConditionalAccessNamedLocation -All | Group-Object -Property Id -AsHashTable
Write-Progress -Activity "Exporting" -Status "Retrieving CA policies..." -PercentComplete 30
Write-Progress -Activity "Initializing" -Completed


#endregion
Expand All @@ -274,7 +291,8 @@ $AllPolicies | ForEach-Object {
$State = $_.State

# Show progress bar for current policy being processed
Write-Progress -Activity "Exporting Conditional Access Policies" -Status "Processing: $DisplayName" -PercentComplete (($ProcessedCount / $total) * 100)
$percent = if ($total -gt 0) { [math]::Round(($ProcessedCount / $total) * 100) } else { 100 }
Write-Progress -Activity "Exporting Conditional Access Policies" -Status "Processing: $DisplayName" -PercentComplete $percent

#Filter CA policies based on their State
if ($ActiveCAPoliciesOnly.IsPresent -and $State -ne "Enabled") {
Expand Down Expand Up @@ -369,8 +387,8 @@ $AllPolicies | ForEach-Object {

# --- Conditions Block ---
# Evaluate risk levels, client apps, platforms, and locations
$UserRiskLevel = $_.Conditions.UserRiskLevelLevels
$SigninRiskLevel = $_.Conditions.SigninRiskLevelLevels
$UserRiskLevel = $_.Conditions.UserRiskLevels
$SigninRiskLevel = $_.Conditions.SignInRiskLevels
$ClientAppTypes = $_.Conditions.ClientAppTypes
$IncludeDevicePlatform = $_.Conditions.Platforms.IncludePlatforms
$ExcludeDevicePlatform = $_.Conditions.Platforms.ExcludePlatforms
Expand Down Expand Up @@ -398,7 +416,7 @@ $AllPolicies | ForEach-Object {
# Evaluate grant control settings and operator
$GrantControls = Join-Array $_.GrantControls.BuiltInControls
$GrantControlsOperator = $_.GrantControls.Operator
$GrantControlsAuthStrength = $_.GrantControls.GrantControlsAuthStrength.DisplayName
$GrantControlsAuthStrength = $_.GrantControls.AuthenticationStrength.DisplayName

# --- Session Controls Block ---
# Evaluate session controls like app restrictions and sign-in frequency
Expand All @@ -420,7 +438,6 @@ $AllPolicies | ForEach-Object {
$SignInFrequencyValue = ""
}

$OutputCount++
$Result = @{'DisplayName' = $DisplayName;
'Description' = $Description;
'Created Date Time' = $CreatedDateTime;
Expand Down Expand Up @@ -462,29 +479,21 @@ $AllPolicies | ForEach-Object {
#region Final Output and Export
# Define export column order (must match keys in $Result)
$orderedHeaders = @(
'DisplayName', 'Description', 'Created Date Time', 'Modified Date Time',
'Include Users', 'Exclude Users', 'Include Groups', 'Exclude Groups',
'Include Roles', 'Exclude Roles', 'Include Guests or External Users', 'Exclude Guests or External Users',
'Include Applications', 'Exclude Applications', 'User Action', 'User Risk Level',
'Signin Risk Level', 'Client App Types', 'Include Device Platform', 'Exclude Device Platform',
'Include Locations', 'Exclude Locations', 'Grant Controls', 'Grant Controls Operator',
'Grant Controls Authentication Strength', 'App Enforced Restrictions Enabled', 'Cloud App Security',
'CAE Mode', 'Disable Resilience Defaults', 'Signin Frequency Enabled', 'Signin Frequency Value',
'State'
'DisplayName', 'Description', 'State', 'Include Users', 'Exclude Users', 'Include Groups', 'Exclude Groups', 'Include Roles', 'Exclude Roles', 'Include Guests or External Users', 'Exclude Guests or External Users', 'Include Applications', 'Exclude Applications', 'User Action', 'User Risk Level', 'Signin Risk Level', 'Client App Types', 'Include Device Platform', 'Exclude Device Platform', 'Include Locations', 'Exclude Locations', 'Grant Controls', 'Grant Controls Operator', 'Grant Controls Authentication Strength', 'App Enforced Restrictions Enabled', 'Cloud App Security', 'CAE Mode', 'Disable Resilience Defaults', 'Signin Frequency Enabled', 'Signin Frequency Value', 'Created Date Time', 'Modified Date Time'
)

# Finalize and export the filtered policy data to CSV, optionally pruning empty columns
if ($Results.Count -eq 0) {
Write-Host "No data found for the given criteria."
Write-Warning "No data found for the given criteria."
} else {
Export-CaPolicyReport -Results $Results -Headers $orderedHeaders -Path $ExportCSV -IncludeEmptyColumns:$IncludeEmptyColumns

Write-Host "The output file contains $($Results.Count) CA policies."
Write-Verbose "The output file contains $($Results.Count) CA policies."
if ((Test-Path -Path $ExportCSV) -eq $true) {
Write-Host "The output file is available at: " -ForegroundColor Yellow
Write-Verbose "The output file is available at: $ExportCSV"
Write-Host $ExportCSV
}
# Clean up Microsoft Graph session
Write-Host "Disconnecting from Microsoft Graph..."
Write-Verbose "Disconnecting from Microsoft Graph..."
Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
}
Loading