1

I am beginner in scripting with powershell. My boss asked me to create a script that will get information about the last logon from every user in our domain on every DC. I have created the following script:

Import-Module ActiveDirectory

function Get-ADUsersLastLogon() {
    $dcs = Get-ADDomainController -Filter { Name -like "*" }
    $users = Get-ADUser -Filter * -Properties LastLogonDate | Where-Object { $_.LastLogonDate -le (Get-Date).AddDays(-30) }
    $time = 0
    $exportFilePath = "C:\output\aduser.csv"
    $columns = "name,username,datetime,domain controller,enabled"

    Out-File -filepath $exportFilePath -force -InputObject $columns

    foreach ($user in $users) {
        $Activeuser = $user.Enabled

        foreach ($dc in $dcs) { 
            $hostname = $dc.HostName
            $currentUser = Get-ADUser $user.SamAccountName | Get-ADObject -Server $hostname -Properties lastLogon
    
            if ($currentUser.LastLogon -gt $time) {
                $time = $currentUser.LastLogon
            }
        }

        $dt = [DateTime]::FromFileTime($time)
        $row = $user.Name + "," + $user.SamAccountName + "," + $dt + "," + $hostname + "," + $Activeuser
    
        Out-File -filepath $exportFilePath -append -noclobber -InputObject $row
    
        $time = 0
    }
}

Get-ADUsersLastLogon

My boss asked me to change the following things in this script:

  • the output of the domain controller is only our DC in an other country. I want to know which DC the user last logged into.
  • The running of the script is taking too long. Like half a day. Is it possible to make this faster?

I hope someone can help me with this, I tried a few things, but I didn't work :(

Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
  • there isnt much room for improvement in your script tbh, the only corrections it needs is the way you're exporting the data and the line on `$currentUser` doesn't require `Get-ADObject`... aside from that if you want to make this faster it will require multithreading – Santiago Squarzon Mar 29 '22 at 13:01
  • And this `$_.LastLogonDate -le (Get-Date).AddDays(-30)` could be done with AD filter – Santiago Squarzon Mar 29 '22 at 13:08

1 Answers1

2

the output of the domain controller is only our DC in an other country. I want to know which DC the user last logged into.

It looks like you've already solved this by querying each DC for each user object.

The running of the script is taking too long. Like half a day. Is it possible to make this faster?

That's because you're querying each DC for each user object :)

One way of speeding it up would be to flip your query logic around - query each DC for all the users but only once, and then update the logon timestamps only when a newer one is encountered.
For that to work, you need to keep track of the highest timestamp seen for each user. I'd suggest using a simple hashtable to index the user accounts by their user name:

$dcs = Get-ADDomainController -Filter { Name -like "*" }

# create hashtable to keep track of latest timestamps per user
$userLastLogonTable = @{}

foreach($dc in $dcs){
  # fetch all users from each DC
  Get-ADUser -Filter * -Properties LastLogonDate -Server $dc | ForEach-Object {
    # Only add new timestamps to table if we either haven't seen the username before, or if the timestamp is newer than the current
    if(-not $userLastLogonTable.Contains($_.SAMAccountName) -or $userLastLogonTable[$_.SAMAccountName].LastLogonDate -lt $_.LastLogonDate){
      $userLastLogonTable[$_.SAMAccountName] = [pscustomobject]@{
        LastLogonDate = $_.LastLogonDate
        LogonServer   = $dc.Name
      }
    }
  }
}

# Now that we have a complete table of all users and their last logon timestamp, 
# we can then easily identify usernames that have no recent logons
$staleUserNames = $userLastLogonTable.PSBase.Keys |Where-Object { $userLastLogonTable[$_].LastLogonDate -le (Get-Date).AddDays(-30) }

$staleUserNames now contain the user names of all user accounts that have not logged in for 30 days of more.

Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
  • Can you provide me with the full script, so that I can test if this is the right solution? – Justsomerandomusers Mar 30 '22 at 14:12
  • @arjunajankie I'm not sure I understand your question. Did you try executing the code in my answer? Remove `$staleUserNames =` to make it print the output to the console instead of storing it in that variable – Mathias R. Jessen Mar 30 '22 at 14:15
  • I tried somethins, but I don't think I understand the script totally. I see that there are some users were printed in the terminal. Can you add the following things in the script: -the last DC the users logged into -If the user account is enabled -The user name itself. If this information can be exported to csv, it would be nice. Already thnx for the help – Justsomerandomusers Apr 05 '22 at 12:43
  • 1
    @arjunajankie Sure, done. To export, simply do `$staleUserNames |Export-Csv -Path path\to\output.csv -NoTypeInformation` – Mathias R. Jessen Apr 05 '22 at 12:46
  • thank you for trying, but I doesn't work entirly, when I am the script. I see a .csv with length and a bunch of numbers. Can you troubleshoot this for me please. I tried some stuff but it didn't work. I did this to the last 2 rows: $staleUserNames = $userLastLogonTable.PSBase.Keys |Where-Object { $userLastLogonTable[$_].LastLogonDate -le (Get-Date).AddDays(-30) } $staleUserNames |Export-Csv -Path $path -NoTypeInformation – Justsomerandomusers Apr 06 '22 at 08:33
  • @arjunajankie A single `Length` column is a symptom that you've attempted to export a list of raw string values (as opposed to a list of objects). It sounds like you've updated the last line of the script, but not the rest - please make sure you run the latest version of the code above. – Mathias R. Jessen Apr 06 '22 at 08:38
  • I used the latest script, but still no change. The output is the single Length column. – Justsomerandomusers Apr 06 '22 at 09:20