<# .SYNOPSIS Responde cuántos alias SMTP tiene cada destinatario y cuáles son. .DESCRIPTION Lista la dirección principal y los alias smtp: de cada destinatario, ordenados por número de alias. No cuenta direcciones SIP, X500 ni SPO como alias, pero sí las suma en TotalDirecciones, porque el límite de Exchange Online es por direcciones proxy: 300 en Microsoft 365 según "Exchange Online limits", que recomienda tomar ese número como tope seguro. Los que lo alcanzan se marcan en Aviso. Solo lectura: no cambia nada en el tenant. .PARAMETER Minimo Muestra solo los destinatarios con este número de alias o más. Por defecto, todos. .PARAMETER Tipo Tipo de destinatario: UserMailbox, SharedMailbox, RoomMailbox, EquipmentMailbox, GroupMailbox, MailUniversalDistributionGroup, MailUniversalSecurityGroup, MailContact, MailUser o Todos (por defecto). .PARAMETER RutaCsv Ruta del CSV de salida. Si se omite, solo se muestra en pantalla. .EXAMPLE .\get-alias-por-buzon.ps1 -Minimo 3 -RutaCsv .\alias.csv .NOTES Permisos: ninguno de Graph; usa ExchangeOnlineManagement. Rol mínimo: Lector global (View-Only Organization Management en Exchange Online). Licencia: Exchange Online Plan 1 o superior, incluido en Microsoft 365 Business Premium. Licencia del código: MIT. Se entrega TAL CUAL, sin garantía. Pruébalo primero en un tenant de pruebas. #> #Requires -Version 7.0 [CmdletBinding()] param( [ValidateRange(0, [int]::MaxValue)] [int]$Minimo = 0, [ValidateSet('UserMailbox', 'SharedMailbox', 'RoomMailbox', 'EquipmentMailbox', 'GroupMailbox', 'MailUniversalDistributionGroup', 'MailUniversalSecurityGroup', 'MailContact', 'MailUser', 'Todos')] [string]$Tipo = 'Todos', [string]$RutaCsv ) $ErrorActionPreference = 'Stop' if (-not (Get-Command Connect-ExchangeOnline -ErrorAction SilentlyContinue)) { throw 'Falta el módulo. Instálalo con: Install-Module ExchangeOnlineManagement -Scope CurrentUser' } function Limpiar-Fila($fila) { foreach ($p in $fila.PSObject.Properties) { if ($p.Value -is [string] -and $p.Value -match '^[=+\-@\t\r]') { $p.Value = "'$($p.Value)" } } $fila } # El CSV se vacía desde el inicio: si algo truena o no hay resultados, no queda uno viejo que parezca actual. if ($RutaCsv) { Set-Content -LiteralPath $RutaCsv -Value $null -Encoding utf8BOM } $filtro = @{} if ($Tipo -ne 'Todos') { $filtro.RecipientTypeDetails = $Tipo } Connect-ExchangeOnline -ShowBanner:$false try { $destinatarios = @(Get-EXORecipient @filtro -ResultSize Unlimited -Properties DisplayName, PrimarySmtpAddress, EmailAddresses -ErrorAction Stop) } finally { Disconnect-ExchangeOnline -Confirm:$false } $filas = foreach ($d in $destinatarios) { $direcciones = @($d.EmailAddresses) # smtp: en minúsculas es alias y SMTP: en mayúsculas es la principal: la comparación distingue mayúsculas. $alias = @($direcciones | Where-Object { "$_" -clike 'smtp:*' } | ForEach-Object { "$_".Substring(5) }) if ($alias.Count -lt $Minimo) { continue } [pscustomobject]@{ Destinatario = $d.DisplayName DireccionPrincipal = $d.PrimarySmtpAddress Tipo = $d.RecipientTypeDetails NumeroAlias = $alias.Count TotalDirecciones = $direcciones.Count Alias = $alias -join '; ' Aviso = if ($direcciones.Count -ge 300) { 'En el límite de 300 direcciones de Exchange Online' } else { '' } } } $salida = @($filas | Sort-Object NumeroAlias -Descending) Write-Host "Destinatarios en el reporte: $($salida.Count)" if ($RutaCsv) { if ($salida.Count) { # UTF-8 con BOM para que Excel en español muestre bien los acentos. $salida | ForEach-Object { Limpiar-Fila $_ } | Export-Csv -LiteralPath $RutaCsv -NoTypeInformation -Encoding utf8BOM } Write-Host "CSV guardado en $RutaCsv" } else { $salida | Format-Table -AutoSize }