Hello,
I want to compare lastLogon (and get total LogonCount) between DCs for our users.
While my scripts actually work they are really (really really) bad. I would appreciate a cleaner, faster approach.
My first attempt, while beeing really slow it also connects to each DC a couple of thousand times (oops):
$dcr = Get-ADDomainController -Filter {Name -like "*"}
$users = Get-ADUser -SearchBase "OU=Users,OU=Accounts,DC=company,DC=com" -SearchScope Subtree -Filter * -Properties LogonCount,lastLogon
$users | ForEach-Object {
$user = $_
$lc = 0
$dcr | ForEach-Object {
$dcruser = Get-ADUser $user.SamAccountName -Properties LogonCount,lastLogon -Server $_.HostName
$lc = $lc + $dcruser.LogonCount
If ($dcruser.lastLogon -gt $user.lastLogon)
{
$user = $dcruser
}
}
}
For my second attempt i thought to myself that I really should collect all data first, and then process it.
What i ended up with does make my stomach feel less queasy, but it actually runs even slower then my first attempt:
$dcr = Get-ADDomainController -Filter {Name -like "*"}
$dcrusers = @{}
$dcr | ForEach-Object {
$users = Get-ADUser -SearchBase "OU=Users,OU=Accounts,DC=company,DC=com" -SearchScope Subtree -Filter * -Properties LogonCount,lastLogon -Server $_.HostName
$dcrusers += @{$_.Name = @()}
$dcrusers.($_.Name) += $users
}
$dcrusers.($dcr[0].Name) | ForEach-Object {
$user = $_
$lc = $user.LogonCount
$dcr[1..($dcr.count -1)] | ForEach-Object {
$dcruser = $dcrusers.($_.Name) | Where-Object {$_.SamAccountName -eq $user.SamAccountName}
$lc = $lc + $dcruser.LogonCount
If ($dcruser.lastLogon -gt $user.lastLogon) {
$user = $dcruser
}
}
}
You can attempt to simplify your approach. If you want the sum of logons and the lastest logon date, let’s start with just raw data:
$dcr = Get-ADDomainController -Filter {Name -like "*"}
$dcrusers = foreach ($dc in $dcr) {
$params = @{
SearchBase = "OU=Users,OU=Accounts,DC=company,DC=com"
SearchScope = "Subtree"
Filter = "*"
Properties = @("LogonCount","lastLogon")
Server = $dc.HostName
}
Get-ADUser @params |
Select *, @{Name="DomainController";Expression={$dc.HostName}}
}
If a mock dcrusers object is created with some basic properties:
$dcUsers = @()
$dcUsers += [pscustomobject]@{
SamAccountName = "jsmith01"
LogonCount = "52"
LastLogon = ((Get-Date).AddDays(-11))
DomainController = "DC1"
}
$dcUsers += [pscustomobject]@{
SamAccountName = "thanks02"
LogonCount = "321"
LastLogon = ((Get-Date).AddDays(-5))
DomainController = "DC1"
}
$dcUsers += [pscustomobject]@{
SamAccountName = "jsmith01"
LogonCount = "342"
LastLogon = ((Get-Date).AddDays(-3))
DomainController = "DC2"
}
$dcUsers += [pscustomobject]@{
SamAccountName = "thanks02"
LogonCount = "10"
LastLogon = ((Get-Date).AddDays(-1))
DomainController = "DC2"
}
We get an object that looks like this:
SamAccountName LogonCount LastLogon DomainController
-------------- ---------- --------- ----------------
jsmith01 52 9/15/2016 10:24:14 AM DC1
thanks02 321 9/21/2016 10:24:14 AM DC1
jsmith01 342 9/23/2016 10:24:14 AM DC2
thanks02 10 9/25/2016 10:24:14 AM DC2
Some general recommendations. I don’t know how many users you are querying, but I’ve had issues with memory consumption in the past, so another option is to do queries of each service into an XML file or even consider a SQL database to dump data to. If you have 10k users * 10 DCs, you’re working on 100k records. Keep the object as small as possible, so if you only need the properties above, I would update the -Properties and Select appropriately to only return what you need. Once you have the data, you can do all of the calculations in a single place versus trying to build it while remotely connected to the servers:
$dcUsers | Group-Object SamAccountName |
Select Name,
@{Name="TotalCount";Expression={$_.Group | Measure-Object LogonCount -Sum | Select -ExpandProperty Sum}},
@{Name ="LatestLogon";Expression={$_.Group | Sort-Object LastLogon -Descending | Select -First 1 -ExpandProperty LastLogon}}
Name TotalCount LatestLogon
---- ---------- -----------
jsmith01 394 9/23/2016 10:24:14 AM
thanks02 331 9/25/2016 10:24:14 AM
Thank you very much, I got caught up in trying to get the exact data I was after right away, and ended up nesting foreach loops to compare objects. That last piece of code was an eye opener, pure gold. I will reuse that line of thinking going forward.