"Who wiped that device?" Simple question, complicated answer.

If we know exactly when the wipe was issued, it's easy - the Intune audit logs are sorted by timestamp and we can just find the one that matches our when. But it can often be several hours (or more) between the action being executed and the result being noticed - user's out to lunch, device is sleeping, it happened on a Friday, Intune is slow, etc. And you probably have a team of techs that wipe devices all day long as part of their normal duties, so your recent wipe (or "Fresh Start" - aka cleanWindowsDevice) events are going to be filled with noise. So, what can be done to narrow this down?

Note: If you just want to "skip to the good part", there's a script at the bottom of this post that can be used to just grab the answer you're looking for.

Graph's auditEvents and their limitations

Let's take a look at some of the auditEvents for the "wipe" and "cleanWindowsDevice" actions to see what fields we can search on:

#Requires -Modules Microsoft.Graph

# Connect to Microsoft Graph interactively
PS C:\> Connect-MgGraph
Welcome To Microsoft Graph!

# Switch to the "beta" endpoint - more data there.
PS C:\> Select-MgProfile "beta"

# Get some auditEvents
PS C:\> $events = Get-MgDeviceManagementAuditEvent -Top 100

# Let's take a look at a random event
PS C:\> $events[2] | Format-List *

Activity				:
ActivityDateTime		: 12/18/2022 11:25:25 PM
ActivityOperationType	: Create
ActivityResult			: Success
ActivityType			: Create ClientCertificate
Actor					: Microsoft.Graph.PowerShell.Models.MicrosoftGraphAuditActor
Category				: Enrollment
ComponentName			: CertificateAuthority
CorrelationId			: guid-guid-guid-guid-guid
DisplayName				: ClientCertificate stored in certificate inventory
Id						: guid-guid-guid-guid-guid
Resources				: {}
AdditionalProperties	: {}

# Ok, so that's an audit event, but not quite what we're looking for - let's narrow it down
PS C:\> $events.Category | Group-Object
Count	Name						Group
-----	-----						-----
17		Enrollment					{Enrollment, Enrollment, Enrollment...}
8		Device						{Device, Device, Device, Device...}
75		DeviceConfiguration 		{DeviceConfiguration, DeviceConfiguration...}

# Hmm, maybe we'll look at another property
PS C:\> $events.ActivityType | Group-Object
Count	Name						Group
-----	-----						-----
59		Create ClientCertificate	{Create ClientCertificate...}
3		Patch MobileApp				{Patch MobileApp, Patch MobileApp...}
30		Get DeviceConfiguration 	{Get DeviceConfiguration...}
5		wipe ManagedDevice			{wipe ManagedDevice, wipe ManagedDevice...}
3		cleanWindowsDevice Man...	{cleanWindowsDevice ManagedDevice...}

# Ok, now let's explore some example events of the kind we're interested in
PS C:\> $exampleEvents = $events | Where-Object { $_.ActivityType -like "wipe*" -or $_.ActivityType -like "cleanWindows*" }

PS C:\> $exampleEvents[2] | Format-List *
Activity				:
ActivityDateTime		: 12/18/2022 8:22:43 PM
ActivityOperationType	: Action
ActivityResult			: Success
ActivityType			: wipe ManagedDevice
Actor					: Microsoft.Graph.PowerShell.Models.MicrosoftGraphAuditActor
Category				: Device
ComponentName			: ManagedDevices
CorrelationId			: guid-guid-guid-guid-guid
DisplayName				: wipe ManagedDevice
Id						: guid-guid-guid-guid-guid
Resources				: {wipe}
AdditionalProperties	: {}

PS C:\> $exampleEvents[5] | Format-List *
Activity				:
ActivityDateTime		: 12/15/2022 2:51:18 AM
ActivityOperationType	: Action
ActivityResult			: Success
ActivityType			: cleanWindowsDevice ManagedDevice
Actor					: Microsoft.Graph.PowerShell.Models.MicrosoftGraphAuditActor
Category				: Device
ComponentName			: ManagedDevices
CorrelationId			: guid-guid-guid-guid-guid
DisplayName				: cleanWindowsDevice ManagedDevice
Id						: guid-guid-guid-guid-guid
Resources				: {cleanWindowsDevice}
AdditionalProperties	: {}

PS C:\> $exampleEvents[5].Actor | Format-List *
ApplicationDisplayName	: Microsoft Intune portal extension
ApplicationId			: guid-guid-guid-guid-guid
IPAddress				:
RemoteTenantId			:
RemoteUserId			:
ServicePrincipalName	:
Type					: ItPro
UserId					: guid-guid-guid-guid-guid
UserPermissions			: {Microsoft.Intune/ManagedDevices/Delete;Microsoft.Intune/ManagedDevices/Read...}
UserPrincipalName		: [email protected]
UserRoleScopeTags		: {}
AdditionalProperties	: {[auditActorType, ItPro]}

