Public/Get-MailGroupWithMember.ps1
|
function Get-MailGroupWithMembers { <# .SYNOPSIS List Distribution Groups and Mail-Enabled Security Groups in AD with members. .DESCRIPTION - Includes all Distribution Groups (regardless of mail attributes) - Includes only Security Groups that are mail-enabled (mail or proxyAddresses present) - Labels type and lists members (direct or recursive) - Optionally exports results to CSV .PARAMETER SearchBase Optional DN to scope the search (e.g., "OU=Groups,DC=corp,DC=contoso,DC=com") .PARAMETER Recursive If provided, expands membership recursively. .PARAMETER ExportCsvPath Optional file path to export the flattened membership list as CSV. .EXAMPLE .\Get-MailGroupsWithMembers.ps1 -Recursive -ExportCsvPath C:\Temp\Groups.csv .EXAMPLE .\Get-MailGroupsWithMembers.ps1 -SearchBase "OU=Email,DC=example,DC=com" #> [CmdletBinding()] param( [string]$SearchBase, [switch]$Recursive, [string]$ExportCsvPath ) # Ensure ActiveDirectory module is available if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) { Throw "The ActiveDirectory module is required. Install RSAT or run on a domain-joined management host with RSAT." } Import-Module ActiveDirectory -ErrorAction Stop # Build the filter: # - Include ALL Distribution groups (regardless of mail presence) # - Include ONLY Security groups that are mail-enabled (mail or proxyAddresses) $groupFilter = '((groupCategory -eq "Distribution") -or ((groupCategory -eq "Security") -and ((mail -like "*") -or (proxyAddresses -like "*"))))' $commonProps = @('mail','proxyAddresses','groupCategory','groupScope','displayName','managedBy') $groupParams = @{ Filter = $groupFilter Properties = $commonProps } if ($SearchBase) { $groupParams['SearchBase'] = $SearchBase } $groups = Get-ADGroup @groupParams | Sort-Object Name # Helper: Extract primary SMTP (uppercase 'SMTP:') function Get-PrimarySmtp { param([string[]]$ProxyAddresses) if (-not $ProxyAddresses) { return $null } $primary = $ProxyAddresses | Where-Object { $_ -cmatch '^SMTP:' } | Select-Object -First 1 if ($primary) { return $primary.Substring(5) } return $null } # Collect results (flattened: one row per member per group) $results = New-Object System.Collections.Generic.List[object] foreach ($g in $groups) { $groupType = if ($g.GroupCategory -eq 'Security') { 'Mail-Enabled Security Group' } else { 'Distribution Group' } $primarySmtp = Get-PrimarySmtp -ProxyAddresses $g.proxyAddresses # Pull members (direct or recursive) try { $memberParams = @{ Identity = $g.DistinguishedName } if ($Recursive.IsPresent) { $memberParams['Recursive'] = $true } $members = Get-ADGroupMember @memberParams -ErrorAction Stop } catch { Write-Warning "Failed to get members for group '$($g.Name)': $($_.Exception.Message)" $members = @() } if (-not $members -or $members.Count -eq 0) { # Emit at least one row so empty groups are visible $results.Add([pscustomobject]@{ GroupName = $g.Name GroupSamAccountName = $g.SamAccountName GroupType = $groupType GroupScope = $g.GroupScope GroupEmail = $g.mail GroupPrimarySmtp = $primarySmtp GroupDisplayName = $g.DisplayName GroupManagedByDN = $g.ManagedBy GroupDistinguishedName = $g.DistinguishedName MemberName = $null MemberSamAccountName = $null MemberType = $null MemberDistinguishedName= $null }) continue } foreach ($m in $members) { # m can be user, group, computer, contact $results.Add([pscustomobject]@{ GroupName = $g.Name GroupSamAccountName = $g.SamAccountName GroupType = $groupType GroupScope = $g.GroupScope GroupEmail = $g.mail GroupPrimarySmtp = $primarySmtp GroupDisplayName = $g.DisplayName GroupManagedByDN = $g.ManagedBy GroupDistinguishedName = $g.DistinguishedName MemberName = $m.Name MemberSamAccountName = $m.SamAccountName MemberType = $m.objectClass MemberDistinguishedName= $m.DistinguishedName }) } } # Emit to console (table) and optionally export $results | Sort-Object GroupName, MemberType, MemberSamAccountName | Format-Table GroupName, GroupType, GroupScope, GroupPrimarySmtp, MemberType, MemberSamAccountName -AutoSize if ($ExportCsvPath) { $dir = Split-Path -Path $ExportCsvPath -Parent if ($dir -and -not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null } $results | Export-Csv -Path $ExportCsvPath -NoTypeInformation -Encoding UTF8 Write-Host "Exported results to: $ExportCsvPath" -ForegroundColor Green } # Also produce a quick summary by group (counts by member type) $summary = $results | Group-Object GroupName | ForEach-Object { $rows = $_.Group [pscustomobject]@{ GroupName = $_.Name GroupType = ($rows | Select-Object -First 1).GroupType GroupScope = ($rows | Select-Object -First 1).GroupScope PrimarySmtp = ($rows | Select-Object -First 1).GroupPrimarySmtp MembersTotal = ($rows | Where-Object { $_.MemberSamAccountName }).Count UsersCount = ($rows | Where-Object { $_.MemberType -eq 'user' }).Count GroupsCount = ($rows | Where-Object { $_.MemberType -eq 'group' }).Count ContactsCount = ($rows | Where-Object { $_.MemberType -eq 'contact' }).Count ComputersCount = ($rows | Where-Object { $_.MemberType -eq 'computer' }).Count } } | Sort-Object GroupName Write-Host "`nSummary (per group):" -ForegroundColor Cyan $summary | Format-Table -AutoSize } |