<# .SYNOPSIS ¿Qué incidentes de Microsoft Defender XDR siguen abiertos y cuáles atiendo primero? .DESCRIPTION Lista los incidentes en estado activo o en progreso, ordenados de mayor a menor severidad, con un resumen por severidad, el responsable y la liga al portal. Solo lectura: no cambia nada en el tenant. .PARAMETER RutaCsv Ruta del CSV de salida. Si se omite, solo se muestra en pantalla. .PARAMETER ZonaHoraria Zona horaria para mostrar las fechas. Por defecto, Central Standard Time (Mexico). .EXAMPLE .\get-incidentes-abiertos.ps1 -RutaCsv .\incidentes.csv .NOTES Permisos: SecurityIncident.Read.All Rol mínimo: Lector de seguridad Licencia: Cualquier licencia con acceso a Microsoft Defender XDR, por ejemplo Microsoft 365 Business Premium, Microsoft Defender for Business, Microsoft Defender for Endpoint, Microsoft Defender for Office 365 Plan 2 o Microsoft 365 E5 Licencia del código: MIT. Se entrega TAL CUAL, sin garantía. Pruébalo primero en un tenant de pruebas. #> [CmdletBinding()] param( [string]$RutaCsv, [string]$ZonaHoraria = 'Central Standard Time (Mexico)' ) $ErrorActionPreference = 'Stop' $permisos = @('SecurityIncident.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' } Connect-MgGraph -Scopes $permisos -NoWelcome function ConvertirFecha($valor) { if (-not $valor) { return $null } $utc = if ($valor -is [datetime]) { $valor.ToUniversalTime() } else { ([datetimeoffset]"$valor").UtcDateTime } [TimeZoneInfo]::ConvertTimeFromUtc($utc, $zona).ToString('yyyy-MM-dd HH:mm') } $severidades = @{ high = 'Alta'; medium = 'Media'; low = 'Baja'; informational = 'Informativa'; unknown = 'Desconocida' } $orden = @{ high = 0; medium = 1; low = 2; informational = 3; unknown = 4 } $estados = @{ active = 'Activo'; inProgress = 'En progreso' } # awaitingAction no se pide: solo lo asigna Defender Experts y exige un encabezado Prefer. $uri = "https://graph.microsoft.com/v1.0/security/incidents?`$filter=status eq 'active' or status eq 'inProgress'" $incidentes = while ($uri) { $pagina = Invoke-MgGraphRequest -Method GET -Uri $uri $pagina.value $uri = $pagina.'@odata.nextLink' } $filas = $incidentes | Sort-Object { if ($orden.ContainsKey("$($_.severity)")) { $orden["$($_.severity)"] } else { 5 } }, { -[int]$_.priorityScore } | ForEach-Object { [pscustomobject]@{ Severidad = if ($severidades["$($_.severity)"]) { $severidades["$($_.severity)"] } else { $_.severity } Estado = if ($estados["$($_.status)"]) { $estados["$($_.status)"] } else { $_.status } Incidente = $_.displayName Id = $_.id Prioridad = $_.priorityScore Responsable = if ($_.assignedTo) { $_.assignedTo } else { 'Sin asignar' } Creado = ConvertirFecha $_.createdDateTime UltimaActualizacion = ConvertirFecha $_.lastUpdateDateTime Liga = $_.incidentWebUrl } } Write-Host "Incidentes abiertos: $(@($filas).Count)" $filas | Group-Object Severidad | ForEach-Object { Write-Host " $($_.Name): $($_.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 }