PS C:\> $exampleEvents[5].Resources | Format-List *
DisplayName				: cleanWindowsDevice
ModifiedProperties		: {DeviceManagementAPIVersion}
ResourceId				: guid-guid-guid-guid-guid
Type					: Microsoft.Management.Services.Api.ManagedDevice
AdditionalProperties	: {[auditResourceType, Microsoft.Management.Services.Api.ManagedDevice]}

Using Microsoft.Graph to get some Intune auditEvents

Cool, so now we've got some example events and we know what properties those events contain. It looks like .Actor - that's the who - and .Resources - the what - are going to be the most important to us. .Actor contained userPrincipalName, which is good enough for me, but .Resources didn't really tell us a whole lot about the what other than the ResourceId. So let's search for that ResourceId:

PS C:\> $device = Get-MgDeviceManagementManagedDevice -ManagedDeviceId $exampleEvents[5].resources.ResourceId

Searching for the managed device object that was 'cleanWindowsDevice'd

Hmm... no results. That's weird.

What happens to the managedDevice object when you fresh start or wipe a device from Intune?

It gets deleted from Intune and the ResourceId doesn't exist anymore. Of course! So how do we find out which device was wiped? I don't keep track of all of my devices' ResourceIds, do you?

The Intune Data Warehouse

The Intune Data Warehouse is a historical record of your Intune tenant - every user, every device (and more) - that is refreshed every day. That testing device you set up three years ago that is long, long gone? It's in there. The Data Warehouse has that device's total RAM, information about which user enrolled that device, and what that device's resourceId was. And regarding the devices that were wiped / fresh started, they're in there, too - so we'll need to use it in order to figure out exactly what device was once known as b7dd1fa2-d9e6-41f9-a4cc-750079d0d4b0.

The data comes to us in an OData feed, which is great, but Microsoft's examples for connecting to the Warehouse come in the form of a C# program that requires getting a token for an Application Registration using a plaintext password or using Power BI for interactive searching. Power BI is nice and all - and I've used it to explore the Data Warehouse, for sure - but I don't want to have to open it up every time I need to cross-reference something.

So, let's get connected to the Data Warehouse and do some exploring:

#Requires -Modules Az

# Connect to Microsoft Azure interactively
PS C:\> Connect-AzAccount
Account			SubscriptionName			TenantId		Environment
-------			----------------			--------		-----------
[email protected]		My Subscription				guid-guid-guid	AzureCloud

# Get an access token for the API endpoint
PS C:\> $access = Get-AzAccessToken -ResourceUrl "https://api.manage.microsoft.com/"

# Set up our token in an Authorization header
PS C:\> $headers = @{ Authorization = "Bearer $($access.Token)" }

# Set our URL for the Data Warehouse. You can find yours at https://endpoint.microsoft.com/#view/Microsoft_Intune_Enrollment/ReportingMenu/~/dataWarehouse
PS C:\> $URL = "https://fef.msua02.manage.microsoft.com/ReportingService/DataWarehouseFEServices?api-version=v1.0"

# Make the call to the warehouse and take a look
PS C:\> $results = Invoke-RestMethod -Method GET -Uri $URL -Headers $headers

# Show the tables we can get
PS C:\> $results.value
name							kind			url
----							----			---
mobileAppInstallStates			EntitySet		mobileAppInstallStates
mobileAppInstallStatusCounts	EntitySet		mobileAppInstallStatusCounts
appRevisions					EntitySet		appRevisions
{and so on...}
devices							EntitySet		devices

# Devices! Yeah! Let's get the devices
PS C:\> $devicesURL = "https://fef.msua05.manage.microsoft.com/ReportingService/DataWarehouseFEService/devices?api-version=v1.0"
PS C:\> $deviceResults = Invoke-RestMethod -Method GET -Uri $devicesURL -Headers $headers
PS C:\> $deviceResults.value
deviceKey				: 1234567
deviceId				: guid-guid-guid-guid-guid
deviceName				: ABC-123O6B7Q
deviceTypeKey			: 33381
lastSyncDateTime		: 2022-12-15T16:06:21.0492347Z
{and so on...}
osVersion				: 10.0.17763.1098
serialNumber			: 12306B7Q
isDeleted				: False

{...and 9,999 more results of all your devices - including mobiles!}

Connecting to and exploring the Intune Data Warehouse with PowerShell

Cool. So now we have a way to get our historical devices from the Data Warehouse in PowerShell - no application registration required (though your account will need permissions to the DW, of course). The Invoke-RestMethod will only return the first 10,000 rows - and unfortunately the only way to filter returned results before they're sent to us by the endpoint is by the timestamp of when the row was last modified (likely multitudes cheaper for Microsoft to not support a bunch of filter parameters there) - so we'll have to do some paging to go through all the results. No problem.

