When in a corporation there will be groups that have people assigned to that particular group and that group will also be assigned to a service, but the question is does anyone use that service, so have people not been using that service?
If you have a small group then this is very simple to check, but if you have a larger group then this can be more time consuming, that is where the script is helpful for operations like this where you provide the Entra group ID and the rest is automatic from the script.
When the script is run you will need to authenticate to Exchange Online and Azure AD then you will be asked for the group ID as you can see below:
You can get this object ID from Entra by finding the group required, then it will be on the Overview tab:
Script : AzureGroupLoginReporter.ps1
# Import and connect
Import-Module AzureAD
Connect-ExchangeOnline
Connect-AzureAD
function Get-NestedGroupMembers {
param (
[Parameter(Mandatory = $true)]
[string]$GroupId,
[System.Collections.Generic.HashSet[string]]$ProcessedGroups = (New-Object System.Collections.Generic.HashSet[string])
)
if (!$ProcessedGroups.Add($GroupId)) {
return @()
}
$members = @()
$groupMembers = Get-AzureADGroupMember -ObjectId $GroupId -All $true
foreach ($member in $groupMembers) {
if ($member.ObjectType -eq "Group") {
$members += Get-NestedGroupMembers -GroupId $member.ObjectId -ProcessedGroups $ProcessedGroups
} else {
$members += $member
}
}
return $members
}
$groupId = Read-Host -Prompt "Enter Entra Group Object ID"
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$logFilePath = Join-Path $PSScriptRoot "LastLogin_$timestamp.txt"
try {
$group = Get-AzureADGroup -ObjectId $groupId
Write-Host "Fetching all members (including nested groups)..."
$allMembers = Get-NestedGroupMembers -GroupId $groupId | Sort-Object UserPrincipalName -Unique
$totalMembers = $allMembers.Count
$processedCount = 0
$noLoginCount = 0
@"
Last Login Report (14 Days)
Generated: $(Get-Date)
Group: $($group.DisplayName)
Total Members Found: $totalMembers
$('=' * 50)
"@ | Out-File $logFilePath
foreach ($member in $allMembers) {
$processedCount++
Write-Progress -Activity "Checking login history" -Status "Processing $processedCount of $totalMembers" -PercentComplete (($processedCount / $totalMembers) * 100)
$lastSignin = Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-14) -EndDate (Get-Date)`
-UserIds $member.UserPrincipalName -Operations UserLoggedIn -ResultSize 1 |
Select-Object -First 1
if (!$lastSignin) {
$noLoginCount++
}
$lastLoginTime = if ($lastSignin) {
$lastSignin.CreationDate
} else {
"No login in past 14 days"
}
@"
$($member.DisplayName) ($($member.UserPrincipalName))
Last Login: $lastLoginTime
$('-' * 50)
"@ | Tee-Object -FilePath $logFilePath -Append
}
@"
Summary:
Total Members: $totalMembers
Members with no login in past 14 days: $noLoginCount
$('=' * 50)
"@ | Tee-Object -FilePath $logFilePath -Append
}
catch {
Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
exit
}
Write-Host "`nLog saved to: $logFilePath" -ForegroundColor Green
Disconnect-AzureAD
Disconnect-ExchangeOnline -Confirm:$false
Then the output will be saved to a log file this log file will contain all users that are recursively in the group as well as any sign in data in the last 14 days this will look like this:
Last Login Report (14 Days)
Generated: 02/07/2025 16:34:45
Group: AdobeAcrobatPro-Users
Total Members Found: 2
==================================================
--------------------------------------------------
Alberto Scotti (alfred.adobe@croucher.cloud)
Last Login: No login in past 14 days
--------------------------------------------------
Aaron.cooke@skewb.uk (lelia.adobe@croucher.cloud)
Last Login: No login in past 14 days
--------------------------------------------------
Summary:
Total Members: 2
Members with no login in past 14 days: 2
==================================================
This then confirms that the two users assigned to this licensing group are not actually using the application as they should be.