Skip to content

robgrame/EntraDevice.RecentSignIn

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 

Repository files navigation

EntraDevice.RecentSignIn

Find Entra ID Windows devices that have signed in recently — using Microsoft Graph with certificate-based app-only auth — so you can drive a security group whose membership depends on sign-in activity (something Entra dynamic membership groups cannot do natively).

PowerShell License: MIT


Why does this exist?

Entra ID's dynamic device membership rules do not expose any sign-in date/time attribute — the only date/time property available is employeeHireDate, and only for users. The supported device properties are the well-known set (accountEnabled, deviceOSType, enrollmentProfileName, deviceManagementAppId, deviceTrustType, isCompliant, isRooted, the extension attributes, etc.). There is no approximateLastSignInDateTime hook for dynamic membership.

The supported workaround is to keep an assigned security group and synchronize its membership from an automated job. This repo is that job: a single self-contained PowerShell script that queries Microsoft Graph and returns the matching devices, ready to be piped into Add-MgGroupMemberByRef / Remove-MgGroupMemberByRef.

What it does

Get-RecentlyActiveWindowsDevices.ps1 authenticates to Microsoft Graph as an Entra app registration using an X.509 certificate (no secret, no interactive sign-in) and returns all devices matching:

deviceOSType            = Windows
accountEnabled          = true
isManaged               = true
trustType               ≠ Workplace                  (i.e. AzureAd or ServerAd)
enrollmentProfileName   = null
deviceManagementAppId   ∈ { 0000000a-0000-0000-c000-000000000000,    # Intune AAD-joined
                            54b943f8-d761-4f8d-951e-9cea1846db5a }   # Intune Hybrid / co-managed
approximateLastSignInDateTime  ≥  (UtcNow − DaysBack)                # default 30 days

These criteria translate the customer-supplied Entra dynamic membership rule 1:1, plus the sign-in window that dynamic rules cannot express.

Key implementation notes

Topic Detail
Property name mapping The dynamic-group rule uses device.deviceManagementAppId and device.deviceOSType. In Graph $filter the same attributes are named mdmAppId and operatingSystem. The script uses the Graph names.
isManaged Not filterable on /devices. Applied client-side after the server-side filter has reduced the set.
Advanced query ne, eq null and approximateLastSignInDateTime filters require advanced query params — the script sets -ConsistencyLevel eventual -CountVariable count.
Paging Get-MgDevice -All is used so the SDK handles @odata.nextLink transparently.
Cert auth Supports either a thumbprint (resolved from both CurrentUser\My and LocalMachine\My) or a .pfx file + SecureString password. The password is passed directly to the X509Certificate2 ctor — it is never materialized as a managed string. The private key is loaded with X509KeyStorageFlags::EphemeralKeySet.
Auth verification After connecting, the script asserts Get-MgContext.AuthType -eq 'AppOnly' and bails out otherwise.

Requirements

  • PowerShell 7+ (recommended) or Windows PowerShell 5.1.
  • Microsoft Graph PowerShell SDK modules:
    Install-Module Microsoft.Graph.Identity.DirectoryManagement -Scope CurrentUser
    (Microsoft.Graph.Authentication is brought in as a dependency.)
  • An Entra app registration with:
    • Application permission Device.Read.All (admin consent granted).
    • A certificate uploaded to Certificates & secrets → Certificates.
  • The corresponding certificate either:
    • installed in CurrentUser\My or LocalMachine\My (use -CertificateThumbprint), or
    • available as a .pfx file (use -CertificatePath + -CertificatePassword).

Usage

Tip: TenantId, ClientId and CertificateThumbprint have placeholder defaults at the top of the param() block — edit them once for your environment and you can then run the script with no arguments. The script refuses to start if any of them is still the placeholder, so it can never accidentally talk to the wrong tenant. You can still override any value on the command line.

Thumbprint (cert already in the local store)

.\Get-RecentlyActiveWindowsDevices.ps1 `
    -TenantId  'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' `
    -ClientId  '11111111-2222-3333-4444-555555555555' `
    -CertificateThumbprint 'A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0' `
    -DaysBack 30 `
    -OutputCsv .\active-devices.csv

PFX file + password

$pwd = Read-Host -AsSecureString 'PFX password'

.\Get-RecentlyActiveWindowsDevices.ps1 `
    -TenantId  'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' `
    -ClientId  '11111111-2222-3333-4444-555555555555' `
    -CertificatePath  C:\certs\graph-app.pfx `
    -CertificatePassword $pwd `
    -DaysBack 30 `
    -PassThru | Out-GridView

Parameters

Parameter Description
-TenantId Entra tenant ID (GUID).
-ClientId Application (client) ID of the Entra app registration.
-CertificateThumbprint SHA‑1 thumbprint of the certificate to use (resolved from CurrentUser\My and LocalMachine\My).
-CertificatePath Path to a .pfx file containing the certificate and its private key.
-CertificatePassword SecureString password for the .pfx.
-DaysBack Sign-in lookback window in days. Default 30, range 1..365.
-ExcludeNameRegex Optional .NET regex applied (case-insensitive) to DisplayName. Any device whose name matches is excluded from the output. Example: -ExcludeNameRegex '^(KIOSK|LAB|TEST)-'.
-OutputCsv Optional CSV export path (UTF-8).
-PassThru Emit the device objects to the pipeline instead of pretty-printing the top 20.

Driving an Entra security group

Pipe the output into the membership cmdlets:

$groupId = '<assigned-security-group-id>'

$desired = .\Get-RecentlyActiveWindowsDevices.ps1 -TenantId ... -ClientId ... `
            -CertificateThumbprint ... -PassThru
$current = (Get-MgGroupMember -GroupId $groupId -All).Id

# Add
$desired.Id | Where-Object { $_ -notin $current } | ForEach-Object {
    New-MgGroupMemberByRef -GroupId $groupId -BodyParameter @{
        "@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/$_"
    }
}
# Remove
$current | Where-Object { $_ -notin $desired.Id } | ForEach-Object {
    Remove-MgGroupMemberByRef -GroupId $groupId -DirectoryObjectId $_
}

A typical deployment runs this on a schedule (Azure Automation runbook with a Managed Identity, an AKS CronJob, a scheduled task, etc.).

Caveats

  • approximateLastSignInDateTime has a latency of a few hours and reflects the device sign-in, not the user's sign-in on that device. If you need user-level sign-ins, query auditLogs/signIns instead and aggregate by deviceDetail.deviceId.
  • The Device.Read.All permission is application-level and returns the entire tenant device inventory; treat the output as confidential.

License

MIT

About

Find Entra ID Windows devices with recent sign-in activity via Microsoft Graph (app-only certificate auth) — workaround for the missing sign-in attribute in Entra dynamic membership groups.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors