Gundog provides you with guided hunting for Microsoft 365 Defender. Especially (if not only) for Email and 
Endpoint Alerts at the moment.

You provide an AlertID you might received via Email notification and gundog will then hunt for as much as possible associated data. It does not give you the flexibility of advanced hunting like you have in the portal, but it will give you a quick, first overview of the alert, all associated entities and some enrichment.

All the hunting it does is based on the alert timestamp – so we only care about events shortly before, or after the alert.

It also provides you with PowerShell objects for each entity it hunted for – like $Network for everything it found related to this alert in the Microsoft 365 Defender DeviceNetworkEvents table.

gundog also comes up with some other features that make your life easier:

  • per default, only the most relevant data is displayed (this is the way)
  • it gives you context wherever possible: last AAD Sign-Ins & user’s AAD address
  • network connections can be automatically filtered to display more relevant connections only (get rid of connections to Office 365 e.g.)
  • network connections are enriched with geo location (country & city)
  • in the variables section you can easily adjust most parameters like advanced hunting timeframe of every query

In addition it searches for IOCs at other services like, or I ask you to apply for their paid services if you use them commercially.

After first evaluations with gundog, you can continue in the portal to dig deeper into the rabbit hole.

Feel free to extend gundog and send me pull requests! For the best psychodelic experience, use Windows 
Terminal Dracula theme with gundog. 


Since gundog is working with nearly all currently available Microsoft 365 Defender related APIs, it needs a lot of (read) permissions:

Microsoft Graph

  • Directory.Read.All
  • IdentityRiskEvent.Read.All
  • IdentityRiskyUser.Read.All
  • SecurityEvents.Read.All
  • User.Read

Microsoft Threat Protection

  • AdvancedHunting.ReadAll
  • Incident.Read.All

Windows Defender ATP

  • AdvancedQuery.Read.All
  • Alert.Read.All
  • File.Read.All
  • Ip.Read.All
  • Url.Read.All
  • User.Read.All
  • Vulnerability.Read.All

After you have registered an AAD app with the mentioned permissions, gundog takes 4 parameters

  • forgetIncidents (when you run gundog, it will query all incidents and alerts from the last 30 days and save them in a global variable. When you need to update this global var, set forgetIncidents=$true, you would do this when a new incident occurred since the last run of gundog or when you are switching tenants).
  • tenantID (mandatory)
  • clientID (mandatory)
  • clientSecret (mandatory)

NOTE: handle your app secrets with care. In the wrong hands it will allow anonymous access to your data and might be the start of an attack. Its better to store client secrets in Azure Key Vault and require authentication against it, each time you run the script. Examples of how to do that can be found on my GitHub page.

Gundog will use direct API calls instead of using the Advanced Hunting API whenever possible, since this way is much more effective and therefore faster. E.g. instead of Hunting for vulnerabilities via the advanced hunting API, gundog uses the /vulnerabilities/machineVulnerabilities endpoint. However, in some cases we need to use advanced hunting. As stated above, the goal of gundog is to provide you with as much as possible information at a glance. The trade-off here is: performance. So, decide for yourself what information you want to display at each run of gundog:

All of the API calls are done via Invoke-RestMethod in PowerShell – and there is always verbose and debug switched on in those requests, so while gundog is hunting, it will tell you what he is after, at the moment:

Especially the performance of the advanced hunting queries can be influenced by the timeframe you are looking for. In general, as said above, everything in gundog is Alert-Based. We are always interested in what happend around the timeframe the alert occurred. With that in mind, you can adjust the settings for the advanced hunting query timeframes:

In the second example, gundog is hunting for registry events 120 minutes before the event and 10 minutes after the event. Play around with those settings and see how it is going.

When hunting, you are always busy to sort out not relevant data and concentrate on the important data. To exclude certain URLs from the network connection results, modify this filter here:

Be careful here: I would recommend to only match the end of URLs (specified by $). Otherwise you might filter out things like:

Walking the dog for the first time

After starting, gundog will ask you for an AlertID, which you can find e.g. in the URL form an alert notification. It will then do some hunting for you.

After passing the AlertID to gundog, it will start hunting for associated events. It will then display its findings, lets have a closer look at each section:

At the top, you get some basic information about the alert title, severity, timestamp and more. Here you see the properties of the alert from the freshly created $alert object:

The alert object is built by gundog at runtime.

In the next section, you get information about the associated files, if available:

