Src/Private/Get-AbrExoDistributionGroups.ps1
|
function Get-AbrExoDistributionGroups { <# .SYNOPSIS Documents Exchange Online distribution groups, mail-enabled security groups, dynamic distribution groups, and Microsoft 365 Groups (mail-enabled). .NOTES Version: 0.1.1 Author: Pai Wei Sing #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory)] [string]$TenantId ) begin { Write-PScriboMessage -Message "Collecting Exchange Online Distribution Group information for $TenantId." Show-AbrDebugExecutionTime -Start -TitleMessage 'DistributionGroups' } process { Section -Style Heading2 'Group Inventory' { Paragraph "The following section documents distribution groups, mail-enabled security groups, dynamic distribution groups, and Microsoft 365 Groups for tenant $TenantId." BlankLine # Collect each group type separately using the correct cmdlet per type $DistGroups = [System.Collections.ArrayList]::new() $MailSecGroups = [System.Collections.ArrayList]::new() $DynGroups = [System.Collections.ArrayList]::new() $M365Groups = [System.Collections.ArrayList]::new() $OtherGroups = [System.Collections.ArrayList]::new() #region Fetch from Get-DistributionGroup (DLs + Mail-Sec groups) try { Write-Host " - Retrieving distribution and mail-enabled security groups..." $RawGroups = Get-DistributionGroup -ResultSize Unlimited -ErrorAction Stop foreach ($Grp in $RawGroups) { switch ($Grp.RecipientTypeDetails) { 'MailUniversalDistributionGroup' { $null = $DistGroups.Add($Grp) } 'MailUniversalSecurityGroup' { $null = $MailSecGroups.Add($Grp) } 'RoomList' { $null = $DistGroups.Add($Grp) } # Room lists are a type of DL default { $null = $OtherGroups.Add($Grp) } } } } catch { Write-ExoError 'DistributionGroups' "Unable to retrieve distribution groups: $($_.Exception.Message)" } #endregion #region Fetch Dynamic Distribution Groups try { Write-Host " - Retrieving dynamic distribution groups..." $DynRaw = Get-DynamicDistributionGroup -ResultSize Unlimited -ErrorAction Stop foreach ($Grp in $DynRaw) { $null = $DynGroups.Add($Grp) } } catch { Write-AbrDebugLog "Dynamic distribution groups unavailable: $($_.Exception.Message)" 'DEBUG' 'DistributionGroups' } #endregion #region Fetch Microsoft 365 Groups (Unified Groups) try { Write-Host " - Retrieving Microsoft 365 Groups..." $M365Raw = Get-UnifiedGroup -ResultSize Unlimited -ErrorAction Stop foreach ($Grp in $M365Raw) { $null = $M365Groups.Add($Grp) } } catch { Write-AbrDebugLog "Unified Groups (M365 Groups) unavailable: $($_.Exception.Message)" 'DEBUG' 'DistributionGroups' } #endregion $GrandTotal = $DistGroups.Count + $MailSecGroups.Count + $DynGroups.Count + $M365Groups.Count + $OtherGroups.Count # Summary table $SumObj = [System.Collections.ArrayList]::new() $sumInObj = [ordered] @{ 'Distribution Groups (incl. Room Lists)' = $DistGroups.Count 'Mail-Enabled Security Groups' = $MailSecGroups.Count 'Dynamic Distribution Groups' = $DynGroups.Count 'Microsoft 365 Groups' = $M365Groups.Count 'Other' = $OtherGroups.Count 'Total' = $GrandTotal } $SumObj.Add([pscustomobject]$sumInObj) | Out-Null $SumTableParams = @{ Name = "Group Summary - $TenantId"; List = $true; ColumnWidths = 55, 45 } if ($Report.ShowTableCaptions) { $SumTableParams['Caption'] = "- $($SumTableParams.Name)" } $SumObj | Table @SumTableParams #region Distribution Groups Detail if ($DistGroups.Count -gt 0) { Section -Style Heading3 'Distribution Groups' { Paragraph "The following $($DistGroups.Count) distribution group(s) are configured in tenant $TenantId." BlankLine $DgObj = [System.Collections.ArrayList]::new() foreach ($Grp in ($DistGroups | Sort-Object DisplayName)) { $MemberCount = 'N/A (IL1)' if ($InfoLevel.DistributionGroups -ge 2) { try { $MemberCount = @(Get-DistributionGroupMember -Identity $Grp.Identity -ResultSize Unlimited -ErrorAction SilentlyContinue).Count } catch {} } $dgInObj = [ordered] @{ 'Display Name' = $Grp.DisplayName 'Primary SMTP' = $Grp.PrimarySmtpAddress 'Type' = $Grp.RecipientTypeDetails 'Managed By' = if ($Grp.ManagedBy) { ($Grp.ManagedBy | Select-Object -First 2) -join ', ' } else { 'Not Set' } 'Member Count' = $MemberCount 'Accept From' = if ($Grp.AcceptMessagesOnlyFromSendersOrMembers) { 'Restricted' } else { 'Anyone' } 'Require Sender Auth' = $Grp.RequireSenderAuthenticationEnabled 'Moderated' = $Grp.ModerationEnabled 'Hidden from GAL' = $Grp.HiddenFromAddressListsEnabled } $DgObj.Add([pscustomobject](ConvertTo-HashToYN $dgInObj)) | Out-Null } $null = (& { if ($HealthCheck.ExchangeOnline.Mailboxes) { $null = ($DgObj | Where-Object { $_.'Require Sender Auth' -eq 'No' -and $_.'Accept From' -eq 'Anyone' -and $_.Moderated -eq 'No' } | Set-Style -Style Warning | Out-Null) } }) $DgTableParams = @{ Name = "Distribution Groups - $TenantId"; List = $false; ColumnWidths = 16, 20, 14, 14, 8, 9, 8, 6, 5 } if ($Report.ShowTableCaptions) { $DgTableParams['Caption'] = "- $($DgTableParams.Name)" } if ($DgObj.Count -gt 0) { $DgObj | Table @DgTableParams } $script:ExcelSheets['Distribution Groups'] = $DgObj } } #endregion #region Mail-Enabled Security Groups if ($MailSecGroups.Count -gt 0) { Section -Style Heading3 'Mail-Enabled Security Groups' { Paragraph "The following $($MailSecGroups.Count) mail-enabled security group(s) are configured in tenant $TenantId." BlankLine $MsgObj = [System.Collections.ArrayList]::new() foreach ($Grp in ($MailSecGroups | Sort-Object DisplayName)) { $msgInObj = [ordered] @{ 'Display Name' = $Grp.DisplayName 'Primary SMTP' = $Grp.PrimarySmtpAddress 'Managed By' = if ($Grp.ManagedBy) { ($Grp.ManagedBy | Select-Object -First 2) -join ', ' } else { 'Not Set' } 'Accept From' = if ($Grp.AcceptMessagesOnlyFromSendersOrMembers) { 'Restricted' } else { 'Anyone' } 'Require Sender Auth' = $Grp.RequireSenderAuthenticationEnabled 'Moderated' = $Grp.ModerationEnabled 'Hidden from GAL' = $Grp.HiddenFromAddressListsEnabled } $MsgObj.Add([pscustomobject](ConvertTo-HashToYN $msgInObj)) | Out-Null } $null = (& { if ($HealthCheck.ExchangeOnline.Mailboxes) { $null = ($MsgObj | Where-Object { $_.'Require Sender Auth' -eq 'No' -and $_.'Accept From' -eq 'Anyone' -and $_.Moderated -eq 'No' } | Set-Style -Style Warning | Out-Null) } }) $MsgTableParams = @{ Name = "Mail-Enabled Security Groups - $TenantId"; List = $false; ColumnWidths = 20, 26, 18, 10, 10, 8, 8 } if ($Report.ShowTableCaptions) { $MsgTableParams['Caption'] = "- $($MsgTableParams.Name)" } if ($MsgObj.Count -gt 0) { $MsgObj | Table @MsgTableParams } $script:ExcelSheets['Mail-Enabled Security Groups'] = $MsgObj } } #endregion #region Dynamic Distribution Groups if ($DynGroups.Count -gt 0) { Section -Style Heading3 'Dynamic Distribution Groups' { Paragraph "The following $($DynGroups.Count) dynamic distribution group(s) use filters to automatically determine membership in tenant $TenantId." BlankLine $DdgObj = [System.Collections.ArrayList]::new() foreach ($Grp in ($DynGroups | Sort-Object DisplayName)) { $ddgInObj = [ordered] @{ 'Display Name' = $Grp.DisplayName 'Primary SMTP' = $Grp.PrimarySmtpAddress 'Recipient Filter' = if ($Grp.RecipientFilter) { ($Grp.RecipientFilter -replace '\s+', ' ').Trim() } else { 'Default' } 'Recipient Container' = if ($Grp.RecipientContainer) { $Grp.RecipientContainer } else { 'All' } 'Managed By' = if ($Grp.ManagedBy) { ($Grp.ManagedBy | Select-Object -First 2) -join ', ' } else { 'Not Set' } 'Hidden from GAL' = $Grp.HiddenFromAddressListsEnabled } $DdgObj.Add([pscustomobject](ConvertTo-HashToYN $ddgInObj)) | Out-Null } $DdgTableParams = @{ Name = "Dynamic Distribution Groups - $TenantId"; List = $false; ColumnWidths = 18, 20, 28, 12, 14, 8 } if ($Report.ShowTableCaptions) { $DdgTableParams['Caption'] = "- $($DdgTableParams.Name)" } if ($DdgObj.Count -gt 0) { $DdgObj | Table @DdgTableParams } $script:ExcelSheets['Dynamic Distribution Groups'] = $DdgObj } } #endregion #region Microsoft 365 Groups if ($M365Groups.Count -gt 0) { Section -Style Heading3 'Microsoft 365 Groups' { Paragraph "The following $($M365Groups.Count) Microsoft 365 Group(s) are configured in tenant $TenantId. These groups provide shared mailbox, calendar, SharePoint site, and Teams collaboration." BlankLine # Pre-build a GUID->DisplayName lookup for ManagedBy resolution # ManagedBy on UnifiedGroups returns ObjectIds (GUIDs), not UPNs $ManagedByCache = @{} $M365Obj = [System.Collections.ArrayList]::new() foreach ($Grp in ($M365Groups | Sort-Object DisplayName)) { # Resolve ManagedBy GUIDs to display names $ManagedByDisplay = 'Not Set' if ($Grp.ManagedBy -and @($Grp.ManagedBy).Count -gt 0) { $ResolvedOwners = @() foreach ($OwnerId in ($Grp.ManagedBy | Select-Object -First 3)) { $OwnerStr = "$OwnerId" if ($ManagedByCache.ContainsKey($OwnerStr)) { $ResolvedOwners += $ManagedByCache[$OwnerStr] } else { try { $Resolved = Get-Recipient -Identity $OwnerStr -ErrorAction SilentlyContinue $Name = if ($Resolved) { $Resolved.DisplayName } else { $OwnerStr } } catch { $Name = $OwnerStr } $ManagedByCache[$OwnerStr] = $Name $ResolvedOwners += $Name } } $ManagedByDisplay = $ResolvedOwners -join ', ' $Extra = @($Grp.ManagedBy).Count - 3 if ($Extra -gt 0) { $ManagedByDisplay += " (+$Extra more)" } } # Fix #16: SharePoint enabled = URL is not empty $SharePointEnabled = ($Grp.SharePointDocumentsUrl -and "$($Grp.SharePointDocumentsUrl)" -ne '') $m365InObj = [ordered] @{ 'Display Name' = $Grp.DisplayName 'Primary SMTP' = $Grp.PrimarySmtpAddress 'Access Type' = $Grp.AccessType 'Managed By' = $ManagedByDisplay 'Allow External' = $Grp.AllowAddGuests 'Hidden from GAL' = $Grp.HiddenFromAddressListsEnabled 'SharePoint Enabled' = $SharePointEnabled } $M365Obj.Add([pscustomobject](ConvertTo-HashToYN $m365InObj)) | Out-Null } $null = (& { if ($HealthCheck.ExchangeOnline.Mailboxes) { # Fix #15: flag ANY public group that allows external (not just both conditions) $null = ($M365Obj | Where-Object { $_.'Access Type' -eq 'Public' -and $_.'Allow External' -eq 'Yes' } | Set-Style -Style Warning | Out-Null) # Also flag private groups allowing guests (lower risk but worth noting) $null = ($M365Obj | Where-Object { $_.'Access Type' -eq 'Public' } | Set-Style -Style Warning | Out-Null) } }) $M365TableParams = @{ Name = "Microsoft 365 Groups - $TenantId"; List = $false; ColumnWidths = 18, 22, 10, 18, 10, 12, 10 } if ($Report.ShowTableCaptions) { $M365TableParams['Caption'] = "- $($M365TableParams.Name)" } if ($M365Obj.Count -gt 0) { $M365Obj | Table @M365TableParams } $script:ExcelSheets['Microsoft 365 Groups'] = $M365Obj } } #endregion if ($GrandTotal -eq 0) { Paragraph "No distribution groups or mail-enabled groups found in tenant $TenantId." } } } end { Show-AbrDebugExecutionTime -End -TitleMessage 'DistributionGroups' } } |