<# .SYNOPSIS Lista los casos de eDiscovery y su estado desde Microsoft Graph. .DESCRIPTION Consulta security/cases/ediscoveryCases y señala los casos cerrados con error, porque ese estado indica fallas al liberar retenciones. Con el rol eDiscovery Manager solo se ven los casos propios; con eDiscovery Administrator, todos. Solo lectura: no cambia nada en el tenant. .PARAMETER ZonaHoraria Zona horaria para mostrar las fechas. .PARAMETER RutaCsv Ruta del CSV de salida. Si se omite, solo se muestra en pantalla. .EXAMPLE .\get-casos-ediscovery.ps1 -RutaCsv .\casos-ediscovery.csv .NOTES Permisos: eDiscovery.Read.All Rol mínimo: eDiscovery Manager para casos propios; eDiscovery Administrator para todos los casos de la organización. Licencia: eDiscovery (Standard) en Microsoft 365 E3/E5/A5/F5/G5 u Office 365 E3/A3/G3/F3/E5/A5/G5; eDiscovery (Premium) requiere E5 o un complemento equivalente. Licencia del código: MIT. Se entrega TAL CUAL, sin garantía. Pruébalo primero en un tenant de pruebas. #> [CmdletBinding()] param( [string]$ZonaHoraria = 'Central Standard Time (Mexico)', [string]$RutaCsv ) $ErrorActionPreference = 'Stop' $permisos = @('eDiscovery.Read.All') $zona = [TimeZoneInfo]::FindSystemTimeZoneById($ZonaHoraria) if (-not (Get-Command Connect-MgGraph -ErrorAction SilentlyContinue)) { throw 'Falta el módulo. Instálalo con: Install-Module Microsoft.Graph.Authentication -Scope CurrentUser' } function HoraLocal($valor) { if (-not $valor) { return '' } $utc = if ($valor -is [datetime]) { if ($valor.Kind -eq 'Unspecified') { [datetime]::SpecifyKind($valor, 'Utc') } else { $valor.ToUniversalTime() } } else { [datetimeoffset]::Parse("$valor", [cultureinfo]::InvariantCulture, 'AssumeUniversal').UtcDateTime } [TimeZoneInfo]::ConvertTimeFromUtc($utc, $zona).ToString('yyyy-MM-dd HH:mm') } $estados = @{ active = 'Activo' closed = 'Cerrado' closing = 'Cerrándose' closedWithError = 'Cerrado con error al liberar retenciones' pendingDelete = 'Eliminación pendiente' unknown = 'Desconocido' } Connect-MgGraph -Scopes $permisos -NoWelcome $uri = 'https://graph.microsoft.com/v1.0/security/cases/ediscoveryCases' $casos = while ($uri) { $pagina = Invoke-MgGraphRequest -Method GET -Uri $uri $pagina.value $uri = $pagina.'@odata.nextLink' } $filas = @( foreach ($c in $casos) { [pscustomobject]@{ Caso = $c.displayName Estado = if ($estados[[string]$c.status]) { $estados[[string]$c.status] } else { $c.status } NumeroExterno = $c.externalId Creado = HoraLocal $c.createdDateTime Modificado = HoraLocal $c.lastModifiedDateTime ModificadoPor = $c.lastModifiedBy.user.displayName Cerrado = HoraLocal $c.closedDateTime CerradoPor = $c.closedBy.user.displayName } } ) Write-Host "Casos: $($filas.Count). Activos: $(@($filas | Where-Object Estado -eq 'Activo').Count). Con error: $(@($filas | Where-Object Estado -like '*error*').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 }