Many attributes in Active Directory have data type Large Integer. These are 64-bit integers. The syntax is sometimes called Integer8, because the values require 8 bytes of storage. Some of these attributes represent large numbers, some represent dates, and some represent TimeSpans.


Introduction

Some attributes in Active Directory are 32-bit integers. Examples include badPwdCount, countryCode, groupType, logonCount, minPwdLength, and userAccountControl. The value of these attributes can range from -2^31 to 2^31 - 1, which in decimal is -2,147,483,648 to 2,147,483,647. These attributes take up 4 bytes in Active Directory.

Active Directory also can store 8 byte values, called Large Integer. Large Integer attributes in Active Directory are 64-bit integers. A 64-bit storage location can have a value that ranges from -2^63 to 2^63 - 1. In decimal, these large values range from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.

↑ Return to Top


Large Numbers

Some Large Integer attributes in Active Directory are used to store very large numbers, too large for 4 bytes. Examples are maxStorage, rIDAllocationPool, rIDAvailablePool, uSNChanged, and uSNCreated. For example, the value of the maxStorage attribute of a user is the maximum amount of disk space in bytes the user is allowed to use. Active Directory restricts the values of these attributes to positive numbers.

↑ Return to Top


Dates

Many dates are saved in Active Directory as Large Integer values. Examples include accountExpires, lastLogon, lastLogonTimestamp, and pwdLastSet. These attributes represent dates as the number of 100-nanosecond intervals since 12:00 AM January 1, 1601. 100-nanosecond intervals, equal to 0.0000001 seconds, are also called ticks. As you might guess, a very large number of ticks are required for most dates.

Dates in Active Directory are always saved in Coordinated Universal Time, or UTC (the acronym for the French term). This used to be called Greenwich Mean Time (GMT). The time zone bias in the local machine registry is used to convert values between UTC and local time.

As an example, if a user is configured to have their account expire at 5:00 PM June 5, 2015 CST (In the Central time zone of the United States, when Daylight Savings Time is in affect), this would be the same as 10:00 PM June 5, 2015 in UTC (the time zone bias is 5 hours). The corresponding Large Integer value would be 130,780,152,000,000,000. The value saved in Active Directory will not have the commas.

All Large Integer dates in Active Directory can only have positive values. Most values are similar to the example above. However, two special values have the meaning "never". All Large Integer date attributes can have the value zero, and this means "never". For example, if a user has never logged on, the lastLogon attribute will be zero. In addition, the accountExpires attribute can be assigned the largest possible value, 2^63 - 1 (9,223,372,036,854,775,807). This corresponds to a date so far in the future (September 14 of the year 30,828), that essentially the account never expires.

↑ Return to Top


TimeSpans

Several Active Directory attributes are TimeSpans saved as Large Integer values. Examples include lockoutDuration, maxPwdAge, and minPwdAge. These represent TimeSpans in ticks (100-nanosecond intervals) and are always saved as negative values. You cannot assign 0 to the maxPwdAge attribute in Active Directory, because the system requires that the absolute value must always exceed that of minPwdAge. But you can assign zero for the maximum password age for the domain in Group Policy. The system will then assign the largest possible negative value to the maxPwdAge attribute. This corresponds to 29,227 years in the future. Group Policy displays the value as "Never".

↑ Return to Top


Example Attributes

The following table documents allowed values for various Large Integer attributes in Active Directory.

lDAPDisplayName of Attribute Type Can Have no Value Minimum Allowed Maximum Allowed
maxStorage Number Yes 0 2^63 - 1
rIDAllocationPool Number No 0 2^63 - 1
uSNChanged Number No 0 2^63 - 1
lastLogon DateTime No 0 -
lastLogonTimeStamp DateTime Yes 0 -
pwdLastSet DateTime No 0 -
accountExpires DateTime No 0 2^63 - 1
lockoutTime DateTime Yes 0 -
badPasswordTime DateTime Yes 0 -
lockoutDuration TimeSpan No -2^63 0
maxPwdAge TimeSpan No -2^63 0
minPwdAge TimeSpan No -2^63 0

Technically, all of the Number and DateTime attributes in the table above can have the maximum value allowed. But in practice the DateTime attributes will never have values greater than the value corresponding to the current date. The exception is the accountExpires attribute. If an account in Active Directory has never had an expiration date, then the largest possible value will be assigned by the system. If the account has an expiration date, but it is removed in ADUC by selecting "Never" on the "Account" tab, then the system assigns 0 to the accountExpires attribute.

↑ Return to Top


ADSI

