Microsoft Defender - Advanced Hunting API

New Contributor



I am looking into utilizing the Advanced Hunting API to search for emails sent by a specific IP / sender / sender domain. Within the Advanced Hunting console, I am able to carry out the following query without any issues:


EmailEvents | where Timestamp > ago(1d)
| where SenderIPv4 contains 'x.x.x.x' | take 1
However, I need to do a search against a large number of emails addresses / IPs, hence looking into
using the API to do some sort of loop with PowerShell. I followed the steps here to create an app:
and gave the app the correct permission of "WindowsDefenderATP -> AdvancedQuery.Read.All".
When using PowerShell, I can generate a bearer token without any issues, but when attempting to execute the query above,
I receive this error:
Invoke-WebRequest : The remote server returned an error: (400) Bad Request
Using Postman, I see a slightly more detailed error message:
"code": "BadRequest",
"message": "'table' operator: Failed to resolve table expression named 'EmailEvents'. Fix semantic errors in your query.
However, if I changed the query to for example:
"DeviceEvents | take 1"
It works without any issues. My organization currently has a mixture of A5 (E5 equivalent) and A1 licenses, so license wise I don't believe it is an issue.
Any suggestions on how to fix this would be great. Alternatively, if you have a different method of pulling email activities for multiple IPs / senders, let me know.
My end goal is to see if IPs / senders added to an anti-spam policy are still being in used in the last X days, and if not, remove it.
Thank you
4 Replies

I am having this exact same issues when trying the query 'EmailAttachmentInfo | limit 10'  I get a 400 but if i change the query to 'DeviceRegistryEvents | limit 10' the script runs ok.


Odd thing is in Defender I cant see the schema DeviceRegistryEvents.


DId you ever find a fix, is it permissions related?


Any resolution to this issue?

After doing some more digging, I was able to get it working. There were a few issues, I was not using the correct endpoint. Additionally, my permissions were not setup correctly as the permissions stated on MS documents were not the ones that I was supposed to use.

Once you create the OAUTH app under "App registrations", make sure to add the "Microsoft Threat Protection -> AdvancedHunting.Read.All" API permission. This is the code that I used:

$tenantId = '[TENANT_ID]'
$appId = '[APP_ID]'
$appSecret = '[APP_SECRET]'

$resourceAppIdUri = ''
$oAuthUri = "$tenantId/oauth2/token"

$authBody = [Ordered] @{
    resource = $resourceAppIdUri
    client_id = $appId
    client_secret = $appSecret
    grant_type = 'client_credentials'

$authResponse = Invoke-RestMethod -Method Post -Uri $oAuthUri -Body $authBody -ErrorAction Stop
$token = $authResponse.access_token

$headers = @{ 
    'Content-Type' = 'application/json'
    Accept = 'application/json'
    Authorization = "Bearer $token" 

# This assumes the .csv file has a column named 'AllowedSenders'
$AntiSpamInboundSenders = (Import-CSv -Path "C:\PATH_TO_CSV_FILE").AllowedSenders

# By default, the API can only pull data for the last 30 days
foreach ($Sender in $AntiSpamInboundSenders){
    Write-Host ("-" * 100) -ForegroundColor Yellow
    Write-Host "Checking $Sender"
    # Search by sender domain
    $query = "EmailEvents | where SenderMailFromDomain contains '$Sender' | where Timestamp > ago(30d) | take 1"
    # Search by sender
    # $query = "EmailEvents | where SenderMailFromAddress contains '$Sender' or SenderFromAddress contains '$Sender' | where Timestamp > ago(30d) | take 1"
    # Search by IP
    #$query = "EmailEvents | where SenderIPv4 contains '$IP' | where Timestamp > ago(30d) | take 1"
    $body = ConvertTo-Json -InputObject @{ 'Query' = $query }

    $queryUrl = ""
    $webResponse = Invoke-WebRequest -Method Post -Uri $queryUrl -Headers $headers -Body $body -ErrorAction Stop
    $Response =  ($webResponse | ConvertFrom-Json).Results

    if ($Response){
        $Sender = $Response.SenderFromAddress
        $RecipientEmailAddress = $Response.RecipientEmailAddress
        $Timestamp = $Response.Timestamp
        $Subject = $Response.Subject

        Write-Host $Sender
        Write-Host $RecipientEmailAddress
        Write-Host $Timestamp
        Write-Host $Subject
        Write-Host "No results" -ForegroundColor Red
    Start-Sleep -Seconds 1 # This is to reduce the number of queries sent to not go over the API limit request

 I hope this is useful.

TYVM for the info!