Public/Export-IOConditionalAccessReport.ps1
|
function Export-IOConditionalAccessReport { <# .SYNOPSIS Exports all Conditional Access policies with gap analysis. .EXAMPLE Export-IOConditionalAccessReport .EXAMPLE Export-IOConditionalAccessReport -ToCsv "ca-policies.csv" #> [CmdletBinding()] param( [switch]$EnabledOnly, [string]$ToCsv ) $cmdName = $MyInvocation.MyCommand.Name Write-IOLog 'Exporting Conditional Access policies...' -Level Info -Component $cmdName $results = [System.Collections.Generic.List[PSCustomObject]]::new() $policies = Invoke-IOGraphRequest -Uri 'v1.0/identity/conditionalAccess/policies' foreach ($pol in $policies) { if ($EnabledOnly -and $pol.state -ne 'enabled') { continue } # ── Gap analysis flags ───────────────────────────────────────────── $gaps = [System.Collections.Generic.List[string]]::new() # Check if policy targets all users $includeUsers = $pol.conditions.users.includeUsers $includeGroups = $pol.conditions.users.includeGroups if ($includeUsers -notcontains 'All' -and (-not $includeGroups -or $includeGroups.Count -eq 0)) { $gaps.Add('NarrowUserScope') } # Check for excluded users (potential bypass) if ($pol.conditions.users.excludeUsers -and $pol.conditions.users.excludeUsers.Count -gt 0) { $gaps.Add('HasUserExclusions') } # Check if no session controls $session = $pol.sessionControls if (-not $session -or ( -not $session.signInFrequency -and -not $session.persistentBrowser -and -not $session.applicationEnforcedRestrictions -and -not $session.cloudAppSecurity )) { $gaps.Add('NoSessionControls') } # Check if policy is report-only if ($pol.state -eq 'enabledForReportingButNotEnforced') { $gaps.Add('ReportOnlyMode') } # Check grant controls $grantControls = @() if ($pol.grantControls.builtInControls) { $grantControls = $pol.grantControls.builtInControls } # Flatten included apps $apps = @() if ($pol.conditions.applications.includeApplications) { $apps = $pol.conditions.applications.includeApplications } $results.Add([PSCustomObject]@{ PolicyName = $pol.displayName PolicyId = $pol.id State = $pol.state CreatedDateTime = if ($pol.createdDateTime) { [datetime]::Parse($pol.createdDateTime, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AssumeUniversal).ToString('yyyy-MM-dd') } else { '' } ModifiedDateTime = if ($pol.modifiedDateTime) { [datetime]::Parse($pol.modifiedDateTime, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AssumeUniversal).ToString('yyyy-MM-dd') } else { '' } IncludeUsers = ($includeUsers -join '; ') ExcludeUsers = (($pol.conditions.users.excludeUsers) -join '; ') IncludeGroups = (($includeGroups) -join '; ') IncludeApps = ($apps -join '; ') GrantControls = ($grantControls -join '; ') Gaps = if ($gaps.Count -gt 0) { $gaps -join '; ' } else { 'None' } GapCount = $gaps.Count }) } $sorted = $results | Sort-Object -Property GapCount -Descending Export-IOResult -Data $sorted -ToCsv $ToCsv -CommandName $cmdName } |