ADSI (Active Directory Service interface) is a library of routines that provide interfaces for various directory namespaces, such as Active Directory. ADSI provides the IADsLargeInteger interface that allows applications and scripts to handle Large Integer values. This interface treats these values as an object and provides two methods, the HighPart and LowPart methods. These break up the 64-bit values into high and low 32-bit parts. The Large Integer value is equal to the value returned by the HighPart method multiplied by 2^32 plus the value returned by the LowPart method.

↑ Return to Top


VBScript

VBScript uses ADSI to deal with Active Directory objects. But VBScript cannot handle 64-bit numbers. It can only handle 15 significant digits. All integers up to 2^53 are represented exactly in VBScript. But 64-bit numbers representing dates typically have 18 decimal digits, so in VBScript the last 3 digits will typically be zeros. This is not a problem, as the script will still be accurate to the nearest 1000 100-nanosecond intervals, which is 0.0001 second.

However, the IADSLargeInteger interface has a quirk. Because of the way 32-bit numbers are handled, the LowPart method can return negative values. When this happens, adding the LowPart value to 2^32 times the HighPart value will be wrong by 2^32 ticks, which is 7 minutes 9.5 seconds. To compensate for this, you need to increase the value returned by the HighPart method by one whenever the value returned by the LowPart method is negative.

Below is example VBScript code to retrieve the lockoutDuration attribute of the domain object.

' Bind to the domain object in Active Directory.
Set objDomain = GetObject("LDAP://dc=MyDomain,dc=com")
' Retrieve lockoutDuration with IADsLargeInteger interface.
Set objDuration = objDomain.lockoutDuration
lngHigh = objDuration.HighPart
lngLow = objDuration.LowPart
' Adjust for error in IADsLargeInteger interface.
If (lngLow < 0) then
    lngHigh = lngHigh + 1
End If
' Calculate number of 100-nanosecond intervals.
lngDuration = (lngHigh * (2^32)) + lngLow
' Convert to minutes.
lngDuration = -lngDuration / (60 * 10000000)
Wscript.Echo "Domain policy lockout duration in minutes: " & lngDuration

↑ Return to Top


PowerShell

PowerShell DateTime and TimeSpan values are also represented in ticks (100-nanosecond intervals). However, the zero date in PowerShell (and .NET Framework) is 12:00 AM January 1, 0001 (There was no year 0000). This is 1600 years earlier than the zero date in Active Directory. The following table documents some dates and how they are represented in PowerShell and Active Directory.

Date (UTC) PowerShell Ticks Active Directory Ticks
January 1, 0001, 00:00:00 0 N/A
January 1, 1601, 00:00:00 504,911,232,000,000,000 0
May 21, 2015, 13:00:00 635,678,100,000,000,000 130,766,868,000,000,000
December 31, 9999, 23:59:59.9999999 3,155,378,975,999,999,999 2,650,467,743,999,999,999
September 14, 30828, 02:48:05.478 N/A 9,223,372,036,854,775,807

The difference between the number of PowerShell (or .NET Framework) ticks and the number of Active Directory ticks that represents any particular date is the number of ticks in 1600 years. This is 504,911,232,000,000,000 ticks.

Using the IADsLargeInteger interface in PowerShell is a bit tricky. The script below demonstrates. The trick is to specify the Value property of pwdLastSet. Using IADsLargeInteger requires only PowerShell V1.

$User = [ADSI]"LDAP://CN=Jim Smith,ou=West,dc=MyDomain,dc=com"
$Date = $User.pwdLastSet.Value
# Use the IADsLargeInteger interface to invoke the HighPart and LowPart methods.
$High = $Date.GetType().InvokeMember("HighPart", "GetProperty", $Null, $Date, $Null)
$Low = $Date.GetType().InvokeMember("LowPart", "GetProperty", $Null, $Date, $Null)
# Adjust for error in IADsLargeInteger interface.
If ($Low -lt 0)
{
    $High = $High + 1
}
# Display the date the password was last set, in local time.
([Datetime]($High * 4GB) + $Low).AddYears(1600).ToLocalTime()

Note that we still must adjust for the problem with the IADsLargeInteger interface. If pwdLastSet is zero, the code above will display the Active Directory zero date of January 1, 1601 (converted into local time). An error will never be raised, since pwdLastSet will never exceed the maximum number of date ticks allowed in PowerShell. This is not true if we retrieve the accountExpires attribute. The accountExpires attribute can have the huge value mentioned before.

The script below demonstrates an alternative method using the PasswordLastSet property of the Get-ADUser cmdlet. This requires PowerShell V2 and the Active Directory module.

# Display date password last set using PasswordLastSet property of Get-ADUser.
(Get-ADUser -Identity jsmith -Properties PasswordLastSet).PasswordLastSet

The following PowerShell code demonstrates how to retrieve the account expiration date for a specified Active Directory user and display the value in local time. It uses PowerShell V1 techniques. It accounts for both values that correspond to "Never". It also uses the ConvertLargeIntegerToInt64 method instead of the methods exposed by the IADsLargeInteger interface. Note again that we must specify the Value property of accountExpires in order to use this method.

