Comparing Objects between AD DCs, horrible scripting champion

This topic contains 2 replies, has 2 voices, and was last updated by Profile photo of Erik Sundin Erik Sundin 3 months, 3 weeks ago.

  • Author
    Posts
  • #54594
    Profile photo of Erik Sundin
    Erik Sundin
    Participant

    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
            }
        }
    }
    
  • #54608
    Profile photo of Rob Simmers
    Rob Simmers
    Participant

    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
    
  • #54648
    Profile photo of Erik Sundin
    Erik Sundin
    Participant

    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.

You must be logged in to reply to this topic.