Src/Private/Get-AbrIntuneConditionalAccess.ps1
|
function Get-AbrIntuneConditionalAccess { <# .SYNOPSIS Documents Entra ID Conditional Access policies. .DESCRIPTION Collects and reports on: - CA policy inventory (name, state, created/modified) - Conditions: users, apps, platforms, locations, sign-in risk - Grant and session controls - Named locations (IP ranges and country-based) .NOTES Version: 0.1.0 Added: v0.2.51 — addresses CRITICAL gap from gap tracker Endpoint: beta/identity/conditionalAccess/policies #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory)] [string]$TenantId ) begin { Write-PScriboMessage -Message "Collecting Conditional Access policies for $TenantId." Show-AbrDebugExecutionTime -Start -TitleMessage 'Conditional Access' } process { #region CA Policies try { Write-Host " - Retrieving Conditional Access policies..." $CAResp = $null try { $CAResp = Invoke-MgGraphRequest -Method GET ` -Uri "$($script:GraphEndpoint)/beta/identity/conditionalAccess/policies" ` -ErrorAction Stop } catch { Write-AbrDebugLog "CA policies unavailable: $($_.Exception.Message)" 'WARN' 'ConditionalAccess' } # Handle paging $CAPolicies = if ($CAResp -and $CAResp.value) { [System.Collections.ArrayList]@($CAResp.value) } else { $null } while ($CAResp -and $CAResp.'@odata.nextLink') { try { $CAResp = Invoke-MgGraphRequest -Method GET -Uri $CAResp.'@odata.nextLink' -ErrorAction Stop if ($CAResp.value) { $CAPolicies.AddRange([object[]]$CAResp.value) } } catch { break } } if ($CAPolicies -and $CAPolicies.Count -gt 0) { Section -Style Heading2 'Conditional Access Policies' { Paragraph "The following documents all Conditional Access policies configured in tenant $TenantId ($($CAPolicies.Count) policy/policies)." BlankLine # Summary table $CAObj = [System.Collections.ArrayList]::new() foreach ($Policy in ($CAPolicies | Sort-Object displayName)) { $stateLabel = switch ($Policy.state) { 'enabled' { 'Enabled' } 'disabled' { 'Disabled' } 'enabledForReportingButNotEnforced' { 'Report-only' } default { if ($Policy.state) { $Policy.state } else { '--' } } } # Conditions summary $cond = $Policy.conditions $usersLabel = if ($cond -and $cond.users) { $u = if ($cond -is [System.Collections.IDictionary]) { $cond['users'] } else { $cond.users } $inc = if ($u -is [System.Collections.IDictionary]) { $u['includeUsers'] } else { $u.includeUsers } if ($inc -contains 'All') { 'All users' } elseif ($inc) { "$(@($inc).Count) user/group(s)" } else { '--' } } else { '--' } $appsLabel = if ($cond) { $a = if ($cond -is [System.Collections.IDictionary]) { $cond['applications'] } else { $cond.applications } $incApps = if ($a -is [System.Collections.IDictionary]) { $a['includeApplications'] } else { $a.includeApplications } if ($incApps -contains 'All') { 'All cloud apps' } elseif ($incApps) { "$(@($incApps).Count) app(s)" } else { '--' } } else { '--' } # Grant controls $grant = $Policy.grantControls $grantLabel = if ($grant) { $op = if ($grant -is [System.Collections.IDictionary]) { $grant['operator'] } else { $grant.operator } $built = if ($grant -is [System.Collections.IDictionary]) { $grant['builtInControls'] } else { $grant.builtInControls } $parts = @() if ($built) { $parts += $built -join ", " } "$($op): $($parts -join '; ')" } else { 'Allow' } $CAObj.Add([pscustomobject]([ordered]@{ 'Policy Name' = $Policy.displayName 'State' = $stateLabel 'Users' = $usersLabel 'Apps' = $appsLabel 'Grant' = $grantLabel 'Modified' = if ($Policy.modifiedDateTime) { ([datetime]$Policy.modifiedDateTime).ToString('yyyy-MM-dd') } else { '--' } })) | Out-Null } $CAParams = @{ Name = "Conditional Access Policies - $TenantId"; ColumnWidths = 24, 11, 15, 15, 24, 11 } if ($Report.ShowTableCaptions) { $CAParams['Caption'] = "- $($CAParams.Name)" } $CAObj | Table @CAParams # InfoLevel 2 — per-policy detail if ($InfoLevel.TenantOverview -ge 2) { foreach ($Policy in ($CAPolicies | Sort-Object displayName)) { Section -Style Heading3 $Policy.displayName { BlankLine $pRows = [System.Collections.ArrayList]::new() $stateLabel = switch ($Policy.state) { 'enabled' { 'Enabled' } 'disabled' { 'Disabled' } 'enabledForReportingButNotEnforced' { 'Report-only' } default { if ($Policy.state) { $Policy.state } else { '--' } } } $pRows.Add([pscustomobject]@{ Setting = 'State'; Value = $stateLabel }) | Out-Null # Conditions $pRows.Add([pscustomobject]@{ Setting = 'Conditions'; Value = '' }) | Out-Null $cond = $Policy.conditions if ($cond) { $users = if ($cond -is [System.Collections.IDictionary]) { $cond['users'] } else { $cond.users } if ($users) { $incU = if ($users -is [System.Collections.IDictionary]) { $users['includeUsers'] } else { $users.includeUsers } $excU = if ($users -is [System.Collections.IDictionary]) { $users['excludeUsers'] } else { $users.excludeUsers } $incG = if ($users -is [System.Collections.IDictionary]) { $users['includeGroups'] } else { $users.includeGroups } $excG = if ($users -is [System.Collections.IDictionary]) { $users['excludeGroups'] } else { $users.excludeGroups } if ($incU) { $pRows.Add([pscustomobject]@{ Setting = ' Include users'; Value = $incU -join ', ' }) | Out-Null } if ($excU) { $pRows.Add([pscustomobject]@{ Setting = ' Exclude users'; Value = $excU -join ', ' }) | Out-Null } if ($incG) { $pRows.Add([pscustomobject]@{ Setting = ' Include groups'; Value = ($incG | ForEach-Object { Resolve-IntuneGroupName -GroupId $_ }) -join '; ' }) | Out-Null } if ($excG) { $pRows.Add([pscustomobject]@{ Setting = ' Exclude groups'; Value = ($excG | ForEach-Object { Resolve-IntuneGroupName -GroupId $_ }) -join '; ' }) | Out-Null } } $apps = if ($cond -is [System.Collections.IDictionary]) { $cond['applications'] } else { $cond.applications } if ($apps) { $incA = if ($apps -is [System.Collections.IDictionary]) { $apps['includeApplications'] } else { $apps.includeApplications } $excA = if ($apps -is [System.Collections.IDictionary]) { $apps['excludeApplications'] } else { $apps.excludeApplications } if ($incA) { $pRows.Add([pscustomobject]@{ Setting = ' Include apps'; Value = $incA -join ', ' }) | Out-Null } if ($excA) { $pRows.Add([pscustomobject]@{ Setting = ' Exclude apps'; Value = $excA -join ', ' }) | Out-Null } } $platforms = if ($cond -is [System.Collections.IDictionary]) { $cond['platforms'] } else { $cond.platforms } if ($platforms) { $incP = if ($platforms -is [System.Collections.IDictionary]) { $platforms['includePlatforms'] } else { $platforms.includePlatforms } if ($incP) { $pRows.Add([pscustomobject]@{ Setting = ' Platforms'; Value = $incP -join ', ' }) | Out-Null } } $riskLevels = if ($cond -is [System.Collections.IDictionary]) { $cond['signInRiskLevels'] } else { $cond.signInRiskLevels } if ($riskLevels) { $pRows.Add([pscustomobject]@{ Setting = ' Sign-in risk levels'; Value = $riskLevels -join ', ' }) | Out-Null } } # Grant controls $pRows.Add([pscustomobject]@{ Setting = 'Grant Controls'; Value = '' }) | Out-Null $grant = $Policy.grantControls if ($grant) { $op = if ($grant -is [System.Collections.IDictionary]) { $grant['operator'] } else { $grant.operator } $built = if ($grant -is [System.Collections.IDictionary]) { $grant['builtInControls'] } else { $grant.builtInControls } $custom = if ($grant -is [System.Collections.IDictionary]) { $grant['customAuthenticationFactors'] } else { $grant.customAuthenticationFactors } if ($op) { $pRows.Add([pscustomobject]@{ Setting = ' Operator'; Value = $op }) | Out-Null } if ($built) { $pRows.Add([pscustomobject]@{ Setting = ' Built-in controls'; Value = $built -join ', ' }) | Out-Null } if ($custom) { $pRows.Add([pscustomobject]@{ Setting = ' Custom factors'; Value = $custom -join ', ' }) | Out-Null } } else { $pRows.Add([pscustomobject]@{ Setting = ' Action'; Value = 'Allow (no controls)' }) | Out-Null } $null = ($pRows | Where-Object { $_.Setting -in @('Conditions','Grant Controls') } | Set-Style -Style 'TableSectionHeader') $PParams = @{ Name = "CA Policy Detail - $($Policy.displayName)"; ColumnWidths = 35, 65 } if ($Report.ShowTableCaptions) { $PParams['Caption'] = "- $($PParams.Name)" } $pRows | Table @PParams } } } } } else { Write-AbrDebugLog 'No Conditional Access policies found.' 'INFO' 'ConditionalAccess' } } catch { if (Test-AbrGraphForbidden -ErrorRecord $_) { Write-AbrPermissionError -Section 'Conditional Access' -RequiredRole 'Global Administrator or Security Administrator' } else { Write-AbrSectionError -Section 'Conditional Access' -Message "$($_.Exception.Message)" } } #endregion #region Named Locations try { Write-Host " - Retrieving Named Locations..." $NLResp = $null try { $NLResp = Invoke-MgGraphRequest -Method GET -Uri "$($script:GraphEndpoint)/beta/identity/conditionalAccess/namedLocations" -ErrorAction Stop } catch { Write-AbrDebugLog "namedLocations unavailable: $($_.Exception.Message)" 'WARN' 'ConditionalAccess' } if ($NLResp -and $NLResp.value -and @($NLResp.value).Count -gt 0) { Section -Style Heading2 'Named Locations' { Paragraph "Named Locations are used in Conditional Access conditions to define trusted IP ranges and country-based locations." BlankLine $NLObj = [System.Collections.ArrayList]::new() foreach ($Loc in ($NLResp.value | Sort-Object displayName)) { $locType = if ($Loc.'@odata.type' -like '*ipNamedLocation') { 'IP Range' } elseif ($Loc.'@odata.type' -like '*countryNamedLocation') { 'Country' } else { '--' } $detail = if ($Loc.ipRanges) { "$(@($Loc.ipRanges).Count) IP range(s)" } elseif ($Loc.countriesAndRegions) { $Loc.countriesAndRegions -join ', ' } else { '--' } $NLObj.Add([pscustomobject]([ordered]@{ 'Location Name' = $Loc.displayName 'Type' = $locType 'Trusted' = if ($null -ne $Loc.isTrusted) { if ($Loc.isTrusted) { 'Yes' } else { 'No' } } else { '--' } 'Detail' = $detail })) | Out-Null } $NLParams = @{ Name = "Named Locations - $TenantId"; ColumnWidths = 30, 12, 10, 48 } if ($Report.ShowTableCaptions) { $NLParams['Caption'] = "- $($NLParams.Name)" } $NLObj | Table @NLParams } } } catch { if (Test-AbrGraphForbidden -ErrorRecord $_) { Write-AbrPermissionError -Section 'Named Locations' -RequiredRole 'Global Administrator or Security Administrator' } else { Write-AbrSectionError -Section 'Named Locations' -Message "$($_.Exception.Message)" } } #endregion } end { Show-AbrDebugExecutionTime -End -TitleMessage 'Conditional Access' } } |