Defender XDR - Custom Detection Rules PowerShell Module
Follow-up to my previous post on the same topic, Defender XDR Custom Detection Rules Push/Pull via API. Basically what I’ve done is consolidated the individual scripts into a single PowerShell module (CustomDetectionRuleModule.psm1) and added some functions to make it a bit easier to use.
What’s in the module
| Function | Description |
|---|---|
Get-GraphAccessToken | Get a token via Az context (managed identity/interactive) or SPN client credentials |
Get-GraphHeader | Build the auth header from a token |
Get-DetectionRules | List all rules, optionally save each to a JSON file |
Get-DetectionRule | Get a single rule by ID, optionally save to file |
New-DetectionRule | Create a rule from parameters or a JSON file |
Update-DetectionRule | Update a rule from parameters or a JSON file |
Remove-DetectionRule | Delete a rule by ID |
Every function accepts an optional -token parameter. If omitted, it falls back to Invoke-MgGraphRequest, which if you’ve read the previous post doesn’t quite work all that well (yet, still).
Authenticating with an SPN
The module also supports authenticating with a service principal directly if needed. Without a token it defaults to using your current Azure context and requires the Az-module like before.
1. Create the app registration and SPN
# Create app registration
az ad app create --display-name "CustomDetectionRules" --sign-in-audience "AzureADMyOrg"
# Note the appId from the output, then create the service principal
az ad sp create --id "<appId>"
2. Assign CustomDetection.ReadWrite.All
# Get the Microsoft Graph SP and the role ID
$graphSp = az ad sp list --filter "appId eq '00000003-0000-0000-c000-000000000000'" --query "[0].{id: id, appRoles: appRoles}" -o json | ConvertFrom-Json
$role = $graphSp.appRoles | Where-Object { $_.value -eq 'CustomDetection.ReadWrite.All' }
# Get your SPN's object ID
$sp = az ad sp list --filter "appId eq '<appId>'" --query "[0].id" -o tsv
# Assign the role (this also grants admin consent)
@{ principalId = $sp; resourceId = $graphSp.id; appRoleId = $role.id } | ConvertTo-Json | Out-File role-body.json -Encoding utf8NoBOM
az rest --method POST --uri "https://graph.microsoft.com/v1.0/servicePrincipals/$sp/appRoleAssignments" --body "@role-body.json" --headers "Content-Type=application/json"
3. Create a client secret
az ad app credential reset --id "<appId>" --display-name "cdr-secret"
Save the password from the output!
4. Get a token and use it
Import-Module .\CustomDetectionRuleModule.psm1
$token = Get-GraphAccessToken -ClientId "<appId>" -ClientSecret "<password>" -TenantId "<tenantId>"
# List all rules
Get-DetectionRules -token $token
# Get a single rule
Get-DetectionRule -ruleId "60" -token $token
# Download all rules to disk
Get-DetectionRules -token $token -OutputPath "./rules"
# Create a new rule
New-DetectionRule -displayName "I write sins not tragedies" -isEnabled $false -queryText "DeviceProcessEvents | where FileName == 'cmd.exe' | take 10" -period "24H" -alertTitle "CMD Execution" -alertDescription "But what a shame" -severity "low" -category "Execution" -identifier "deviceId" -token $token
# Update a rule
Update-DetectionRule -ruleId "192" -displayName "only for the weak" -severity "medium" -token $token
# Create from a JSON file
New-DetectionRule -InputFile "./rules/My_New_Rule.json" -token $token
# Delete a rule
Remove-DetectionRule -ruleId "192" -token $token
The existing Az context flow still works the same as before, simply call Get-GraphAccessToken without parameters.
For the full details on the API, request bodies and response formats, see the previous post.