Attribute für AD User : lastLogonTimestamp
Das Active Directory Attribut lastLogonTimestamp zeigt den exakten Zeitpunkt, an dem sich der Benutzer das letzte Mal erfolgreich in der Domäne authentisiert hat. Im Unterschied zum Attribut lastLogon wird lastLogonTimestamp zwischen Domänencontrollern repliziert - allerdings nur, wenn es älter ist als zwei Wochen (minus einem zufälligen Wert von 0-5 Tagen). Diese Einschränkung soll die die Netzwerkbandbreite, die durch die AD-Replikation belegt wird, so gering wie möglich halten. Der Wert lastLogonTimestamp ist es also eher dazu geeignet, inaktive Konten ausfindig zu machen, die sich schon lange Zeit nicht mehr in der Domäne angemeldet haben.
Es spielt dabei keine Rolle, auf welchem Weg die Anmeldung geschah - ob interaktiv, übers Netzwerk oder weitergeleitet von einem Radius-Server oder einem anderen Kerberos-Realm. Falls sich der Benutzer noch nie am DC angemeldet hat, ist der Wert von lastLogonTimestamp gleich Null.
lastLogonTimestamp
| LDAP Name | lastLogonTimestamp |
| Datentyp | Integer8 (64 bit signed numeric) |
| Multivalue (Array) | Nein |
| System Flags | 0x11 |
| Search Flags | 0x0 |
| Im Global Catalog? | Nein |
| Attribute ID | 1.2.840.113556.1.4.1696 |
| AD DB Attributname | Last-Logon |
| ADSI datatype | 10 - LargeInteger |
| LDAP syntax | 1.2.840.113556.1.4.906 - Microsoft Large Integer |
| Wird verwendet in ... | > W2K3 |
| Schema Doku | Microsoft - MSDN |
Die Wartezeit von 14 Tage, die in einzelner Domänencontroller verstreichen läßt, bevor er den lastLogonTimestamp-Wert eines User-Objektes zu anderen DCs repliziert, ist übrigens im Attribut msDS-LogonTimeSyncInterval festgelegt, dass in den Eigenschaften des LDAP-Objektes der Domäne selbst zu finden ist.
Der lastLogonTimestamp Wert ist ein Microsoft Large Integer, es handelt sich hier um numerische Werte von 8 Byte (64 bit) Länge - aus diesem Grund werden sie oft auch Integer8 Werte genannt:
| Minimum value: -9223372036854775808 (-2^63) or hex 0x8000000000000000 |
Maximum value: 9223372036854775807 (2^63 - 1) orhex 0x7FFFFFFFFFFFFFFF |
Es gibt einen anderen Artikel im SelfADSI Tutorial über Microsoft Integer8 Werte, die Datum/Zeit-Stempel oder Zeitintervalle repräsentieren.
Der Wert, der in einem lastLogonTimestamp Attribut gespeichert ist, repräsentiert ein Datum und ein Uhrzeit, ausgedrückt in 100-Nanosekunden-Schritten seit dem 1. Januar 1600, 0:00 Uhr.
Nebenbei erwähnt wird diese Spezifikation auch in der Microsoft FileTime Struktur verwendet. Zusätzlich dazu sollte man wissen, dass alle Datums- und Zeitangaben in der Active Directory Datenbank intern im UTC Zeitformat gespeichert werden (Universal Coordinated Time) - das ist (fast) das gleiche wie die frühere Greenwich Meantime (GMT). Wenn Sie also ein System in der mitteleuropäische Zeitzone zur Sommerzeit betreiben (MEZ, das ist GMT + 2), so müssen Sie die Integer8 Attributwerte entsprechend umrechnen, wenn Sie sie in lokaler Zeit sehen wollen.
Wenn Sie nun das lastLogonTimestamp Attribut eines bestimmten Benutzers lesen wollen, so müssen Sie zuerst mit dem zurückgegebenen Large Integer Wert umgehen können, der aus zwei Teilwerten besteht: Dem HighPart und dem LowPart. Diese Teilwerte sind durch das ADSI Interface für diesen Datentyp zugreifbar. Aber: Sie müssen beim Auslesen eines Large Integer/Integer8 Attributes in einm ADSI Script unbedingt mit einem vorangehenden 'Set' ein Objekt erzeugen. Ansonsten kann man auf die entsprechenden ADSI Interface-Eigenschaften 'HighPart' und 'LowPart' nicht zugreifen.
Umwandlung eins lastLogonTimestamp Wertes in eine lesbare Datum-/Zeitangabe
Hier kommt also ein Script, indem ein Integer8 Wert in ein Datum plus Uhrzeit umgewandelt werden kann, inklusive der Anpassung an die lokale Zeitzone (die Abweichung von UTC ermitteln wir aus der Registry):
'Sie müssen hier den Distinguished Name eines Objekts aus Ihrer eigene Umgebung angeben!
Set obj = GetObject("LDAP://cn=Administrator,cn=Users,dc=cerrotorre,dc=de")
Set llValue = obj.Get("lastLogonTimestamp")
'nicht vergessen: mit 'SET' ein Object erzeugen
WScript.Echo LargeIntegerToDate(llValue)
Function LargeIntegerToDate(value)
'nimmmt einen Microsoft LargeInteger Wert (Intger8) und gibt das entsprechende Datum plus Uhrzeit zurück
'erst die lokale Zeitabweichung aus der Registry auslesen
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
'der Large Integer wird mit zwei Long Werten gehandhabt (HighPart und LowPart)
i8High = value.HighPart
i8Low = value.LowPart
If (i8Low < 0) Then
i8High = i8High + 1
End If
'das Datum und die Uhrzeit ausrechnen: 100-Nanosecond-Schritte seit dem 1. Januar 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
Alle Benutzer suchen, die sich seit 6 Monaten nicht angemeldet haben
Und hier ein weiteres Script, indem wir umgekehrt die Umwandlung eines Datums/Zeit-Wertes in den entsprechenden Integer8-Wert meistern müssen - wir wollen alle Benutzer finden, die sich seit 6 Monaten nicht mehr oder noch nie in der Domäne angemeldet haben. Um den korrekten LDAP Filter zu bauen, brauchen wir den Large Integer Wert für das Datum/die Uhrzeit von vor sechs Monaten... Wenn Sie nicht genau wissen, wie die Suchfunktion dieses Scripts arbeitet: Es gibt einen detaillierten Artikel hier im SelfADSI Tutorial, indem die LDAP Suche mit ADO Techniken erklärt wird.
'Sie müssen hier andere Namen und Anmeldedaten aus Ihrer eigene Umgebung angeben!
serverName = "nadrash.cerrotorre.de"
baseStr = "dc=cerrotorre,dc=de"
userName = "philipp@cerrotorre.de"
userPass = "secret"
'Datum+Zeit von vor 6 Monaten ermitteln, DateAdd ist eine Standardfunktion von VBScript, wir könnten auch Tag, Jahr usw ermitteln
sixMonthsBefore= DateAdd("m", -6, Now)
'Zeigt den Integer8 Wert des betreffenden Datums+Uhrzeit...
sixMonthsBeforeValue = DateToLargeIntegerString(sixMonthsBefore)
'Beide Werte anzeigen lassen:
WScript.Echo "Timestamp von vor sechs Monaten: " & sixMonthsBefore & " => " & sixMonthsBeforeValue
'Nun eine Liste aller Benutzer ausgeben lassen, die sich seit 6 Monaten nicht mehr angemeldet haben:
filterStr = "(&(objectclass=user)(lastLogonTimestamp<=" & sixMonthsBeforeValue & "))"
'Suche Ausführen!
Set ado = CreateObject("ADODB.Connection") 'Neue
ADO Connection erzeugen
ado.Provider = "ADSDSOObject" 'Die
ADSI-Schnittstelle verwenden
ado.Properties("User ID") = userName 'Credentials
angeben - wenn sie diese zwei Zeilen weglassen, ...
ado.Properties("Password") = userPass '... wird die aktuellen Anmeldedaten verwendet
ado.Properties("Encrypt Password") = True
ado.Open "ADS-Search" 'Beliebigen
Namen für die Connection vergeben
Set adoCmd = CreateObject("ADODB.Command") 'Neues
ADO-Kommando erzeugen
adoCmd.ActiveConnection = ado 'Zuordnung
zur bestehenden ADO-Connection
adoCmd.Properties("Page Size") = 1000 'Parameter
für Paged Result Suche auf 1000 setzen (=AD Standard)
adoCmd.Properties("Cache Results") = True
adoCmd.CommandText = "<LDAP://"
& serverName & "/" & baseStr &
">;" & filterStr & ";distinguishedName;subtree"
Set objectList = adoCmd.Execute 'Suche durchführen
While Not objectList.EOF
WScript.Echo objectList.Fields("distinguishedName") 'Output:
Distinguished Name dieser Objekte
objectList.MoveNext 'zum
nächsten gefundenen Objekt
Wend
Function DateToLargeIntegerString(value)
'nimmt ein Datun+Uhrzeit und gibt den entsprechenden
Microsoft LargeInteger Wert (Intger8) zurück
'erst die lokale Zeitabweichung aus der Registry auslesen
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
'die Zeit von lokaler Zeit in UTC umrechnen
value = DateAdd("n", timeShift, value)
'wieviele Sekunden seit 1601 sind vergangen?
secs = DateDiff("s", #1/1/1601#, value)
'in Nano-Sekunden umrechnen
DateToLargeIntegerString = CStr(secs) & "0000000"
End Function

