I'm currently trying to query the domain controllers to find the latest logon date for users. I know I can use the lastlogondate but that doesn't get replicated as often as I'd like and I have no control over the that. I tried making a script to query each domain controller and check it against a hashtable of users but this is proving to be really slow to even query one DC. I know I could run background jobs for each DC but still just going all the latest logon times for one domain controller is taking a long time. Any advice?
$dcs = Get-ADDomainController -Filter {Name -like "*"}
$users = Get-ADUser -filter * -SearchBase "OU=users,DC=com"
$outfile = "c:users.csv"
$usertable = @{}
# creating table for all users
foreach($user in $users) {
$userobject = New-Object PSObject
$userobject | Add-Member -MemberType NoteProperty -Name DomainController -Value "0"
$userobject | Add-Member -MemberType NoteProperty -Name LastLogon -Value "0"
$usertable.Add($user.SamAccountName ,$userObject)
} # end foreach
# Method for looping through domain controllers to find last logon
foreach($dc in $dcs) {
$hostname = $dc.HostName
$logons = Get-ADUser -Filter * -SearchBase "OU=users,DC=com" -Property LastLogon -Server $hostname
foreach($u in $logons) {
$sam = $u.SamAccountName
if($u.LastLogon -gt $usertable.$sam.LastLogon) {
$usertable.$sam.LastLogon = $u.LastLogon
$usertable.$sam.DomainController = $dc.HostName
Write-Host "$sam has the latest date"
$usertable.$sam.DomainController
}
} #end inner foreach
} #end outer domain foreach
foreach($h in $usertable.GetEnumerator()) {
$sam = $($h.Name)
$newdate = $($h.Value).LastLogon
$append = "$sam,$newdate"
$append | out-file $outfile -Encoding ascii -Force -Append
}
I think I've figured this out. It's very clear from testing and comparing with my own similar program that the delay is in the following loop:
foreach($u in $logons) {
$sam = $u.SamAccountName
if($u.LastLogon -gt $usertable.$sam.LastLogon) {
$usertable.$sam.LastLogon = $u.LastLogon
$usertable.$sam.DomainController = $dc.HostName
Write-Host "$sam has the latest date"
$usertable.$sam.DomainController
}
} # end inner foreach
Now my program is not exactly the same as yours. One could sanitize this, but for all intents and purposes the below segment serves the same function as the above excerpt:
# Note: the re-use of $DC...
ForEach($DC in $RDCs )
{ # Should you add any measurements here??
Write-Host "Working on $DC : $(Get-Date -Format G) ..."
Get-ADUser -Filter $Filter -SearchBase $SearchBase -Server $DC -Properties $AD_Props |
ForEach-Object{
If ( $Users.Contains( $_.samAccountName ) )
{ # So you don't attemp when it wasn't in the root set.
If( $_.lastlogon -gt $Users[$_.samAccountName].LastLogon )
{
$Users[$_.samAccountName].LastLogon = $_.lastlogon
$Users[$_.samAccountName].DC = $DC
}
}
}
}
You can see that I'm using a ForEach-Object
loop instead of the ForEach
construct. However, this isn't a sufficient explanation, especially seeing as the latter is often faster.
Note: I timed your loop in several different ways and accounted for code earlier in the script. For example, I'm not creating the objects the same way, but I ruled that out as a factor.
Not able to figure out why your loop would be "dramatically" slower I relucantly rewrote it to:
$logons |
ForEach-Object{
If( $_.LastLogon -gt $usertable[$_.samAccountName].LastLogon )
{
$usertable[$_.samAccountName].LastLogon = $_.LastLogon
$usertable[$_.samAccountName].DomainController = $hostname
However, this didn't seem much faster if at all. So, even more reluctantly I tried it like:
Get-ADUser -Filter * -SearchBase $UsersOU -Property LastLogon -Server $hostname |
ForEach-Object{
If( $_.LastLogon -gt $usertable[$_.samAccountName].LastLogon )
{
$usertable[$_.samAccountName].LastLogon = $_.LastLogon
$usertable[$_.samAccountName].DomainController = $hostname
}
}
Now I started seeing performance similar to my own program. In fact, it seemed a little faster, probably due to not checking the Hash.
Based on experience I can only hypothesize that the objects are live and maintaining some kind of connection back to the source DC. I've seen this type of thing once before in the PowerShell Community Extensions (PSCX) the objects returned from Get-TerminalSession
exibited similar behavior. I could be crazy, I'm happy to be corrected and frankly hoping someone has a better explanation.
There are a few other things I can advise here, none as impactful as above. Please also note, I didn't test through to the end, and so can't guarantee there aren't other issues.