Skip to content

Commit 4f7fc04

Browse files
authored
Additional fixes and enhancements
1 parent 56f87e3 commit 4f7fc04

File tree

1 file changed

+56
-46
lines changed

1 file changed

+56
-46
lines changed

EntraID/ConditionalAccess/Export-EntraCAPoliciesReport.ps1

Lines changed: 56 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,55 @@
22
.SYNOPSIS
33
Export Conditional Access (CA) policies from Microsoft Entra ID (Azure AD) to a structured CSV file.
44
.DESCRIPTION
5-
This script connects to Microsoft Graph (Beta) to retrieve Conditional Access policy configurations
6-
from Entra ID and exports them to a timestamped CSV file.
7-
5+
This script uses Microsoft Graph (beta) to extract Conditional Access policy configurations into a timestamped CSV report
6+
for audit, compliance, and operational insight.
87
Key Features:
9-
• Filters: export only active, disabled, report-only, recently created, or recently modified policies
10-
• Authentication: supports both delegated (interactive) and app-based (certificate) authentication
11-
• Output: CSV with 30+ attributes; supports exclusion of empty columns
12-
• Structure: modular functions, progress indicators, clean output formatting
13-
• Hygiene: disconnects from Graph, suppresses output, respects automation use
14-
• Standards: compliant with PowerShell 7.2+, Graph SDK, and internal scripting conventions
15-
8+
• Filters: Active, Disabled, Report-Only, recently created or modified
9+
• Output: CSV file with 30+ core CA policy attributes
10+
• Column handling: Optional exclusion of empty columns
11+
• Authentication: Supports interactive and certificate-based Graph auth
12+
• Progress: Includes progress bar with per-policy feedback (safe for divide-by-zero cases)
13+
• Performance: Caches display names and uses optimized object creation
14+
• Reliability: Verifies module presence and avoids redundant imports
15+
• Hygiene: Disconnects from Graph after execution and suppresses disconnect output
16+
• Verbose Mode: Uses [CmdletBinding()] with Write-Verbose for optional detailed output
17+
• Standards: Aligns with PowerShell approved verbs, coding standards, and internal compliance rules
1618
.PARAMETER ActiveCAPoliciesOnly
17-
Export only policies where State = Enabled.
19+
Only include policies whose State is Enabled.
1820
.PARAMETER DisabledCAPoliciesOnly
19-
Export only policies where State = Disabled.
21+
Only include policies whose State is Disabled.
2022
.PARAMETER ReportOnlyMode
21-
Export only policies where State = EnabledForReportingButNotEnforced.
23+
Only include policies in report-only mode.
2224
.PARAMETER RecentlyCreatedCAPolicies
23-
Export policies created within the last N days.
25+
Include only policies created within the past N days.
2426
.PARAMETER RecentlyModifiedCAPolicies
25-
Export policies modified within the last N days.
27+
Include only policies modified within the past N days.
2628
.PARAMETER CreateSession
27-
Disconnect any existing Graph session before connecting.
29+
Force disconnection and re-authentication to Microsoft Graph.
2830
.PARAMETER TenantId
29-
Azure AD tenant ID (GUID); used for app-only authentication.
31+
Directory (tenant) ID for Graph auth (used with ClientId and CertificateThumbprint).
3032
.PARAMETER ClientId
31-
Application (client) ID for Graph auth.
33+
Application (client) ID for certificate-based Graph auth.
3234
.PARAMETER CertificateThumbprint
33-
Certificate thumbprint used for app-only authentication.
35+
Thumbprint of the certificate used for app-only authentication.
3436
.PARAMETER OutputDirectory
35-
Folder where the CSV file will be saved (default: $PSScriptRoot\Output).
37+
Directory path for the generated CSV file. Default: "$PSScriptRoot\Output"
3638
.PARAMETER OutputFileName
37-
File name for the exported report (default includes timestamp).
39+
File name for the output. Default: "CA_Policies_Report_<timestamp>.csv"
3840
.PARAMETER IncludeEmptyColumns
39-
Include columns that have no data in any row.
41+
Switch to include columns that are empty across all results.
42+
.PARAMETER Verbose
43+
Enables detailed console output using Write-Verbose. Available because the script uses [CmdletBinding()].
44+
Use -Verbose to turn on; default is off.
45+
.EXAMPLE
46+
.\Export-EntraCAPoliciesReport.ps1
47+
Exports all CA policies with default settings and minimal console output.
48+
.EXAMPLE
49+
.\Export-EntraCAPoliciesReport.ps1 -ReportOnlyMode -Verbose
50+
Exports only report-only policies and emits detailed progress and status messages.
51+
.EXAMPLE
52+
.\Export-EntraCAPoliciesReport.ps1 -OutputDirectory 'D:\Reports' -OutputFileName 'CA_Policies.csv' -IncludeEmptyColumns
53+
Exports to a custom path and includes columns that are empty across all rows.
4054
.NOTES
4155
Author: Travis McDade
4256
Last Updated: 08/08/2025
@@ -45,7 +59,9 @@
4559
Author: Kashyap Patel
4660
URL : https://github.com/RapidScripter/export-conditional-access-policies
4761
Revision History:
48-
1.0.0 – 08/08/2025 – Finalized for production: cleanup, refactor, export modularization, best practices
62+
1.0.0 – 08/08/2025 – Production-ready version with CmdletBinding(), Write-Verbose conversion,
63+
module enforcement, property name corrections, improved progress handling,
64+
and Graph session cleanup.
4965
0.4.0 – 08/08/2025 – Refactor for efficiency, object creation, join-logic, header handling
5066
0.3.0 – 08/07/2025 – Column pruning and ordered header logic
5167
0.2.0 – 08/06/2025 – Progress integration and parameter enhancements
@@ -55,6 +71,7 @@ Revision History:
5571
#Requires -Version 7.2
5672

