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.
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.csvObtiene el mismo reporte cada mes en CSV y lo anexa al expediente de la auditoría.
Cómo implementarlo
- Escribe y prueba tu consulta en el portal, en Búsqueda > Búsqueda avanzada.
- Guárdala en un archivo .kql o pásala con -Consulta.
- Corre el script e inicia sesión con una cuenta con el rol Lector de seguridad.
- 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
}