Query Details
let query_frequency = 4h;
let query_period = 8h;
let threshold = 4;
union
(
AuditLogs
| where TimeGenerated > ago(query_period)
| where OperationName == "Add device" and Result == "success"
| mv-apply modifiedProperty = TargetResources[0]["modifiedProperties"] on (
summarize modifiedProperties = make_bag(bag_pack(tostring(modifiedProperty["displayName"]), translate(@'["\]', "", tostring(modifiedProperty["newValue"]))))
)
| extend
DeviceDisplayName = tostring(TargetResources[0]["displayName"]),
DeviceOSType = tostring(modifiedProperties["DeviceOSType"]),
DeviceObjectId = tostring(TargetResources[0]["id"]),
DeviceId = tostring(modifiedProperties["DeviceId"]),
UserId = extract(@"USER\-HWID\:([^\:\,]+)", 1, tostring(modifiedProperties["DevicePhysicalIds"]))
),
(
AuditLogs
| where TimeGenerated > ago(query_period)
| where OperationName == "Register device" and Result == "success"
| mv-apply AdditionalDetail = AdditionalDetails on (
summarize ParsedAdditionalDetails = make_bag(bag_pack(tostring(AdditionalDetail["key"]), tostring(AdditionalDetail["value"])))
)
| extend
DeviceDisplayName = tostring(TargetResources[0]["displayName"]),
DeviceOSType = tostring(ParsedAdditionalDetails["Device OS"]),
IPAddress = tostring(InitiatedBy[tostring(bag_keys(InitiatedBy)[0])]["ipAddress"]),
DeviceId = tostring(ParsedAdditionalDetails["Device Id"]),
UserId = iif(isnotempty(InitiatedBy["app"]), tostring(InitiatedBy["app"]["servicePrincipalId"]), tostring(InitiatedBy["user"]["id"]))
)
| summarize
StartTime = min(TimeGenerated),
OperationNames = array_sort_asc(make_set(OperationName)),
UserId = take_any(UserId),
IPAddress = take_any(IPAddress),
DeviceDisplayName = take_any(DeviceDisplayName),
DeviceOSType = take_any(DeviceOSType),
DeviceObjectId = take_any(DeviceObjectId),
TargetResources = make_bag(bag_pack(OperationName, TargetResources)),
CorrelationIds = make_set(CorrelationId)
by DeviceId//, UserId
| join kind=leftouter (
IntuneOperationalLogs
| where TimeGenerated > ago(query_period)
| where OperationName == "Enrollment"
| extend Properties = todynamic(Properties)
| project
EnrollmentTimeGenerated = TimeGenerated,
EnrollmentResult = Result,
FailureReason = tostring(Properties["FailureReason"]),
DeviceId = tostring(Properties["AADDeviceId"]),
UserId = tostring(Properties["IntuneUserId"]),
IntuneDeviceId = tostring(Properties["IntuneDeviceId"]),
//EnrollmentDeviceOSType = tostring(Properties["Os"]),
EnrollmentProperties = Properties
) on DeviceId//, UserId
| project-away DeviceId1, UserId1
| extend OperationNames = iff(isnotempty(EnrollmentTimeGenerated), set_union(OperationNames, dynamic(["Enrollment"])), OperationNames)
| where isnotempty(UserId)
// | where isnotempty(EnrollmentTimeGenerated)
| as _Events
| join kind=leftsemi (
_Events
| extend
Type = "AuditLogs",
TimeGenerated = StartTime
| evaluate activity_counts_metrics(Type, TimeGenerated, ago(query_period), now(), query_frequency, UserId)
| summarize
arg_min(PreviousTimeGenerated = TimeGenerated, PreviousCount = ["count"]),
arg_max(CurrentTimeGenerated = TimeGenerated, CurrentCount = ["count"])
by UserId
| where CurrentTimeGenerated > ago(query_period)
| extend PreviousCount = iff(PreviousTimeGenerated == CurrentTimeGenerated, 0, PreviousCount)
| where (not(PreviousCount > threshold) and CurrentCount > threshold)
or ((CurrentCount - PreviousCount) > threshold)
) on UserId
| join kind=leftouter (
IdentityInfo
| where TimeGenerated > ago(14d)
| summarize arg_max(TimeGenerated, AccountUPN, JobTitle) by AccountObjectId, AccountSID
| project
AccountObjectId,
UserPrincipalName = AccountUPN,
JobTitle
) on $left.UserId == $right.AccountObjectId
| project-away AccountObjectId
| sort by UserId, StartTime asc
| project
StartTime,
UserId,
UserPrincipalName,
JobTitle,
IPAddress,
DeviceDisplayName,
DeviceOSType,
DeviceId,
OperationNames,
EnrollmentTimeGenerated,
EnrollmentResult,
FailureReason,
EnrollmentProperties,
TargetResources,
IntuneDeviceId,
DeviceObjectId,
CorrelationIds
This KQL query is designed to analyze and monitor device-related activities and user behaviors within a specified time frame. Here's a simplified breakdown of what the query does:
Define Parameters:
query_frequency: Set to 4 hours, determines how often the query checks for activity.query_period: Set to 8 hours, defines the time window for looking back at logs.threshold: Set to 4, used to identify significant changes in activity.Extract Device Activities:
AuditLogs for "Add device" and "Register device" operations that were successful within the last 8 hours.Summarize Device Information:
DeviceId, capturing the earliest activity time, operation names, user ID, IP address, and other device-related details.Join with Enrollment Logs:
IntuneOperationalLogs to include device enrollment information, such as enrollment time, result, and failure reasons.Filter by User Activity:
Join with Identity Information:
IdentityInfo to include user principal name and job title.Output the Results:
In essence, this query is used to monitor device-related operations and user activities, identify significant changes in user activity, and provide detailed insights into device enrollments and user identities within a specified period.

Jose Sebastián Canós
Released: April 23, 2026
Tables
Keywords
Operators