5773
#region Parameters
74+
[CmdletBinding()]
5875
param
5976
(
6077
[switch]$ActiveCAPoliciesOnly,
@@ -81,7 +98,7 @@ foreach ($mod in $RequiredModules) {
8198
Write-Host "Module '$mod' not found. Installing..." -ForegroundColor Yellow
8299
Install-Module -Name $mod -Scope CurrentUser -Force -ErrorAction Stop -Confirm:$false
83100
} else {
84-
Write-Host "Module '$mod' is already installed and available."
101+
Write-Verbose "Module '$mod' is already installed and available."
85102
}
86103
}
87104

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

105122
#region Global Hash Caches
106-
$global:DirectoryObjsHash = @{}
107-
$global:ServicePrincipalsHash = @{}
108-
$global:NamedLocationHash = @{}
123+
$script:DirectoryObjsHash = @{}
124+
$script:ServicePrincipalsHash = @{}
125+
$script:NamedLocationHash = @{}
109126

110127
#endregion
111128

@@ -116,7 +133,7 @@ function Connect-MgGraphSession {
116133
Disconnect-MgGraph -ErrorAction SilentlyContinue
117134
}
118135

119-
Write-Host "Connecting to Microsoft Graph..."
136+
Write-Verbose "Connecting to Microsoft Graph..."
120137

121138
if ($TenantId -and $ClientId -and $CertificateThumbprint) {
122139
Connect-MgGraph -TenantId $TenantId -AppId $ClientId -CertificateThumbprint $CertificateThumbprint -NoWelcome
@@ -230,7 +247,7 @@ function Export-CaPolicyReport {
230247
}
231248
$Results | Sort-Object 'DisplayName' | Select-Object -Property ($Headers | Where-Object { $nonEmptyProps -contains $_ }) | Export-Csv -Path $Path -NoTypeInformation
232249
} else {
233-
$Results | Export-Csv -Path $Path -NoTypeInformation
250+
$Results | Sort-Object 'DisplayName' | Select-Object -Property $Headers | Export-Csv -Path $Path -NoTypeInformation
234251
}
235252
Write-Progress -Activity "Exporting Conditional Access Policies" -Completed
236253
}
@@ -274,7 +291,8 @@ $AllPolicies | ForEach-Object {
274291
$State = $_.State
275292

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

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

370388
# --- Conditions Block ---
371389
# Evaluate risk levels, client apps, platforms, and locations
372-
$UserRiskLevel = $_.Conditions.UserRiskLevelLevels
373-
$SigninRiskLevel = $_.Conditions.SigninRiskLevelLevels
390+
$UserRiskLevel = $_.Conditions.UserRiskLevels
391+
$SigninRiskLevel = $_.Conditions.SignInRiskLevels
374392
$ClientAppTypes = $_.Conditions.ClientAppTypes
375393
$IncludeDevicePlatform = $_.Conditions.Platforms.IncludePlatforms
376394
$ExcludeDevicePlatform = $_.Conditions.Platforms.ExcludePlatforms
@@ -398,7 +416,7 @@ $AllPolicies | ForEach-Object {
398416
# Evaluate grant control settings and operator
399417
$GrantControls = Join-Array $_.GrantControls.BuiltInControls
400418
$GrantControlsOperator = $_.GrantControls.Operator
401-
$GrantControlsAuthStrength = $_.GrantControls.GrantControlsAuthStrength.DisplayName
419+
$GrantControlsAuthStrength = $_.GrantControls.AuthenticationStrength.DisplayName
402420

403421
# --- Session Controls Block ---
404422
# Evaluate session controls like app restrictions and sign-in frequency
@@ -462,29 +480,21 @@ $AllPolicies | ForEach-Object {
462480
#region Final Output and Export
463481
# Define export column order (must match keys in $Result)
464482
$orderedHeaders = @(
465-
'DisplayName', 'Description', 'Created Date Time', 'Modified Date Time',
466-
'Include Users', 'Exclude Users', 'Include Groups', 'Exclude Groups',
467-
'Include Roles', 'Exclude Roles', 'Include Guests or External Users', 'Exclude Guests or External Users',
468-
'Include Applications', 'Exclude Applications', 'User Action', 'User Risk Level',
469-
'Signin Risk Level', 'Client App Types', 'Include Device Platform', 'Exclude Device Platform',
470-
'Include Locations', 'Exclude Locations', 'Grant Controls', 'Grant Controls Operator',
471-
'Grant Controls Authentication Strength', 'App Enforced Restrictions Enabled', 'Cloud App Security',
472-
'CAE Mode', 'Disable Resilience Defaults', 'Signin Frequency Enabled', 'Signin Frequency Value',
473-
'State'
483+
'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'
474484
)
475485

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

482-
Write-Host "The output file contains $($Results.Count) CA policies."
492+
Write-Verbose "The output file contains $($Results.Count) CA policies."
483493
if ((Test-Path -Path $ExportCSV) -eq $true) {
484-
Write-Host "The output file is available at: " -ForegroundColor Yellow
494+
Write-Verbose "The output file is available at: $ExportCSV"
485495
Write-Host $ExportCSV
486496
}
487497
# Clean up Microsoft Graph session
488-
Write-Host "Disconnecting from Microsoft Graph..."
498+
Write-Verbose "Disconnecting from Microsoft Graph..."
489499
Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
490500
}

0 commit comments

Comments
 (0)