Private/SharedMailboxes.ps1
|
function Get-M365SnapshotSharedMailboxes { param( [Parameter(Mandatory=$true)] [hashtable]$GraphHeaders, [Parameter(Mandatory=$true)] [ValidateRange(1, [int]::MaxValue)] [int]$EffectiveMaxSharedMailboxes, [Parameter(Mandatory=$true)] [switch]$ReturnObjects ) $sharedMailboxes = @() $candidateUsers = @() $permissionWarningShown = $false $mailboxSettingsAccessDenied = $false try { $usersUri = "https://graph.microsoft.com/v1.0/users?`$select=id,displayName,userPrincipalName,mail,createdDateTime,accountEnabled,userType,assignedLicenses&`$top=999" do { $usersResponse = Invoke-RestMethod -Uri $usersUri -Headers $GraphHeaders -Method Get -ErrorAction Stop if ($usersResponse.value) { $candidateUsers += @($usersResponse.value | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_.mail) }) } $usersUri = $usersResponse.'@odata.nextLink' } while ($usersUri) foreach ($candidate in $candidateUsers) { if ($sharedMailboxes.Count -ge $EffectiveMaxSharedMailboxes) { break } if ([string]::IsNullOrWhiteSpace([string]$candidate.id)) { continue } try { $mailboxSettingsUri = "https://graph.microsoft.com/beta/users/$($candidate.id)/mailboxSettings?`$select=userPurpose" $mailboxSettings = Invoke-RestMethod -Uri $mailboxSettingsUri -Headers $GraphHeaders -Method Get -ErrorAction Stop $userPurpose = [string]$mailboxSettings.userPurpose if ($userPurpose -ieq 'shared') { $sharedMailboxes += [PSCustomObject]@{ DisplayName = $candidate.displayName PrimarySmtpAddress = $candidate.mail UserPrincipalName = $candidate.userPrincipalName RecipientTypeDetails = 'SharedMailbox' WhenCreatedUTC = $candidate.createdDateTime HiddenFromAddressLists = '(unknown)' DetectionSource = 'GraphMailboxSettings' } } } catch { $settingsError = [string]$_.Exception.Message if (-not $permissionWarningShown -and ($settingsError -match 'MailboxSettings.Read' -or $settingsError -match 'Insufficient privileges' -or $settingsError -match 'Authorization_RequestDenied' -or $settingsError -match 'Forbidden')) { if (-not $ReturnObjects) { Write-Host "[WARNING] Graph mailbox settings access is missing (MailboxSettings.Read). Shared mailbox detection requires this optional delegated Graph scope." -ForegroundColor Yellow Write-Host "[INFO] Falling back to heuristic detection from Entra user properties (best-effort)." -ForegroundColor DarkGray Write-Host "[INFO] For precise detection, grant delegated MailboxSettings.Read and rerun." -ForegroundColor DarkGray Write-Host "[INFO] If you use an existing app, add delegated Microsoft Graph scope MailboxSettings.Read and grant admin consent." -ForegroundColor DarkGray } $permissionWarningShown = $true $mailboxSettingsAccessDenied = $true break } } } if ($mailboxSettingsAccessDenied -and $sharedMailboxes.Count -eq 0) { $heuristicShared = @( $candidateUsers | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_.mail) -and ([bool]$_.accountEnabled -eq $false) -and (([string]$_.userType) -eq 'Member') -and (@($_.assignedLicenses).Count -eq 0) } | Select-Object -First $EffectiveMaxSharedMailboxes ) foreach ($candidate in $heuristicShared) { $sharedMailboxes += [PSCustomObject]@{ DisplayName = $candidate.displayName PrimarySmtpAddress = $candidate.mail UserPrincipalName = $candidate.userPrincipalName RecipientTypeDetails = 'LikelySharedMailbox' WhenCreatedUTC = $candidate.createdDateTime HiddenFromAddressLists = '(unknown)' DetectionSource = 'HeuristicUserProperties' } } if (-not $ReturnObjects) { Write-Host "[INFO] Heuristic detection identified $($sharedMailboxes.Count) likely shared mailbox(es)." -ForegroundColor DarkGray } } if (-not $ReturnObjects) { Write-Host "[OK] Found $($sharedMailboxes.Count) shared mailbox(es)`n" -ForegroundColor Green } } catch { if (-not $ReturnObjects) { Write-Host "[WARNING] Could not collect shared mailboxes: $($_.Exception.Message)`n" -ForegroundColor Yellow } } return [PSCustomObject]@{ SharedMailboxes = @($sharedMailboxes) } } |