|
| 1 | +function Get-CIPPAlertTenantAccess { |
| 2 | + <# |
| 3 | + .FUNCTIONALITY |
| 4 | + Entrypoint |
| 5 | + #> |
| 6 | + [CmdletBinding()] |
| 7 | + param( |
| 8 | + [Parameter(Mandatory = $false)] |
| 9 | + [Alias('input')] |
| 10 | + $InputValue, |
| 11 | + $TenantFilter |
| 12 | + ) |
| 13 | + |
| 14 | + $ExpectedRoles = @( |
| 15 | + @{ Name = 'Application Administrator'; Id = '9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3' }, |
| 16 | + @{ Name = 'User Administrator'; Id = 'fe930be7-5e62-47db-91af-98c3a49a38b1' }, |
| 17 | + @{ Name = 'Intune Administrator'; Id = '3a2c62db-5318-420d-8d74-23affee5d9d5' }, |
| 18 | + @{ Name = 'Exchange Administrator'; Id = '29232cdf-9323-42fd-ade2-1d097af3e4de' }, |
| 19 | + @{ Name = 'Security Administrator'; Id = '194ae4cb-b126-40b2-bd5b-6091b380977d' }, |
| 20 | + @{ Name = 'Cloud App Security Administrator'; Id = '892c5842-a9a6-463a-8041-72aa08ca3cf6' }, |
| 21 | + @{ Name = 'Cloud Device Administrator'; Id = '7698a772-787b-4ac8-901f-60d6b08affd2' }, |
| 22 | + @{ Name = 'Teams Administrator'; Id = '69091246-20e8-4a56-aa4d-066075b2a7a8' }, |
| 23 | + @{ Name = 'SharePoint Administrator'; Id = 'f28a1f50-f6e7-4571-818b-6a12f2af6b6c' }, |
| 24 | + @{ Name = 'Authentication Policy Administrator'; Id = '0526716b-113d-4c15-b2c8-68e3c22b9f80' }, |
| 25 | + @{ Name = 'Privileged Role Administrator'; Id = 'e8611ab8-c189-46e8-94e1-60213ab1f814' }, |
| 26 | + @{ Name = 'Privileged Authentication Administrator'; Id = '7be44c8a-adaf-4e2a-84d6-ab2649e08a13' }, |
| 27 | + @{ Name = 'Billing Administrator'; Id = 'b0f54661-2d74-4c50-afa3-1ec803f12efe'; Optional = $true }, |
| 28 | + @{ Name = 'Global Reader'; Id = 'f2ef992c-3afb-46b9-b7cf-a126ee74c451'; Optional = $true }, |
| 29 | + @{ Name = 'Domain Name Administrator'; Id = '8329153b-31d0-4727-b945-745eb3bc5f31'; Optional = $true } |
| 30 | + ) |
| 31 | + |
| 32 | + try { |
| 33 | + $Tenant = Get-Tenants -TenantFilter $TenantFilter -IncludeErrors |
| 34 | + if (-not $Tenant) { |
| 35 | + return |
| 36 | + } |
| 37 | + $TenantId = $Tenant.customerId |
| 38 | + $Issues = [System.Collections.Generic.List[object]]::new() |
| 39 | + |
| 40 | + # Test Graph API connectivity and GDAP role assignments |
| 41 | + $GraphStatus = $false |
| 42 | + $GraphMessage = '' |
| 43 | + try { |
| 44 | + $BulkRequests = $ExpectedRoles | ForEach-Object { |
| 45 | + @{ |
| 46 | + id = "roleManagement_$($_.Id)" |
| 47 | + method = 'GET' |
| 48 | + url = "roleManagement/directory/roleAssignments?`$filter=roleDefinitionId eq '$($_.Id)'&`$expand=principal" |
| 49 | + } |
| 50 | + } |
| 51 | + $GDAPRolesGraph = New-GraphBulkRequest -tenantid $TenantId -Requests $BulkRequests |
| 52 | + $MissingRoles = [System.Collections.Generic.List[string]]::new() |
| 53 | + |
| 54 | + foreach ($RoleId in $ExpectedRoles) { |
| 55 | + $GraphRole = $GDAPRolesGraph.body.value | Where-Object -Property roleDefinitionId -EQ $RoleId.Id |
| 56 | + $Role = $GraphRole.principal | Where-Object -Property organizationId -EQ $env:TenantID |
| 57 | + if (-not $Role -and $RoleId.Optional -ne $true) { |
| 58 | + $MissingRoles.Add($RoleId.Name) |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + $GraphStatus = $true |
| 63 | + if ($MissingRoles.Count -gt 0) { |
| 64 | + $GraphMessage = "Graph connected but missing required GDAP roles: $($MissingRoles -join ', ')" |
| 65 | + $Issues.Add([PSCustomObject]@{ |
| 66 | + Issue = 'MissingGDAPRoles' |
| 67 | + Message = $GraphMessage |
| 68 | + MissingRoles = ($MissingRoles -join ', ') |
| 69 | + Tenant = $TenantFilter |
| 70 | + }) |
| 71 | + } |
| 72 | + } catch { |
| 73 | + $ErrorMessage = Get-NormalizedError -message $_.Exception.Message |
| 74 | + $GraphMessage = "Failed to connect to Graph API: $ErrorMessage" |
| 75 | + $Issues.Add([PSCustomObject]@{ |
| 76 | + Issue = 'GraphFailure' |
| 77 | + Message = $GraphMessage |
| 78 | + Tenant = $TenantFilter |
| 79 | + }) |
| 80 | + } |
| 81 | + |
| 82 | + # Test Exchange Online connectivity |
| 83 | + $ExchangeStatus = $false |
| 84 | + try { |
| 85 | + $null = New-ExoRequest -tenantid $TenantId -cmdlet 'Get-OrganizationConfig' -ErrorAction Stop |
| 86 | + $ExchangeStatus = $true |
| 87 | + } catch { |
| 88 | + $ErrorMessage = Get-NormalizedError -message $_.Exception.Message |
| 89 | + $Issues.Add([PSCustomObject]@{ |
| 90 | + Issue = 'ExchangeFailure' |
| 91 | + Message = "Failed to connect to Exchange Online: $ErrorMessage" |
| 92 | + Tenant = $TenantFilter |
| 93 | + }) |
| 94 | + } |
| 95 | + |
| 96 | + # Build alert data only if there are issues |
| 97 | + $AlertData = @() |
| 98 | + if (-not $GraphStatus -and -not $ExchangeStatus) { |
| 99 | + $AlertData = @([PSCustomObject]@{ |
| 100 | + Message = "Tenant $TenantFilter is inaccessible. Graph API and Exchange Online connectivity both failed. This tenant may have removed GDAP permissions or requires consent refresh." |
| 101 | + GraphStatus = $false |
| 102 | + ExchangeStatus = $false |
| 103 | + Issues = ($Issues | ForEach-Object { $_.Message }) -join '; ' |
| 104 | + Tenant = $TenantFilter |
| 105 | + }) |
| 106 | + } elseif (-not $GraphStatus) { |
| 107 | + $AlertData = @([PSCustomObject]@{ |
| 108 | + Message = "Tenant $TenantFilter has lost Graph API access. $GraphMessage" |
| 109 | + GraphStatus = $false |
| 110 | + ExchangeStatus = $ExchangeStatus |
| 111 | + Issues = ($Issues | ForEach-Object { $_.Message }) -join '; ' |
| 112 | + Tenant = $TenantFilter |
| 113 | + }) |
| 114 | + } elseif (-not $ExchangeStatus) { |
| 115 | + $AlertData = @([PSCustomObject]@{ |
| 116 | + Message = "Tenant $TenantFilter has lost Exchange Online access. This may indicate missing Exchange Administrator GDAP role or removed consent." |
| 117 | + GraphStatus = $GraphStatus |
| 118 | + ExchangeStatus = $false |
| 119 | + Issues = ($Issues | ForEach-Object { $_.Message }) -join '; ' |
| 120 | + Tenant = $TenantFilter |
| 121 | + }) |
| 122 | + } elseif ($MissingRoles.Count -gt 0) { |
| 123 | + $AlertData = @([PSCustomObject]@{ |
| 124 | + Message = "Tenant $TenantFilter is accessible but missing required GDAP roles: $($MissingRoles -join ', '). This may indicate a CIPP-SAM permission update is needed." |
| 125 | + GraphStatus = $GraphStatus |
| 126 | + ExchangeStatus = $ExchangeStatus |
| 127 | + MissingRoles = ($MissingRoles -join ', ') |
| 128 | + Tenant = $TenantFilter |
| 129 | + }) |
| 130 | + } |
| 131 | + |
| 132 | + Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData |
| 133 | + } catch { |
| 134 | + Write-LogMessage -API 'Alerts' -tenant $TenantFilter -message "Tenant access alert error for $($TenantFilter): $(Get-NormalizedError -message $_.Exception.Message)" -sev Error |
| 135 | + } |
| 136 | +} |
0 commit comments