Putting it all together

We've covered how to explore Intune audit events to get information about who did what action when, and we've also talked about connecting to the Intune Data Warehouse to get information about all of our devices. So let's go back to the question we're trying to answer:

"Who wiped that device?"

I know what device was wiped, but not who wiped it. So let's make a PowerShell script that will show us all the recent auditEvents for a given deviceName[1]. The script will search the Data Warehouse for deviceIds belonging to that deviceName, and then we'll look for those deviceIds within the auditEvents to tell us who took the action.

#Requires -Modules Az, Microsoft.Graph

[CmdletBinding()]
param(
	# Take in a deviceName to search for later on
	[Parameter(Mandatory)]
	[string]$DeviceName
)

# Connect to Graph and Azure
Connect-MgGraph
Select-MgProfile "beta"
Connect-AzAccount

# First, we'll connect to Graph and get some audit events
$auditEvents = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/deviceManagement/auditEvents?`$filter=category eq `'Device`'&`$orderby=activityDateTime desc"
$auditEvents = $auditEvents.value

Write-Host "Earliest event: $($auditEvents[$($auditEvents.count) - 1].ActivityDateTime)"

# Then, we'll connect to the Intune Data Warehouse
# Getting an access token for the Manage API endpoint
$access = Get-AzAccessToken -ResourceUrl "https://api.manage.microsoft.com/"

# Set up our token in an Authorization header
$headers = @{ Authorization = "Bearer $($access.Token)" }

# Set our URL for the Data Warehouse. You can find yours at https://endpoint.microsoft.com/#view/Microsoft_Intune_Enrollment/ReportingMenu/~/dataWarehouse
$devicesURL = "https://fef.msua05.manage.microsoft.com/ReportingService/DataWarehouseFEService/devices?api-version=v1.0"

# Page through the results, adding each page to $devicesTable
$devicesTable = @(

    # Get our results from the Data Warehouse/devices call
    $dwResults = Invoke-RestMethod -Method GET -Uri $devicesURL -Headers $headers

    # Output the results to $devicesTable
    $dwResults.value

    # Setting up the next page - so we can get all rows instead of just 10,000
    $dwResultsNextLink = $dwResults.'@odata.nextLink'

    # Page through the results
    while ($null -ne $dwResultsNextLink) {
        $dwResults = Invoke-RestMethod -Method GET -Uri $dwResultsNextLink -Headers $headers
        $dwResultsNextLink = $dwResults.'@odata.nextLink'
        # Output the new results into $devicesTable
        $dwResults.value
	}
)

# Let's find just the deviceIds we care about from the results
$matchingDWDevices = $devicesTable | Where-Object deviceName -like "*$deviceName*"

# Now we'll go through the audit events we fetched earlier and output ones that match our devices from the DW
foreach ($event in $auditEvents) {
	foreach ($device in $matchingDWDevices) {
    	if ($event.Resources.ResourceId -eq $device.deviceId) {
        	[PSCustomObject]@{
            	ResourceId = $event.Resources.ResourceId
                DateTimeUTC = $event.ActivityDateTime
                OperationType = $event.ActivityOperationType
                Result = $event.ActivityResults
                Type = $event.ActivityType
                ActorUPN = $event.Actor.UserPrincipalName
                Application = $event.Actor.ApplicationDisplayName
                ResourceName = $device.deviceName
            }
        }
    }
}

A PowerShell script to get devices from Intune Data Warehouse and look for corresponding Intune auditEvents

# Now let's put it to use. This could take quite a long time :)
# In my production script, I've added some status messages to script host to help my users.
PS C:\> .\Get-IntuneAuditEventsPerName.ps1 -deviceName ABC-12306B7Q

ResourceId			: guid-guid-guid-guid-guid
DateTimeUTC			: 12/15/2022 8:34:21 PM
OperationType		: Action
Results				: Success
Type				: cleanWindowsDevice ManagedDevice
Actor				: [email protected]
Application			: Microsoft Intune portal extension
ResourceName		: ABC-12306B7Q

An example of the script output from the previous step

Conclusion

So there you have it; now you can search auditEvents by information that is usually a little easier to come by for the times when you need to answer those kinds of questions. Hopefully this has been helpful for you, cheers!

Footnotes

  1. Intune can create multiple deviceIds for any given serialNumber (after wipes / re-enrolls, etc), and it looks like the serialNumber is not always present in the Data Warehouse for isDeleted=$true devices, unfortunately. So we'll have to search by Device Name in this use case. You can certainly search for serialNumber, but you aren't likely to return any events for the device objects that have already been removed from Intune (via wipe / fresh start / etc). If you know more about why serialNumbers aren't always in the DW, drop me a line!

Searching Intune Audit Events by Device Name

Using PowerShell and the Intune Data Warehouse to search for Intune auditEvents by deviceName