Saltar al contenido
Microsoft scripts

Scripts de Defender

Ejecutar una consulta de búsqueda avanzada

Corre tu propia consulta KQL de Microsoft Defender XDR, escrita en el comando o guardada en un archivo, y exporta el resultado a CSV.

Antes de correrloLicencia: con Microsoft 365 Business Premium, Defender for Business no ofrece búsqueda avanzada y Defender for Office 365 Plan 1 tampoco; necesitas Microsoft Defender Suite for Business Premium o los planes 2. Cada tabla se llena solo si tienes el servicio que la alimenta; si no, la consulta no funciona o sale vacía. Microsoft documenta 30 días de datos de Defender XDR y un máximo de 100,000 filas por consulta, también en la API: si llega a ese tope, el script avisa porque pueden faltar resultados. Si activaste RBAC en Defender for Endpoint, ese RBAC decide qué datos de dispositivos ve la cuenta.

Ejemplo en México

Despacho contable en Ciudad de México, 45 personas

Durante una auditoría del cliente, el proveedor de TI debe repetir cada mes la misma consulta y entregar el resultado sin copiarlo a mano.

.\invoke-consulta-hunting.ps1 -RutaKql .\consulta.kql -Dias 30 -RutaCsv .\resultado.csv

Obtiene el mismo reporte cada mes en CSV y lo anexa al expediente de la auditoría.

Cómo implementarlo

  1. Escribe y prueba tu consulta en el portal, en Búsqueda > Búsqueda avanzada.
  2. Guárdala en un archivo .kql o pásala con -Consulta.
  3. Corre el script e inicia sesión con una cuenta con el rol Lector de seguridad.
  4. Revisa el CSV y usa esas filas como evidencia o como base para tu investigación.
Ver el código completo de invoke-consulta-hunting.ps1
<#
.SYNOPSIS
    Ejecuta una consulta KQL de búsqueda avanzada de Microsoft Defender XDR y exporta el resultado.

.DESCRIPTION
    Manda la consulta que escribas en -Consulta o guardes en un archivo .kql a runHuntingQuery y
    muestra o exporta las filas con las columnas que la consulta devuelva, sin traducirlas.
    Las fechas salen en UTC, como las entrega la API.
    Solo lectura: el POST es una consulta y KQL no modifica datos del tenant.

.PARAMETER Consulta
    Texto de la consulta KQL.

.PARAMETER RutaKql
    Archivo con la consulta KQL.

.PARAMETER Dias
    Días hacia atrás (se envía como Timespan). Si se omite, la API usa 30. Los datos propios de
    Defender XDR solo llegan a 30 días; más atrás solo aplica con Microsoft Sentinel conectado.

.PARAMETER RutaCsv
    Ruta del CSV de salida. Si se omite, solo se muestra en pantalla.

.EXAMPLE
    .\invoke-consulta-hunting.ps1 -RutaKql .\consulta.kql -RutaCsv .\resultado.csv

.EXAMPLE
    .\invoke-consulta-hunting.ps1 -Consulta 'DeviceInfo | summarize count() by OSPlatform' -Dias 7

.NOTES
    Permisos: ThreatHunting.Read.All
    Rol mínimo: Lector de seguridad
    Licencia: Búsqueda avanzada de Microsoft Defender XDR: Microsoft Defender for Endpoint Plan 2 (tablas Device*) o Microsoft Defender for Office 365 Plan 2 (tablas Email*, UrlClickEvents), incluidos en Microsoft 365 E5. Microsoft 365 Business Premium no la incluye sin Microsoft Defender Suite for Business Premium
    Licencia del código: MIT. Se entrega TAL CUAL, sin garantía. Pruébalo primero en un tenant de pruebas.
#>
[CmdletBinding(DefaultParameterSetName = 'Texto')]
param(
    [Parameter(Mandatory, ParameterSetName = 'Texto')]
    [string]$Consulta,
    [Parameter(Mandatory, ParameterSetName = 'Archivo')]
    [string]$RutaKql,
    [ValidateRange(1, 365)]
    [int]$Dias,
    [string]$RutaCsv
)

$ErrorActionPreference = 'Stop'
$permisos = @('ThreatHunting.Read.All')

if ($RutaKql) { $Consulta = Get-Content -Path $RutaKql -Raw -Encoding utf8 }
if ([string]::IsNullOrWhiteSpace($Consulta)) { throw 'La consulta está vacía.' }

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 ConvertirCelda($valor) {
    if ($valor -is [string]) {
        # Un remitente o asunto puede traer una fórmula: el apóstrofo evita que Excel la ejecute.
        if ($valor -match '^[=+\-@\t\r]') { return "'$valor" }
        return $valor
    }
    if ($valor -is [System.Collections.IEnumerable]) { return ConvertTo-Json -InputObject $valor -Compress -Depth 10 }
    $valor
}

$cuerpo = @{ Query = $Consulta }
if ($Dias) { $cuerpo.Timespan = "P$($Dias)D" }
$respuesta = Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/v1.0/security/runHuntingQuery' -Body ($cuerpo | ConvertTo-Json) -ContentType 'application/json; charset=utf-8'
if (@($respuesta.results).Count -ge 100000) { Write-Warning 'Se alcanzó el tope de 100,000 filas de la consulta: pueden faltar resultados. Reduce el periodo o filtra más.' }

$columnas = @($respuesta.schema | ForEach-Object { $_.name })
$filas = foreach ($r in $respuesta.results) {
    $fila = [ordered]@{}
    # Se usa el índice porque una columna llamada Count chocaría con la propiedad del hashtable.
    foreach ($c in $columnas) { $fila[$c] = ConvertirCelda $r[$c] }
    [pscustomobject]$fila
}

Write-Host "Filas devueltas: $(@($filas).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
}

Relacionados