In local Active Directory we have the possibility to export last logon for each user but in Azure AD we don´t had that attribute before. From now it´s available in Microsoft Graph beta. There are still limitation in graph queries to filter the data so the best recommendations is to export the data with PowerShell and user filter from there. The requirement to export the data is to create an App registrations in Azure AD with correct permissions.
To get this to work we need to create an App registration in Azure AD.
https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps
1, Push New registration
2, Name the application: LastLogon (or whatever you want)
3, Redirect URL to https://localhost


4, Copy both Application ID and Directory ID to the script. We need this numbers to connect to the application with correct permissions.

5, Assign Microsoft Graph permissions to the application. We need three permissions and add the Application permission. Push API permissions to the left menu.
6, Remove the current User.Read and add the following permissions.
7, Add AuditLog.Read.All, Directory.Read.All, User.Read.All
8, Grant admin consent for Company

9, Push Certificates & secrets, Add new client secret and name the description to Last logon secret. Copy the value to the script. Now the application is ready to connect.


10, Modify the script with correct parameters the just run the script to export all data into an variable. When the data stored in a variable you can filter the result.
What happend if the SignInActivity value is blank?
To generate a lastSignInDateTime timestamp, you need a successful sign-in. Because the lastSignInDateTime property is a new feature, the value of the lastSignInDateTime property can be blank if:
- The last successful sign-in of a user took place before this feature was released (December 1st, 2019).
- The affected user account was never used for a successful sign-in.
<#
.SYNOPSIS
Export Azure AD SignInActivity
.DESCRIPTION
Connect to App registrations and Export Azure AD SignInActivity
.NOTES
Create by Daniel Aldén
#>
# Application (client) ID, Directory (tenant) ID, and secret
$clientID = "b7409785-cba3-43d5-b566-8d4287e629aa"
$tenantName = "alden365.onmicrosoft.com"
$ClientSecret = "*secret*"
$resource = "https://graph.microsoft.com/"
$ReqTokenBody = @{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
client_Id = $clientID
Client_Secret = $clientSecret
}
$TokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token" -Method POST -Body $ReqTokenBody
# Get all users in source tenant
$uri = 'https://graph.microsoft.com/beta/users?$select=displayName,userPrincipalName,signInActivity'
# If the result is more than 999, we need to read the @odata.nextLink to show more than one side of users
$Data = while (-not [string]::IsNullOrEmpty($uri)) {
# API Call
$apiCall = try {
Invoke-RestMethod -Headers @{Authorization = "Bearer $($Tokenresponse.access_token)"} -Uri $uri -Method Get
}
catch {
$errorMessage = $_.ErrorDetails.Message | ConvertFrom-Json
}
$uri = $null
if ($apiCall) {
# Check if any data is left
$uri = $apiCall.'@odata.nextLink'
$apiCall
}
}
# Set the result into an variable
$result = ($Data | select-object Value).Value
$Export = $result | select DisplayName,UserPrincipalName,@{n="LastLoginDate";e={$_.signInActivity.lastSignInDateTime}}
[datetime]::Parse('2020-04-07T16:55:35Z')
# Export data and pipe to Out-GridView for copy to Excel
$Export | select DisplayName,UserPrincipalName,@{Name='LastLoginDate';Expression={[datetime]::Parse($_.LastLoginDate)}} | Out-GridView
# Export and filter result based on domain name (Update the domainname)
$Export | Where-Object {$_.userPrincipalName -match "alden365.se"} | select DisplayName,UserPrincipalName,@{Name='LastLoginDate';Expression={[datetime]::Parse($_.LastLoginDate)}}
Hope you can enjoy this script to create basis to clean up cloud users in Azure AD.
It doesn’t return lastloginDate for anything older than 32 days. Is this expected? My tenant has thousands of users and I’d expect to see some accounts with logins older than that…
LikeLike
I have sign-ins 5 month back so I think this is when the feature was deployed to the tenant and start logging sign-ins. Can you see any changes a couple of days later?
LikeLike
in the instructions you say, copy the tenantId, but in the scipt there is TenantName. which I should use? I get nothing as output. $Data, $Result and $Export are empty
LikeLike
You can use both on that line. Choose between tenantName or tenantID. Both redirect to your tenant. I think you have some connection problem if you don´t have any result in any variable.
LikeLike
HI, We have a similar need to sync last login date with Azure AD to the On Premise AD.
We currently have a mixture of users some working in the office and some at home.
Currently we have some users who are only using SaaS services and authenticating via SSO which updates Azure AD.
We have a script for disabling unused user accounts after 30 days – this script focuses on the On Premise Last AD Login Date.
Has anyone synced these before ?
Thanks Pete
LikeLike
The login information is not synced between on-prem and Azure. You can think about how the user sign-in to M365. In AD Connect you can change from PHS to PTA to sign-in the user against the local Active Directory. There are things to keep in mind with both solutions. Ex PTA you need more than one internet breakout to not lose the sign-in to 365.
LikeLike
Hi,
I have tried this script with all the variables filled in correctly however there is no output, $Data, $Result and $Export are empty
LikeLike
I think you hade problem with the connection strings. Please verify that you are connected and edit the ClientSecret and tenantName.
LikeLike
how long does it takes to run?
LikeLike
It depend on how many users you have in AzureAD.
LikeLike
is there any simple way to automatically disable Azure AD accounts which have not logged in for X number of days?
LikeLike
Yes, you can set the attribute “accountEnabled” to false on this users. Filter the result with Get-Date command and then use graph api or powershell to execute that command.
LikeLike
Is there a way to filter out disabled users?
LikeLike
Update the filter to include accountEnabled. Then you can filter the result with true, false.
$uri = ‘https://graph.microsoft.com/beta/users?$select=displayName,userPrincipalName,signInActivity,accountEnabled’
LikeLike
Thanks for the script… Does this have any license requirements in azure like premium license to connect Microsoft API and export these attributes?
LikeLike
No, the API is open for all that have access to the app registration.
LikeLike
Does this work in free trial account as i want to test this first
LikeLike
Yes
LikeLike
Hi,
i receive just following as output:
Dienstag, 7. April 2020 18:55:35
LikeLike
Then you are not connected. Please verify the connection strings.
LikeLike
Hey Daniel, thanks for the post. I am getting the following error message. Any suggestions?
ConvertFrom-Json : Cannot bind argument to parameter ‘InputObject’ because it is null.
At line:29 char:51
+ $errorMessage = $_.ErrorDetails.Message | ConvertFrom-Json
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [ConvertFrom-Json], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.ConvertFromJsonC
ommand
LikeLike
I think you have problem with the connecttion strings. Please verify them.
LikeLike
Could the script include what license a user has assigned? I tried amending but run out of talent.
LikeLike
If you change the following lines the license sku will be included in the output. There is no way to export the license names from graph. Then you need to create a translation array in the script.
$uri = ‘https://graph.microsoft.com/beta/users?$select=displayName,userPrincipalName,signInActivity,assignedLicenses’
$Export = $result | select DisplayName,UserPrincipalName,@{n=”LastLoginDate”;e={$_.signInActivity.lastSignInDateTime}},@{n=”License”;e={$_.assignedLicenses.skuId}}
LikeLike
Many thanks for sharing the instructions… Is there way to include what device user logged in from (capture device name). All device are build using auto pilot and are managed by Endpoint Manager.
LikeLike
With this query is is not possible to get that information.
LikeLike
Many thanks for the prompt replay… is it possible to collect data such as this. Basically collect information who logged in and from which device – both users and device are all cloud build.
LikeLike
Hi Daniel,
Is it possible to show only the users witch a M365 license instead of all users?
If yes, Can you show the code? Thanks
LikeLike
Hi Stefan
Yes, you can work with the result and filter the users with licenses.
$Export = $Result | select DisplayName,UserPrincipalName,@{n=”License”;e={$_.assignedLicenses.skuId}}
$ExportSpecificLicense = $Export | Where-Object {$_.userPrincipalName -match “@domain.com” -and $_.License -eq “e97c048c-37a4-45fb-ab50-922fbf07a370″} | select DisplayName,UserPrincipalName,@{n=”License”;e={‘Name of license’}} | Export-Csv -Path $($env:temp\Name.csv) -NoTypeInformation -Delimiter ‘;’ -Encoding utf8
LikeLike
Thanks!
LikeLike