$User = [ADSI]"LDAP://CN=Jim Smith,ou=West,dc=MyDomain,dc=com"

$AcctExp = $User.accountExpires.Value $lngValue = $User.ConvertLargeIntegerToInt64($AcctExp)
# A value of 0 or 2^63-1 means never. If ($lngValue -gt [DateTime]::MaxValue.Ticks) {     $lngValue = 0 } # Convert 64-bit integer into DateTime. $Date = [DateTime]$lngValue If ($Date -eq 0) {     $AcctExpires = "<Never>" } Else {     # Convert Active Directory ticks to PowerShell ticks.     # Also convert from UTC to local time.     $AcctExpires = $Date.AddYears(1600).ToLocalTime() } "Account Expires $AcctExpires"

The expression [DateTime]::MaxValue in the script above is the maximum date allowed in the .NET Framework, the end of the day December 31, of the year 9,999. The expression [DateTime]::MaxValue.Ticks is the equivalent in PowerShell ticks, or 3,155,378,975,999,999,999.

A similar script using the Active Directory module Get-ADUser and PowerShell V2 (or above) follows:

$User = Get-ADUser -Identity jsmith -Properties accountExpires

$lngValue = $User.accountExpires If (($lngValue -eq 0) -or ($lngValue -gt [DateTime]::MaxValue.Ticks)) {     $AcctExpires = "<Never>" } Else {     $Date = [DateTime]$lngValue     $AcctExpires = $Date.AddYears(1600).ToLocalTime() } "Account Expires $AcctExpires"

However, the Get-ADUser cmdlet makes this much easier by exposing the AccountExpirationDate property, demonstrated in this script.

$User = Get-ADUser -Identity jsmith -Properties AccountExpirationDate
"AccountExpires " + $User.AccountExpirationDate

The AccountExpirationDate property converts the accountExpires attribute into a DateTime value in the time zone of the local machine. AccountExpirationDate returns nothing if accountExpires is either zero or 2^63 - 1.

In a similar manner, the PowerShell AD modules provide the following properties to handle Large Integer attributes in Active Directory.

AD Module Property AD Attribute AD Classes
AccountExpirationDate accountExpires User, Computer
AccountLockoutTime lockoutTime User, Computer
LastBadPasswordAttempt badPasswordTime User, Computer
LastLogonDate lastLogonTimeStamp User, Computer
PasswordLastSet pwdLastSet User, Computer
LockoutDuration msDS-LockoutDuration msDS-PasswordSettings (PSO)
LockoutObservationWindow msDS-LockoutObservationWindow msDS-PasswordSettings (PSO)
MaxPasswordAge msDS-MaximumPasswordAge msDS-PasswordSettings (PSO)
MinPasswordAge msDS-MinimumPasswordAge msDS-PasswordSettings (PSO)

The first five properties in the table above convert the corresponding Active Directory attributes into DateTime values in the local time zone. The remaining properties in the table convert the AD attributes into TimeSpan values in the format dd.hh:mm:ss, where dd is days, hh is hours, mm is minutes, and ss is seconds. These properties always display the TimeSpans as positive. The last four entries in the table are properties exposed by the Get-ADFineGrainedPasswordPolicy cmdlet. They deal with attributes of Password Settings Objects (PSO) in the "Password Settings Container" of Active Directory.

↑ Return to Top


NOTES

The above discussion ignores the days lost when the switch was made from the Julian to the Gregorian calendar in the year 1582. October 4 was followed by October 15 that year. In fact, this calendar change happened much later in most parts of the world. But this does not affect any dates in Active Directory.

We also ignore any leap seconds. Since 1972 leap seconds have occasionally been added in the last minute of either June 30 or December 31. This is to compensate for irregularities in the rotation of the earth. Leap seconds can also be subtracted, but it has never been necessary yet. The time of events saved in Active Directory, such as lastLogon or pwdLastSet, reflect the affect of leap seconds at the time they are saved. The domain controller that hosts the PDC Emulator role (which is responsible for time throughout the domain) is synchronized with a time source that should be accounting for any leap seconds. But the calculations referenced here assume exactly 60 seconds every minute.

The only time this matters is when you are dealing with TimeSpans that include one or more leap seconds. For example, using PowerShell, VBScript, or .NET Framework methods to calculate the difference between Noon April 1, 2004 and Noon April 1, 2013 results in exactly 3,287 days (9 years, 2 of which are leap years). This is technically wrong by 3 seconds, because 3 leap seconds were added between these dates.

↑ Return to Top


See Also

↑ Return to Top


Other Resources

↑ Return to Top