Políticas de retención y lo que cubren
Muestra tus políticas de retención y las cargas de trabajo donde de verdad aplican: Exchange, SharePoint, OneDrive, Teams y aplicaciones.
Ejemplo en México
Agencia aduanal en Veracruz, 60 personas
Necesitan confirmar que los correos y documentos de los expedientes de importación se conservan y no solo los correos.
.\get-politicas-retencion.ps1 -RutaCsv .\retencion.csvVen que la política cubre Exchange en todos los buzones, pero SharePoint en solo dos sitios, y la amplían a los sitios de expedientes.
Cómo implementarlo
- Instala el módulo si no lo tienes: Install-Module ExchangeOnlineManagement -Scope CurrentUser.
- Ejecuta el script con -RutaCsv e inicia sesión en Security & Compliance PowerShell.
- Lee la columna Cargas y detecta lo que falta, por ejemplo chats de Teams sin política.
- Filtra Revisar por Sí y corrige las políticas deshabilitadas o sin ubicaciones.
Ver el código completo de get-politicas-retencion.ps1
<#
.SYNOPSIS
Lista las políticas de retención y las cargas de trabajo que realmente cubren.
.DESCRIPTION
Usa Get-RetentionCompliancePolicy y Get-AppRetentionCompliancePolicy. Consulta cada política con
-DistributionDetail porque sin ese modificador las ubicaciones salen inexactas, y no usa la propiedad
Workload porque Microsoft documenta que no refleja dónde aplica. Solo lectura: no cambia nada en el tenant.
.PARAMETER RutaCsv
Ruta del CSV de salida. Si se omite, solo se muestra en pantalla.
.EXAMPLE
.\get-politicas-retencion.ps1 -RutaCsv .\retencion.csv
.NOTES
Permisos: ninguno de Graph; usa ExchangeOnlineManagement.
Rol mínimo: rol View-Only Retention Management en un grupo de roles, o grupo Compliance Administrator.
Licencia: Microsoft 365 E5/A5/G5/E3/A3/G3, Business Premium u Office 365 E5/A5/G5/E3/A3/G3 (hay otras opciones por carga de trabajo).
Licencia del código: MIT. Se entrega TAL CUAL, sin garantía. Pruébalo primero en un tenant de pruebas.
#>
[CmdletBinding()]
param(
[string]$RutaCsv
)
$ErrorActionPreference = 'Stop'
if (-not (Get-Command Connect-IPPSSession -ErrorAction SilentlyContinue)) {
throw 'Falta el módulo. Instálalo con: Install-Module ExchangeOnlineManagement -Scope CurrentUser'
}
function Resumir($valor) {
$lista = @($valor | Where-Object { $_ })
if (-not $lista.Count) { return $null }
if ("$lista" -match '(^|\s)All($|\s)') { 'todos' } else { "$($lista.Count) específicos" }
}
function Cargas($p) {
$mapa = [ordered]@{
'Exchange' = $p.ExchangeLocation
'SharePoint' = $p.SharePointLocation
'OneDrive' = $p.OneDriveLocation
'Grupos de Microsoft 365' = $p.ModernGroupLocation
'Carpetas públicas' = $p.PublicFolderLocation
'Skype Empresarial' = $p.SkypeLocation
'Chats de Teams' = $p.TeamsChatLocation
'Canales de Teams' = $p.TeamsChannelLocation
}
$partes = foreach ($nombre in $mapa.Keys) {
$alcance = Resumir $mapa[$nombre]
if ($alcance) { "$nombre ($alcance)" }
}
if ($p.Applications) { $partes = @($partes) + "Aplicaciones ($(@($p.Applications) -join ', '))" }
@($partes) -join '; '
}
function Detalle($politicas, $comando) {
foreach ($p in $politicas) {
# Un -Identity inexistente devuelve todas las políticas; por eso se vuelve a filtrar por GUID.
$d = & $comando -Identity $p.Guid -DistributionDetail | Where-Object { "$($_.Guid)" -eq "$($p.Guid)" } | Select-Object -First 1
if ($d) { $d } else { $p }
}
}
Connect-IPPSSession -ShowBanner:$false
try {
$normales = @(Detalle @(Get-RetentionCompliancePolicy) 'Get-RetentionCompliancePolicy')
$deApps = @(Detalle @(Get-AppRetentionCompliancePolicy) 'Get-AppRetentionCompliancePolicy')
# Una política sin regla no es válida: no retiene nada aunque se vea habilitada.
$sinRegla = @{}
foreach ($p in $normales) { if (-not @(Get-RetentionComplianceRule -Policy "$($p.Guid)")) { $sinRegla["$($p.Guid)"] = $true } }
foreach ($p in $deApps) { if (-not @(Get-AppRetentionComplianceRule -Policy "$($p.Guid)")) { $sinRegla["$($p.Guid)"] = $true } }
} finally {
Disconnect-ExchangeOnline -Confirm:$false
}
$filas = @(
foreach ($p in $normales + $deApps) {
$cargas = Cargas $p
[pscustomobject]@{
Politica = $p.Name
Habilitada = $p.Enabled
Modo = $p.Mode
Cargas = if ($cargas) { $cargas } else { 'Sin ubicaciones visibles' }
Reglas = if ($sinRegla["$($p.Guid)"]) { 'Sin reglas' } else { 'Con reglas' }
Revisar = if ($p.Enabled -eq $false -or $p.Mode -notin @('Enforce', $null) -or -not $cargas -or $sinRegla["$($p.Guid)"]) { 'Sí' } else { 'No' }
}
}
)
Write-Host "Políticas: $($filas.Count). Por revisar: $(@($filas | Where-Object Revisar -eq 'Sí').Count)."
if ($RutaCsv) {
# UTF-8 con BOM para que Excel en español muestre bien los acentos.
$filas | Export-Csv -Path $RutaCsv -NoTypeInformation -Encoding utf8BOM
Write-Host "CSV guardado en $RutaCsv"
} else {
$filas | Format-Table -AutoSize
}