Private/AuthContext/DataCollection/Get-ConditionalAccessPoliciesWithAuthContext.ps1
|
function Get-ConditionalAccessPoliciesWithAuthContext { <# .SYNOPSIS Retrieves all Conditional Access policies and extracts any Authentication Context references. .DESCRIPTION Enumerates Conditional Access policies via Microsoft Graph (beta) handling pagination. For each policy it parses both structured properties (conditions.applications.* / conditions.authenticationContext.* / grantControls) and, as a fallback, performs targeted JSON regex extraction to capture legacy / nested representations of authenticationContextIds or authenticationContextClassReferences (including include* variants). Returns only policies containing at least one reference, with summarized grant & session control indicators. .PARAMETER AuthContexts Collection of authentication context objects (Id, DisplayName) used to map IDs / class references to names. .OUTPUTS PSCustomObject with: PolicyName, PolicyId, State, AuthContextIds, AuthContextClassRefs, AuthContextNames, GrantControls, SessionControls. .NOTES Uses Graph beta endpoint (identity/conditionalAccess/policies). Progress appears under Id 4 unless -NoProgress. Falls back to regex parsing to accommodate tenants exposing legacy schema shapes. .EXAMPLE $ca = Get-ConditionalAccessPoliciesWithAuthContext -AuthContexts $authContexts Retrieves all CA policies that reference any authentication context. #> [CmdletBinding()] param( [Parameter(Mandatory)][object[]]$AuthContexts ) $conditionalAccessPolicies = @() try { $conditionalAccessApiUri = 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' $policyPageIndex = 0 while ($conditionalAccessApiUri) { $conditionalAccessResponse = Invoke-MgGraphRequest -Method GET -Uri $conditionalAccessApiUri -ErrorAction Stop if ($conditionalAccessResponse.value) { foreach ($policyObject in $conditionalAccessResponse.value) { $conditionalAccessPolicies += $policyObject $policyPageIndex++ $processingPercent = if ($policyPageIndex -lt 1000) { [math]::Min(100, [int](($policyPageIndex / 10) * 10)) } else { 100 } $currentPolicyName = $policyObject.displayName if (-not $NoProgress) { Write-Progress -Id 4 -Activity 'Conditional Access Policies' -Status "Retrieved: $currentPolicyName (count=$policyPageIndex)" -PercentComplete $processingPercent } } } $conditionalAccessApiUri = $conditionalAccessResponse.'@odata.nextLink' } if (-not $NoProgress) { Write-Progress -Id 4 -Activity 'Conditional Access Policies' -Completed -Status 'Done' } } catch { Write-Warning "Failed to retrieve conditional access policies: $($_.Exception.Message)" } if (-not $conditionalAccessPolicies) { return @() } $authenticationContextById = @{} foreach ($authenticationContext in $AuthContexts) { $authenticationContextById[$authenticationContext.Id] = $authenticationContext.DisplayName } $conditionalAccessPoliciesWithAuthContext = foreach ($currentPolicy in $conditionalAccessPolicies) { $authenticationContextIds = @(); $authenticationContextClassReferences = @() if ($currentPolicy.conditions -and $currentPolicy.conditions.applications -and $currentPolicy.conditions.applications.authenticationContextClassReferences) { $authenticationContextClassReferences += $currentPolicy.conditions.applications.authenticationContextClassReferences } if ($currentPolicy.conditions -and $currentPolicy.conditions.authenticationContext -and $currentPolicy.conditions.authenticationContext.authenticationContextIds) { $authenticationContextIds += $currentPolicy.conditions.authenticationContext.authenticationContextIds } # Some tenants expose Authentication Context arrays within grant controls instead of conditions if ($currentPolicy.grantControls) { $policyGrantControls = $currentPolicy.grantControls foreach ($authContextProperty in 'authenticationContextClassReferences', 'authenticationContextIds') { if ($policyGrantControls.PSObject.Properties.Name -contains $authContextProperty -and $policyGrantControls.$authContextProperty) { if ($authContextProperty -eq 'authenticationContextClassReferences') { $authenticationContextClassReferences += $policyGrantControls.$authContextProperty } else { $authenticationContextIds += $policyGrantControls.$authContextProperty } } } } if ($authenticationContextIds.Count -eq 0 -and $authenticationContextClassReferences.Count -eq 0) { try { $policyRawJson = $currentPolicy | ConvertTo-Json -Depth 20 -Compress if ($policyRawJson -match 'authenticationContext') { # Extract Authentication Context IDs and Class References from JSON even when deeply nested $contextIdMatches = [regex]::Matches($policyRawJson, '"authenticationContextIds"\s*:\s*\[(.*?)\]') foreach ($contextIdMatchItem in $contextIdMatches) { $authenticationContextIds += ([regex]::Matches($contextIdMatchItem.Groups[1].Value, '"([0-9a-fA-F-]{32,36})"') | ForEach-Object { $_.Groups[1].Value }) } $contextClassRefMatches = [regex]::Matches($policyRawJson, '"authenticationContextClassReferences"\s*:\s*\[(.*?)\]') foreach ($contextClassRefMatchItem in $contextClassRefMatches) { $authenticationContextClassReferences += ([regex]::Matches($contextClassRefMatchItem.Groups[1].Value, '"([^"\\]+)"') | ForEach-Object { $_.Groups[1].Value }) } # Handle singular variants if ($policyRawJson -match '"authenticationContextIds"\s*:\s*"([0-9a-fA-F-]{32,36})"') { $authenticationContextIds += $Matches[1] } if ($policyRawJson -match '"authenticationContextClassReferences"\s*:\s*"([^"]+)"') { $authenticationContextClassReferences += $Matches[1] } # Legacy include* property variants $includeClassReferencesMatches = [regex]::Matches($policyRawJson, '"includeAuthenticationContextClassReferences"\s*:\s*\[(.*?)\]') foreach ($includeClassRefMatchItem in $includeClassReferencesMatches) { $authenticationContextClassReferences += ([regex]::Matches($includeClassRefMatchItem.Groups[1].Value, '"([^"\\]+)"') | ForEach-Object { $_.Groups[1].Value }) } if ($policyRawJson -match '"includeAuthenticationContextClassReferences"\s*:\s*"([^"]+)"') { $authenticationContextClassReferences += $Matches[1] } $includeIdMatches = [regex]::Matches($policyRawJson, '"includeAuthenticationContextIds"\s*:\s*\[(.*?)\]') foreach ($includeIdMatchItem in $includeIdMatches) { $authenticationContextIds += ([regex]::Matches($includeIdMatchItem.Groups[1].Value, '"([0-9a-fA-F-]{32,36})"') | ForEach-Object { $_.Groups[1].Value }) } if ($policyRawJson -match '"includeAuthenticationContextIds"\s*:\s*"([0-9a-fA-F-]{32,36})"') { $authenticationContextIds += $Matches[1] } # Generic pattern matching for class references like "c1", "c2" etc. if ($authenticationContextClassReferences.Count -eq 0) { $genericClassReferencesMatches = [regex]::Matches($policyRawJson, '"c[0-9]+"') | ForEach-Object { $_.Value.Trim('"') } if ($genericClassReferencesMatches) { $authenticationContextClassReferences += $genericClassReferencesMatches } } } } catch { } } $authenticationContextIds = $authenticationContextIds | Sort-Object -Unique $authenticationContextClassReferences = $authenticationContextClassReferences | Sort-Object -Unique if ($authenticationContextIds.Count -gt 0 -or $authenticationContextClassReferences.Count -gt 0) { $mappedAuthenticationContextNames = @() foreach ($authenticationContextId in $authenticationContextIds) { if ($authenticationContextById.ContainsKey($authenticationContextId)) { $mappedAuthenticationContextNames += $authenticationContextById[$authenticationContextId] } } foreach ($authenticationContextClassReference in $authenticationContextClassReferences) { if ($authenticationContextById.ContainsKey($authenticationContextClassReference)) { $mappedAuthenticationContextNames += $authenticationContextById[$authenticationContextClassReference] } } $mappedAuthenticationContextNames = $mappedAuthenticationContextNames | Sort-Object -Unique $grantControlsSummary = $null if ($currentPolicy.grantControls) { $currentPolicyGrantControls = $currentPolicy.grantControls if ($currentPolicyGrantControls.builtInControls) { $grantControlsSummary = ($currentPolicyGrantControls.builtInControls -join '+') } elseif ($currentPolicyGrantControls.customAuthenticationFactors) { $grantControlsSummary = 'CustomAuthFactors' } elseif ($currentPolicyGrantControls.termsOfUse) { $grantControlsSummary = 'TOU' } } $sessionControlsSummary = $null if ($currentPolicy.sessionControls) { $sessionControlsParts = @(); $currentPolicySessionControls = $currentPolicy.sessionControls if ($currentPolicySessionControls.applicationEnforcedRestrictions) { $sessionControlsParts += 'AER' } if ($currentPolicySessionControls.persistentBrowser) { $sessionControlsParts += 'PersistentBrowser' } if ($sessionControlsParts) { $sessionControlsSummary = ($sessionControlsParts -join '+') } } [pscustomobject]@{ PolicyName = $currentPolicy.displayName PolicyId = $currentPolicy.id State = $currentPolicy.state AuthContextIds = ($authenticationContextIds -join ',') AuthContextClassRefs = ($authenticationContextClassReferences -join ',') AuthContextNames = ($mappedAuthenticationContextNames -join ',') GrantControls = $grantControlsSummary SessionControls = $sessionControlsSummary } } } return $conditionalAccessPoliciesWithAuthContext | Sort-Object PolicyName } |