When a user logs onto a domain PC, the authenticating domain controller updates a non-replicated attribute of the user account called lastLogon in its copy of the domain partition.  There is another attribute called lastLogonTimeStamp (since Server 2003) that is replicated but it is not updated on every single logon.  To reduce replication traffic, it is only updated when the value is (by default) 9 to 14 days out of date.  This attribute is designed for assisting detecting stale accounts, not getting a definitive date.  More info is here.

Sometimes though, it can be useful to know exactly when a user last logged on.  Here’s a script I wrote today that queries all domain controllers and gets the lastLogon attribute from all of them to find which is the newest and thus actual Last Logon.  The attribute is stored in UTC format and so returned as an Int64.  Interestingly, PowerShell provides a helpful extra property that contains a DateTime translated version of lastLogonTimeStamp called lastLogonDate.  My script has to do the translation of the lastLogon attribute’s format itself.  See the notes after the script.

function Get-LastLogonDateTime {
#requires -Version 3.0 -Modules ActiveDirectory
    param (
        [Parameter(Mandatory, ValueFromPipeline, HelpMessage='Enter one or more usernames separated by commas.')]

    BEGIN {
        $DCs = Get-ADDomainController -Filter '*'

        foreach ($user in $Username) {
            Write-Verbose ('Processing user "{0}"...' -f $user)
            try {
                Get-ADUser -Identity $user | Out-Null
            } catch {
                Write-Error ('User "{0}" does not exist.' -f $user)
            [Int64]$lastLogon = 0
            foreach ($DC in $DCs) {
                $domainController = $DC.HostName
                Write-Verbose ('Querying Domain Controller "{0}"...' -f $domainController)
                $dcLastLogon = Get-ADUser -Identity $user -Server $domainController -Properties lastLogon | Select-Object -ExpandProperty lastLogon
                $lastLogon = [Math]::Max($lastLogon, $dcLastLogon)
            $lastLogonDateTime = [DateTime]::FromFileTime($lastLogon)
            $obj = [pscustomobject]@{
                'Username' = $user
                'LastLogonDateTime' = $lastLogonDateTime
            Write-Output $obj

    END {}

When looping through each domain controller in turn, I need to compare the returned lastLogon attribute with what has already been found and just keep track of the newest value.  Since its an integer, I merely need to keep the greatest one.  The easiest way to do this is to use the Max( xy ) static method of .NET’s [Math] class which returns the biggest of two numbers.  I pass it the value from the currently-queried domain controller and the previous biggest value seen.

$lastLogon = [Math]::Max($lastLogon, $dcLastLogon)

On the first time through the loop, the value being compared with is set to 0 but note I had to strongly type the variable holding it as an Int64.

[Int64]$lastLogon = 0

This is because the lastLogon attribute is an Int64 and the Math.Max() method expects the two numbers being compared to be of the same type.  If I’d initialised that $lastLogon variable without strongly typing it, it would have been an Int32 by default, which would make the method call fail.  Yes, I found this out the hard way!

Once the newest value is obtained, the script then converts it to a normal [DateTime] value by using a static method on the [DateTime] class.

$lastLogonDateTime = [DateTime]::FromFileTime($lastLogon)

I’ve returned the final value as an Object and written the function so it can handle multiple usernames by the parameter or from the pipeline.

I’m considering writing a meatier version 2 that will also try and get the hostname of the computer the user logged on to from the Security log of the domain controller that authenticated them…!