HomeNewsletterCommunityToolsArchiveBlogAboutQuick Links Subscribe free
← Back to Blog
Scripts PowerShellIntuneMicrosoft GraphDevice ManagementAutomation

Get the Primary User and Last Sync Time for Any Intune Device — Bulk via PowerShell

IA
Imran Awan
26 June 2026

Every Intune admin has been in this situation. You export a list of devices — maybe from an Entra ID group, maybe a compliance report — and all you get is a column of hostnames. No idea who is using them, when they last checked in, or whether they are even still active. Manually looking up each one in the Intune admin centre works for five devices. It does not work for five hundred.

This script solves that in one run. Feed it a CSV of hostnames, and it gives you back a clean report: who the primary user is, the last time each device synced with Intune, the OS, and whether the device was found at all. Everything you need to turn a raw device list into something you can actually act on.

What the output CSV looks like
Hostname PrimaryUser UserPrincipalName LastSyncTime OS Status
DESKTOP-A1B2C3 Sarah Johnson sarah.johnson@contoso.com 2026-06-25 08:14 Windows Found
LAPTOP-X7Y8Z9 Mark Davies mark.davies@contoso.com 2026-05-03 14:52 Windows Found
WS-FINANCE-04 NotFoundInIntune

Before you run it — what you need

The script connects to Microsoft Graph using a registered app and a certificate. This sounds more complicated than it is. Here is exactly what to set up, in order.

1 Create an app registration in Entra ID
  1. Go to Entra ID > App registrations > New registration
  2. Name it something clear — e.g. Intune Device Query
  3. Leave all other settings as default and click Register
  4. Copy the Application (client) ID — you will need this
  5. Copy the Directory (tenant) ID — you will need this too
2 Add the API permission
  1. In your new app, go to API permissions > Add a permission > Microsoft Graph
  2. Choose Application permissions (not delegated)
  3. Search for and add: DeviceManagementManagedDevices.Read.All
  4. Click Grant admin consent — the permission needs a tick, not just added

This is a read-only permission. The script cannot modify, wipe, or retire any device.

3 Create a certificate and upload it

The script uses a certificate instead of a secret for authentication — more secure, no expiry surprises. Run this once in PowerShell to create a self-signed cert:

$cert = New-SelfSignedCertificate `
    -Subject "CN=IntuneDeviceQuery" `
    -CertStoreLocation "Cert:\CurrentUser\My" `
    -KeyExportPolicy Exportable `
    -NotAfter (Get-Date).AddYears(2)

# Copy this thumbprint — paste it into the script
Write-Host $cert.Thumbprint -ForegroundColor Green

# Export the public key to upload to Entra
Export-Certificate -Cert $cert -FilePath "$env:USERPROFILE\Downloads\IntuneDeviceQuery.cer"

Then in your app registration: Certificates & secrets > Upload certificate — upload the .cer file. Keep the thumbprint — it goes in the script config.

4 Prepare your input CSV

The script expects a CSV with a column named displayName containing the device hostnames. The easiest way to get this is from Entra ID:

  1. Go to Entra ID > Groups > your group > Members > Download
  2. Open the downloaded file and confirm there is a displayName column
  3. Save it and note the file path — update $csvInput in the script config

The script uses the displayName column as the device hostname to search Intune. If your CSV uses a different column name, rename it to displayName before running.

The script

Update the three config values at the top — everything else runs automatically. The script installs any missing Graph modules, connects silently, queries each device, and saves the output CSV to your Downloads folder.

Get-IntuneDevicePrimaryUser.ps1
#Requires -Version 5.1
<#
.SYNOPSIS
    Gets the primary user and last sync time for Intune-managed devices from a CSV of hostnames.
.NOTES
    Required permission: DeviceManagementManagedDevices.Read.All (Application, read-only)
#>

# ── Config ────────────────────────────────────────────────────
$tenantId   = 'YOUR-TENANT-ID'
$clientId   = 'YOUR-CLIENT-ID'
$thumbprint = 'YOUR-CERTIFICATE-THUMBPRINT'

$csvInput  = "$env:USERPROFILE\Downloads\devices.csv"
$csvOutput = "$env:USERPROFILE\Downloads\DevicePrimaryUser_$(Get-Date -Format 'yyyy-MM-dd').csv"
# ──────────────────────────────────────────────────────────────

function Ensure-Module {
    param([string]$Name)
    if (-not (Get-Module -ListAvailable -Name $Name)) {
        Write-Host "Installing $Name ..." -ForegroundColor Yellow
        Install-Module -Name $Name -Scope CurrentUser -Repository PSGallery -Force -AllowClobber
    }
    Import-Module $Name -ErrorAction Stop
}

