Printout Header
RSS Feed

Attributes for AD Users : lastLogonTimestamp


The Active Directory attribute lastLogonTimestamp shows the exact timestamp of the user's last successful domain authentication. In contrast to the lastLogon attribute th lastLogonTimestamp is replicated between all domain controllers in the domain - but only if the value is older than 14 days (minus a random percentage of 5 days). This restriction was designed to avoid network bandwidth usage by AD replication. So the lastLogonTimestamp value is rather suitable to shows us the accounts which hasn't been active for a long time.

It doesn't matter here how the user performed this logon operation - interactive, network, passed-through from a radius service or another kerberos realm. If the user never did logon to the DC, the value of lastLogonTimestamp is zero.


lastLogonTimestamp


LDAP Name lastLogonTimestamp
Data type Integer8 (64 bit signed numeric)
Multivalue (Array) No
System Flags

0x11

Search Flags 0x0
In Global Catalog? No
Attribute ID 1.2.840.113556.1.4.1696
AD DB attribute name Last-Logon
ADSI datatype 10 - LargeInteger
LDAP syntax 1.2.840.113556.1.4.906 - Microsoft Large Integer
Used in ... > W2K3
Schema Doku Microsoft - MSDN



By the way: The waiting time of two weeks that a single domain controllers allows to pass before he replicates the lastLogonTimestamp attribute for a user object to other DCs is specified in the attribute msDS-LogonTimeSyncInterval. This attribute can be found in the properties of the LDAP object of the regarding AD domain.

The lastLogontimestamp value is a Microsoft Large Integer, these are signed numeric values of 8 Byte (64 bit) - those are often called Integer8 values for this reason:


Minimum value:
-9223372036854775808 (-2^63)  or
hex 0x8000000000000000

Maximum value:

9223372036854775807 (2^63 - 1) or
hex 0x7FFFFFFFFFFFFFFF


There is another article in the SelfADSI Tutorial about the Microsoft Integer8 values which represent date and time or time intervals.

The value stored in the lastLogon attribute represents the date and time of the account logon, expressed in 100-nanosecond steps since 12:00 AM, January 1, 1601.

By the way, this is a specification which is also used in the Microsoft FileTime structure. Additionally, it is important to know that an Active Directory domain controller stores the date and time always in the UTC time format (Universal Coordinated Time) - this is (almost) the former Greenwich Meantime (GMT). So if your systems are for example in Pacific Standard Time (PST, which is GMT-8), so you have to recalculate the Integer8 attribute values if you want to know the date and times in your local time.

If you want to read the lastLogonTimestamp attribute of a certain user, you first have to handle the returned Large Integer which is divided into two 32bit parts: The HighPart and the LowPart. These parts are accessible in the ADSI interface for this datatype. But: You always have to use a leading 'Set' statement when reading a Large Integer/Integer8 attribute in an ADSI script. Otherwise you can't access the ADSI interface properties 'Highpart' and 'Lowpart'.

Convert a lastLogonTimestamp value to a readable date and time value

So here is the script code to convert an Integer8 into a date and time, including the local time zone adjustment (we take the time abbreviation from UTC from the registry):

'you have to use a distinguished name of an object from you own environment here! Set obj = GetObject("LDAP://cn=Administrator,cn=Users,dc=cerrotorre,dc=de") Set llValue = obj.Get("lastLogonTimestamp") 'remember: use 'SET' to create an object! WScript.Echo LargeIntegerToDate(llValue) Function LargeIntegerToDate(value) 'takes Microsoft LargeInteger value (Integer8) and returns according the date and time 'first determine the local time from the timezone bias in the registry Set sho = CreateObject("Wscript.Shell") timeShiftValue = sho.RegRead("HKLM\System\CurrentControlSet\Control\TimeZoneInformation\ActiveTimeBias") If IsArray(timeShiftValue) Then timeShift = 0 For i = 0 To UBound(timeShiftValue) timeShift = timeShift + (timeShiftValue(i) * 256^i) Next Else timeShift = timeShiftValue End If 'get the large integer into two long values (high part and low part) i8High = value.HighPart i8Low = value.LowPart If (i8Low < 0) Then    i8High = i8High + 1 End If 'calculate the date and time: 100-nanosecond-steps since 12:00 AM, 1/1/1601 If (i8High = 0) And (i8Low = 0) Then LargeIntegerToDate = #1/1/1601# Else LargeIntegerToDate = #1/1/1601# + (((i8High * 2^32) + i8Low)/600000000 - timeShift)/1440 End If End Function

Search for all users which didn't log on for the last six months

Here comes another script where you need to convert a date and time value to the according Integer8 - we want to find all users which didn't log on to the domain in the last six months - and the ones which never logged in. To build a correct LDAP filter, we need the Large Integer value for the date and time six months ago.... If you don't know exactly how the script searches for the objects - there is a detailed article here in the SelfADSI Tutorial which explains the LDAP search with ADO techniques.

'you have to use names and credentials of your own environment here! serverName = "nadrash.cerrotorre.de" baseStr = "dc=cerrotorre,dc=de" userName = "philipp@cerrotorre.de" userPass = "secret" 'get the date and time of one week ago, DateAdd is a standard vbscript function, we could also calculate last month, year etc. sixMonthsBefore = DateAdd("m", -6, Now) 'get the Integer8 value for the regarding date an time... sixMonthsBeforeValue = DateToLargeIntegerString(sixMonthsBefore) 'show the both values: WScript.Echo "Timestamp of six months ago: " & sixMonthsBefore & " => " & sixMonthsBeforeValue 'now get a filter of all user accounts which didn't logged on in the last six months filterStr = "(&(objectclass=user)(lastLogon<=" & sixMonthsBeforeValue & "))" 'do the search! Set ado = CreateObject("ADODB.Connection") 'create new ADO connection ado.Provider = "ADSDSOObject" 'use the ADSI interface ado.Properties("User ID") = userName 'pass credentials - omit these 2 lines to use your current credentials! ado.Properties("Password") = userPass ado.Properties("Encrypt Password") = True ado.Open "ADS-Search" 'use any name for the connection Set adoCmd = CreateObject("ADODB.Command") 'create new ADO command adoCmd.ActiveConnection = ado 'assignment to an existing ADO connection adoCmd.Properties("Page Size") = 1000 'set the Paged Results value to 1000 (AD standard) adoCmd.Properties("Cache Results") = True adoCmd.CommandText = "<LDAP://"& serverName & "/" & baseStr &">;" & filterStr & ";distinguishedName;subtree" Set objectList = adoCmd.Execute 'perform search While Not objectList.EOF WScript.Echo objectList.Fields("distinguishedName") 'output: distinguishedName of these objects objectList.MoveNext 'jump to the next search result entry Wend Function DateToLargeIntegerString(value) 'takes a date/time and returns the according Microsoft LargeInteger value (Intger8) 'first determine the local time from the timezone bias in the registry Set sho = CreateObject("Wscript.Shell") timeShiftValue = sho.RegRead("HKLM\System\CurrentControlSet\Control\TimeZoneInformation\ActiveTimeBias") If IsArray(timeShiftValue) Then timeShift = 0 For i = 0 To UBound(timeShiftValue) timeShift = timeShift + (timeShiftValue(i) * 256^i) Next Else timeShift = timeShiftValue End If 'adjust the local time to UTC value = DateAdd("n", timeShift, value) 'how much seconds since 1601 are in the time? secs = DateDiff("s", #1/1/1601#, value) 'convert it to 100-nanosecond steps DateToLargeIntegerString = CStr(secs) & "0000000" End Function