Add Domain Controller to this last logon time script

This script works and displays the name and lastlogon time, I just need to add the domain controller to the output. The User.values does not contain the DC so I’m not sure how to add that to the output.

$DCs = Get-ADDomainController -filter * | select -ExpandProperty Name

$ALLUsers = ForEach ($DC in $DCs)

{ Get-ADUser -Filter * -properties * -Server $DC

}

$DCs = Get-ADDomainController -filter * | select -ExpandProperty Name

$ALLUsers = ForEach ($DC in $DCs)

{ Get-ADUser -Filter * -properties * -Server $DC

}

$Users = @{}

ForEach ($User in $AllUsers)

{IF ($Users.ContainsKey($User.SamAccountName))

{If ($Users[$User.SamAccountName].lastLogon -lt $User.lastLogon)

{$Users[$User.SamAccountName].lastLogon = $User.lastLogon

}

}

Else

{$Users.Add($User.SamAccountName,($User | Select SamAccountName,Name,LastLogon))

}

}

$Users.Values | select Name,@{Name="Last Logon Date";Expression={ If ($_.lastLogon) { [datetime]::FromFileTime($_.lastLogon) } Else { "None" }}} 

Having difficulty in understanding your question, what do you mean by

User.values does not contain the DC ?

You can always add entries to a hashtable using .Add method or $User.NewKey = ‘NewValue’ and you are already doing the same here.

If I look at the get members on $users.values or $AllUsers there is no DC information.

So in the script when I select name and lastlogon and I’d like to display the domain controller that goes with the lastlogon value. Not sure how to do that.

Well you go through each DC and look at each user and then store the values you want.
So basically you will need nested foreach.

Don’t have a DC to test with but something like this should work.

$DCs = Get-ADDomainController -filter * | select -ExpandProperty Name
$allUsers = Get-ADUser -filter * | select SamAccountName

$data = @()

foreach($a in $allUsers)
{
  foreach($d in $DCs)
  {
    $userValues = Get-ADUser $a -Server $d -properties LastLogon | select LastLogon,Name,SamAccountName
    $data += [PSCustomObject]@{Name = $userValues.Name
                               SamAccountName = $userValues.SamAccountName
                               LastLogon = $userValues.LastLogon
                               DC = $d
                              }  
  }
}

Then you can sort, extract etc. from the $data array.
If you want you can add check during loops if the $data array already contains the user and if the LastLogon value is lower than the one you found.
Then you don’t have store everything in the $data array and sort it out later but this just a concept on how to do it.

Note:
If you can avoid it you shouldn’t really do it because depending on the size of the domain and the number of users in the domain this is a pretty large job.
Going through each DC and each user for each DC is to say the least costly in terms of load on the DC’s. If it’s a large domain I would make sure you got some approval and do it off hours.

If you don’t need a precise LastLogon time and can accept some discrepency you should use LastLogonTimeStamp.
That attribute is replicated across the DC’s and should be accurate to within a 14 day range (by default).
E.g. if you’re going to use this for e.g. “who has not logged on the last month” scenario then LastLogonTimeStamp should be enough.
Then you only need to query one DC for all the users and grab that attribute.
So no need to itterate through all DCs and all users.

Edit
If you happened to see the earlier code I noticed that it was better to switch the order of the foreach loops. To go through each user once for every DC, rather than going through each DC and then run through every user for that DC.
Should be a bit less work but still not recommended.

In terms of load on the DCs this is probably a better idea.
But again depending on size it will require enough memory and possibly CPU on the machine the script is running on.

$DCs = Get-ADDomainController -filter * | select -ExpandProperty Name

$data = @()

foreach($d in $DCs)
{
  $allUsers = Get-ADUser -filter * -properties LastLogon -Server $d | select Name,SamAccountName,LastLogon

  foreach($a in $allUsers)
  {
    $data += [PSCustomObject]@{Name = $a.Name
                               SamAccountName = $a.SamAccountName
                               LastLogon = $a.LastLogon
                               DC = $d
                              }  
  }
}

This way the machine running the script will go through the list of users.
You’re still grabbing all users once for each DC but you won’t be continously quering the DCs.
It’s still not the recommended way if you can avoid it :slight_smile:

Thanks. This will create duplicate users, how can I compare and keep the one with the latest login? Would I create another foreach loop on the $data table and do an if statement?

Yes as I mentioned earlier you need to either run through $data again and just pick the highest version or you add it during the first run.

E.g.

$DCs = Get-ADDomainController -filter * | select -ExpandProperty Name

$data = @()

foreach($d in $DCs)
{
  $allUsers = Get-ADUser -filter * -properties LastLogon -Server $d | select Name,SamAccountName,LastLogon

  foreach($a in $allUsers)
  {
    if($data.SamAccountName -contains $a.SamAccountName)
    {
      $index = $data.SamAccountName.IndexOf($a.SamAccountName)
      
      if($data[$index].LastLogon -lt $a.LastLogon)
      {
         $data[$index].LastLogon = $a.LastLogon
         $data[$index].DC = $d
      }
    }
    else
    {
      $data += [PSCustomObject]@{Name = $a.Name
                               SamAccountName = $a.SamAccountName
                               LastLogon = $a.LastLogon
                               DC = $d
                              }  
    }
  }
}

Thanks. I’m a beginner and I think I understand all the pieces of this except this line

$index = $data.SamAccountName.IndexOf($a.SamAccountName)

What is this doing?

No problem, we all start somewhere.

What it does is check which index-number in the $data array where the object with the SamAccountName matches the current SamAccountName from the foreach loop.

In simpler terms, see the $data array as a table and you want to find the row number which contain the same SamAccountName as the current SamAccountName from the $allUsers foreach loop.

So lets say you have an array with:

$array = ‘bob’,‘julie’,‘sam’

If you then do
$index = $array.IndexOf(‘julie’) you will get the number 1 as the result (since arrays are 0-based).

So later on if we want to edit ‘julie’ we can reference that with the number.
So, $array[$index] = ‘john’ would replace ‘julie’ with ‘john’.
It would also be the same if we did $array[1] = ‘john’

And the reason for that “search” is because you don’t know which number in the $data array the record for that user has.

Just noticed an error in the last piece of code.
The DC variable in the if-statement should be $d and not $a.DC since it doesn’t exist in the $allUsers list.

Have corrected it now.