Ensure-Module 'Microsoft.Graph.Authentication'
Ensure-Module 'Microsoft.Graph.DeviceManagement'

# Connect using certificate — no interactive prompt
$cert = Get-Item "Cert:\CurrentUser\My\$thumbprint" -ErrorAction Stop
Connect-MgGraph -TenantId $tenantId -ClientId $clientId -Certificate $cert -NoWelcome

# Load hostnames from CSV
$devices = Import-Csv -Path $csvInput | Select-Object -ExpandProperty displayName | Where-Object { $_ }
Write-Host "Loaded $($devices.Count) hostnames" -ForegroundColor Cyan

# Query each device
$results = foreach ($hostname in $devices) {
    Write-Host "  Querying: $hostname" -ForegroundColor Cyan

    $filter = "deviceName eq '$hostname'"
    $select = 'deviceName,userDisplayName,userPrincipalName,lastSyncDateTime,operatingSystem,managedDeviceOwnerType'

    try {
        $found = Get-MgDeviceManagementManagedDevice -Filter $filter -Select $select -ErrorAction Stop

        if ($found) {
            foreach ($d in $found) {
                [PSCustomObject]@{
                    Hostname          = $d.DeviceName
                    PrimaryUser       = $d.UserDisplayName
                    UserPrincipalName = $d.UserPrincipalName
                    LastSyncTime      = $d.LastSyncDateTime
                    OS                = $d.OperatingSystem
                    OwnerType         = $d.ManagedDeviceOwnerType
                    Status            = 'Found'
                }
            }
        } else {
            [PSCustomObject]@{
                Hostname          = $hostname
                PrimaryUser       = ''
                UserPrincipalName = ''
                LastSyncTime      = ''
                OS                = ''
                OwnerType         = ''
                Status            = 'NotFoundInIntune'
            }
        }
    }
    catch {
        [PSCustomObject]@{
            Hostname          = $hostname
            PrimaryUser       = ''
            UserPrincipalName = ''
            LastSyncTime      = ''
            OS                = ''
            OwnerType         = ''
            Status            = "Error: $($_.Exception.Message)"
        }
    }
}

# Export and disconnect
$results | Export-Csv -Path $csvOutput -NoTypeInformation -Encoding UTF8
Write-Host "`nDone. Saved to: $csvOutput" -ForegroundColor Green
Disconnect-MgGraph | Out-Null

Reading the output — what LastSyncTime actually tells you

The LastSyncTime column is the most useful field in the report. It shows the last time the device checked in with Intune to receive policy, pull app updates, and confirm compliance. Use it as the primary signal for device health:

Last syncWhat it meansAction
Within 7 daysActive and healthyNo action needed
8 – 30 daysDevice may be offline or user on leaveMonitor — check again next cycle
30 – 60 daysLikely stale — policies may be out of dateContact primary user to confirm status
60 – 90 daysStale — device not receiving Intune policyInvestigate — escalate to service desk
Over 90 days or blankDevice likely decommissioned or unenrolledConsider retiring from Intune and Entra

What NotFoundInIntune means

A device that comes back as NotFoundInIntune is worth investigating before you assume the device is gone. The most common causes:

What you can do with the report

Policy validation
Confirm which devices in a compliance or configuration group are actively syncing and receiving policy updates.
Stale device cleanup
Filter by LastSyncTime > 90 days to build a retire list. Cross-check with the primary user before taking action.
Entra group reviews
Export group members, run this script, and quickly see if the group contains devices with no active users or no recent sync.
Service desk investigations
When a ticket says "I can't sign in on DESKTOP-X," look up the hostname in the output to instantly get the primary user and last check-in time.
Device assignment checks
Verify that devices are assigned to the right user before rolling out a targeted app or policy change to a specific group.
Leavers process
When processing a leaver, run their devices through the script to confirm ownership and last sync before wiping or retiring.

Get the script

The script is available on GitHub. Download it, add your tenant ID, client ID, and certificate thumbprint at the top, and it is ready to run.

🔗 View on GitHub — Imran76Awan/Daily-Tasks

Before sharing your copy: Remove your tenant ID, client ID, and thumbprint and replace them with placeholders. Never commit real credentials or certificate private keys (.pfx files) to a public repository.

Share this post
LinkedIn X / Twitter Reddit Bluesky

More from EndpointWeekly

Intune
Top 10 Intune PowerShell Commands Every Admin Should Know
These 10 Microsoft Graph PowerShell commands are the foundation every IT admin and EUC…
Scripts
Export and Filter Group Policy Objects to CSV with PowerShell
A simple PowerShell script that lets you search your entire GPO estate by keyword and…
Intune
Microsoft Intune: Win32 vs. Store App Deployment — Complete Guide
Win32 or Store? Complete breakdown of both Intune app deployment methods — packaging, IME…