I've wanted to dive into Active Directory and clean up a lot of tech debt which was/is happening in my organization with regards to old accounts not being disabled or deleted. We have standardized on a process for 'employees' but when it comes to contractors, professional experts, test accounts or any other account which does not have an employee ID, our automation is doing nothing. My task is to find, disable and eventually delete stale unused accounts.
We have been going back and forth with leadership, help desk, change control and every time we make progress there is some other hurdle we need to overcome. If you aren't familiar with an Azure hybrid deployment, basically we have many issues finding real last logon. We have found many cases where local logon data is not reporting to Azure and thus we cannot trust Azure for real last logons, so we have created a script to query Microsoft Graph for last logon, filter by creation date/last logon and compare that to the same results in our on premises environment and export that to a CSV.
However, during our review of the list we found a few Exchange mailboxes which report no logons in Azure or on premises but we know are being used. These mailboxes are typically deployed to users with the 'Send as' permission and while there is no reporting to Active Directory or Entra/Graph, if you connect to ExchangeOnline and look at all the logon/interaction attributes you will see recent activity. So basically I am here to get assistance on how to query ExchangeOnline and compare all three sources of data and only export the data which matches our criteria. I am no PowerShell expert and I've done research to find and cobble together scripts found online to assist and modify them to fit what we are doing. The way I am doing it may be the absolute worst way of doing it but so far has worked. Any help would be greatly appreciated.
#Parameters
$InactiveDays = 365
$CSVPath = "C:\AzureInactiveUsers.csv"
$CSVPath2 = "C:\Real_Inactive_Users.csv"
$AzureInactiveUsers = @()
$InactiveThreshold = (Get-Date).AddDays(-$InactiveDays)
$CreationDays = 365
$CreationThreshold = (Get-Date).AddDays(-$CreationDays)
$OnPremInactiveUsers = @()
#=========================================================================================================================
#Connect to Microsoft Graph
Connect-MgGraph -Scopes "AuditLog.Read.All", "User.Read.All"
#Properties to Retrieve
$Properties = @(
'Id','DisplayName','Mail','UserPrincipalName','UserType', 'AccountEnabled', 'SignInActivity', 'CreatedDateTime', 'OnPremisesSamAccountName'
)
#Get All users along with the properties
$AllUsers = Get-MgUser -All -Property $Properties
ForEach ($UserA in $AllUsers)
{
$LastLoginDate = $UserA.SignInActivity.LastSignInDateTime
$CreationDate = $UserA.CreatedDateTime
If($LastLoginDate -eq $null)
{
$LastSignInDate = "Never Signed-in!"
}
Else
{
$LastSignInDate = $LastLoginDate
}
#Collect data
If((!$LastLoginDate -or ($LastLoginDate -and ((Get-Date $LastLoginDate) -lt $InactiveThreshold))) -and ($CreationDate -and ((Get-Date $CreationDate) -lt $CreationThreshold)))
{
$AzureInactiveUsers += [PSCustomObject][ordered]@{
SAMAccountName = $UserA.OnPremisesSamAccountName
UserPN = $UserA.UserPrincipalName
Email = $UserA.Mail
DisplayName = $UserA.DisplayName
UserType = $UserA.UserType
AccountEnabled = $UserA.AccountEnabled
CreationDate = $UserA.createdDateTime
LastSignInDate = $LastSignInDate
}
}
}
#$AzureInactiveUsers
#Export Data to CSV
#$AzureInactiveUsers | Export-Csv -Path $CSVPath -NoTypeInformation
#=========================================================================================================================
#Connect to OnPrem enviornment
#Get filtered list of users
$AllUsersOnPrem = Get-ADUser -Filter {(LastLogonTimestamp -lt $InactiveThreshold) -or (LastLogonTimestamp -notlike "*") -and (createTimeStamp -lt $CreationThreshold)} -Properties sAMAccountName, userPrincipalName, mail, displayName, department, userAccountControl, createTimeStamp, lastLogonTimeStamp | Select-Object sAMAccountName, userPrincipalName, mail, displayName, department, userAccountControl, createTimeStamp, @{N='lastLogonTimeStamp'; E={[DateTime]::FromFileTime($_.LastLogonTimeStamp)}}
#Compare tables and find common sAMAccountName in each array
ForEach ($UserO in $AllUsersOnPrem)
{
ForEach ($UserA in $AzureInactiveUsers)
{
If($UserA.SAMAccountName -eq $UserO.sAMAccountName)
{
$OnPremInactiveUsers += [PSCustomObject][ordered]@{
SAMAccountName = $UserO.sAMAccountName
UserPN = $UserO.userPrincipalName
Email = $UserO.mail
DisplayName = $UserO.displayName
Department = $UserO.department
AccountEnabled = $UserO.userAccountControl
CreationDate = $UserO.createTimeStamp
LastLogin = $UserO.lastLogonTimeStamp
}
}
Else
{
}
}
}
#$OnPremInactiveUsers
#Export Data to CSV
$OnPremInactiveUsers | Export-Csv -Path $CSVPath2 -NoTypeInformation
#=========================================================================================================================
#Connect to Exchange Online
Connect-ExchangeOnline
$AllUsersExchange = Get-Mailbox | Get-MailboxStatistics | select DisplayName, LastInteractionTime