Gundog is querying here the file api in Microsoft Defender and the third party page This can be extended of course to query additional services.

Associated URLs are then thrown to and the result is displayed here:

Next we get some basic information about the user and the device:

Risky sign-ins are checked too, for the user in question. We are also checking for critical vulnerabilities of the device:

Again, you can always go back to the raw data by using the PowerShell object:

In the Network section, gundog is enriching the information found in DeviceNetworkEvents with geo information from (country & City):

and gundog is filtering out all connections you tell him (above you see, we could also filter out

Then we have the processes section. Remember, all of the information is associated with the alert timestamp:

In the sign-ins section, you get information about the latest sign-in locations and you also see the home address based on the AAD (if available):

Hunting for sign-ins is often hitting the configured timeout. So consider if you turn it of – or play with the invoke-restmethod (irm) timeout:

The registry section …

… and the Email section

complete the gundog alert report.

That’s it for the moment. Guys, this thing is ‘work in progress’. I bet there are one million bugs I haven’t encountered yet. Please let me know about them, create pull request, send me your ideas and suggestions to improve gundog.

Thanks for reading!



  • Pingback: Weekly News Roundup β€” February 21 to February 27 - Malware News - Malware Analysis, News and Indicators

  • Hi Jan,
    Awesome work. Where does one find the correct alert ID? I’m using sentinel and running the below, for instance, and it shows that specific alert no problem. However, when I use that alert ID within the gundog script I get “Sorry Mando, we couldn’t get any response”
    | summarize arg_max(TimeGenerated, *) by SystemAlertId
    | where SystemAlertId in(“123_This_alert_ID_123”)


  • Pretty sure and just double checked everything & even created a new secret key. I was assuming that once one reached “Type AlertID” authentication had been successful but just put some junk data in and still got there so now it has me wondering. All 14 permissions were added and granted admin consent etc etc. Will triple check.


    • Callum, here is a short script to test your environment. If you fillout tenantID, ClientID, ClientSecret – you should be able to execute the kql query and get the response. If this is working, GUNDOG should also work with the same parameters:

      $tenantID = ”
      $clientId = ”
      $clientSecret = ”

      $kql = “DeviceEvents | take 10”

      $oAuthUri = “” + $tenantId + “/oauth2/token”
      $client_id = $clientID
      $client_secret = $clientSecret
      $authBody = [Ordered] @{
      resource = “”
      client_id = $client_id
      client_secret = $client_secret
      grant_type = “client_credentials”
      try {
      $authResponse = Invoke-RestMethod -Method Post -Uri $oAuthUri -Body $authBody -ErrorAction Stop
      catch {
      Write-Host “get-huntingResultMTP: failed Invoke-RestMethod (Auth)” -ForegroundColor red
      $token = $authResponse.access_token
      $url = “”
      $headers = @{
      ‘Content-Type’ = ‘application/json’
      Accept = ‘application/json’
      Authorization = “Bearer $token”
      $body = ConvertTo-Json -InputObject @{ ‘Query’ = $kql }
      write-host $topic -ForegroundColor Green
      try {
      $webResponse = Invoke-RestMethod -Method Post -Uri $url -Headers $headers -Body $body -verbose -debug
      catch {
      $errorOutput = “get-huntingResultMTP: failed Invoke-RestMethod ($url) ” + $error


  • You’re a gentleman and a scholar, thanks. Sorry, my mistake was here:
    AdvancedHunting.Read (delegated) instead of AdvancedHunting.Read.All (application)
    Can now run the above script and return data so the app is definitely good.

    Unfortunately, I still get the “Sorry, mando” response when I try to walk the dog. All other permissions are type (Application) except for Microsoft Graph > User.Read (Delegated). I’ve copied and pasted every permission into my search from your above list just to be sure πŸ™‚


    • Slowly we are getting there ;-). Keep going! There is a $debug switch in Gundog – set it to $true. With this you should see the detailed errors. Let me know what you find out.


      • Okay, tried a few old alerts and also generated a brand new one “An active ‘PSAttackTool’ malware was blocked” πŸ™‚
        I’m now receiving the below error when run through the script.
        Error: Query for AlertId failed! This is the way, too!


  • lets move this off this comment section here. Can you contact me via linkedIn or so? Thanks!


  • Pingback: Gundog - Guided Hunting In Microsoft 365 Defender - TECHEFIX

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.