Discussion:
How to get/set accountExpires?
(too old to reply)
Ondrej Krajicek
2004-08-08 14:41:48 UTC
Permalink
Hello,

I have problems with getting/setting accountExpires attribute
for user accounts. I am working in Python and Visual C++ .NET 2003
with no success.

The Schema Reference states, that the Account-Expires value
is the number of seconds elapsed from the beginning
of the epoch. But, for instance, when I manually set the expiration
date using the 'Active Directory Users and Computers' snap-in to
tomorrow and get it back via ADSI, I got value which is totally
different from what I expect:

today: 1091975505 (8th August 2004, 16:31)
tomorrow: today + 24 * 60 * 60 = 1092061905 (9th August 2004, 16:31)
adsi returns IADsLargeInteger: 924692480 (lowpart), 29654620 (highpart);
while I would expect something like this:
1092061905 (lowpart), 0 (highpart)

Spending a whole day on this, I am getting the feeling that I black
magic is involved here. Do you have any clues, code samples
how to get/set account expiration date (accountExpires) preferably
in C++ ?

I haven't found anything useful using google or MSDN.

Thanks,

Ondra
--
____________________________\\--//_________________________
Ondrej Krajicek \\// ***@ics.muni.cz
Institute of Computer Science,||Masaryk University Brno, CR
_____________________________//\\__________________________
Ondrej Krajicek
2004-08-10 18:13:53 UTC
Permalink
I am having the same problem, found a script interface which makes it look
real easy, you could try this with python (Pardon my VBscript, I am terrible
with python).
Set objUser = GetObject _
("LDAP://cn=myerken,ou=management,dc=fabrikam,dc=com")
objUser.AccountExpirationDate = "03/30/2003"
objUser.SetInfo
This was snipped out of the scriptcenter on technet.
I am aware of this, but setting the expiration date using
AccountExpirationDate property of IADsUser using string is IMHO locale
specific. So when someone changes locale settings on
a server where the script is being run (unattended), it gets
immediately broken.

I've found a note using google news search that states, contrary to the
documentation, the time is indeed based on the NT epoch (see the
FILETIME structure for more info), which is number of 1/10 seconds from
1st Jan 1601, could anyone confirm this?

Thanks,

Ondra
--
____________________________\\--//_________________________
Ondrej Krajicek \\// ***@ics.muni.cz
Institute of Computer Science,||Masaryk University Brno, CR
_____________________________//\\__________________________
Joe Richards [MVP]
2004-08-10 19:54:19 UTC
Permalink
The accountExpires attribute is an int8 (64 bit value) that is the number of 100
nanosecond intervals since Jan 1, 1601.

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/adschema/adschema/a_accountexpires.asp

--
Joe Richards Microsoft MVP Windows Server Directory Services
www.joeware.net
Post by Ondrej Krajicek
Hello,
I have problems with getting/setting accountExpires attribute
for user accounts. I am working in Python and Visual C++ .NET 2003
with no success.
The Schema Reference states, that the Account-Expires value
is the number of seconds elapsed from the beginning
of the epoch. But, for instance, when I manually set the expiration
date using the 'Active Directory Users and Computers' snap-in to
tomorrow and get it back via ADSI, I got value which is totally
today: 1091975505 (8th August 2004, 16:31)
tomorrow: today + 24 * 60 * 60 = 1092061905 (9th August 2004, 16:31)
adsi returns IADsLargeInteger: 924692480 (lowpart), 29654620 (highpart);
1092061905 (lowpart), 0 (highpart)
Spending a whole day on this, I am getting the feeling that I black
magic is involved here. Do you have any clues, code samples
how to get/set account expiration date (accountExpires) preferably
in C++ ?
I haven't found anything useful using google or MSDN.
Thanks,
Ondra
Ondrej Krajicek
2004-08-11 15:43:49 UTC
Permalink
Post by Joe Richards [MVP]
The accountExpires attribute is an int8 (64 bit value) that is the
number of 100 nanosecond intervals since Jan 1, 1601.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/adschema/adschema/a_accountexpires.asp
I must have missed this, thanks a lot. However, various documentation
sources of the Account-Expires attribute errorneously state that the
time is indeed based on UNIX epoch.

Ondra
____________________________\\--//_________________________
Ondrej Krajicek \\// ***@ics.muni.cz
Institute of Computer Science,||Masaryk University Brno, CR
_____________________________//\\__________________________
Joe Kaplan (MVP - ADSI)
2004-08-11 15:55:26 UTC
Permalink
Yes, there are some bad docs out there. As far as I know, every time you
see a 64 bit integer in AD that represents a date/time, it is just a
standard Windows FILETIME which equates to what Joe describes below.
Windows (and .NET) also has some nice APIs for dealing with FILETIME that
make them much easier.

Joe K.
Post by Ondrej Krajicek
Post by Joe Richards [MVP]
The accountExpires attribute is an int8 (64 bit value) that is the
number of 100 nanosecond intervals since Jan 1, 1601.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/adschema/adschema/a_accountexpires.asp
Post by Ondrej Krajicek
I must have missed this, thanks a lot. However, various documentation
sources of the Account-Expires attribute errorneously state that the
time is indeed based on UNIX epoch.
Ondra
____________________________\\--//_________________________
Institute of Computer Science,||Masaryk University Brno, CR
_____________________________//\\__________________________
Joe Richards [MVP]
2004-08-12 22:47:00 UTC
Permalink
If you can point them out, we can get them changed.

--
Joe Richards Microsoft MVP Windows Server Directory Services
www.joeware.net
Post by Ondrej Krajicek
Post by Joe Richards [MVP]
The accountExpires attribute is an int8 (64 bit value) that is the
number of 100 nanosecond intervals since Jan 1, 1601.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/adschema/adschema/a_accountexpires.asp
I must have missed this, thanks a lot. However, various documentation
sources of the Account-Expires attribute errorneously state that the
time is indeed based on UNIX epoch.
Ondra
____________________________\\--//_________________________
Institute of Computer Science,||Masaryk University Brno, CR
_____________________________//\\__________________________
Jason Tyler
2004-08-10 20:41:42 UTC
Permalink
I know nothing about Python, so if you can read VB here is a sample from
another post:

In Active Directory the accountExpires and pwdLastSet attributes store a
FILETIME
which is a 64-bit integer (LargeInteger) value that specifies the number of
100-nanosecond intervals since January 1, 1601 00:00:00 UTC (GMT).
The Net APIs such as NetUserSetInfo and NetUserGetInfo use a structure such
as
USER_INFO_3 which contains a useri3_acct_expires field which stores a
32-bit
unsigned integer value that specifies the number of seconds since January
1, 1970
00:00:00 UTC (GMT).
A DateTime also represents time with a 64 bit integer but specifies the
number of
100-nanosecond intervals since January 1, 1 00:00:00 instead and has an
upper bound
of December 31, 9999 23:59:59. It is possible to encounter
ArgumentOutOfRangeException exceptions when converting the FILETIME value
to a
DateTime value so I suggest having an exception handler to handle this.
The following C# code is an example of how to get and set time values such
as
accountExpires.
using System.DirectoryServices;
using ActiveDs;
// Get the accountExpires property value and cast to a IADsLargeInteger
interface
as the
// underlying ADSI code returns this property as a LargeInteger object.
DirectoryEntry entry = new
DirectoryEntry("LDAP://<domain_or_server>/<distinguished_name>");
PropertyCollection properties = entry.Properties;
IADsLargeInteger largeInteger =
(IADsLargeInteger)properties["accountExpires"].Value;
// Calculate the 64 bit integer (Int64)(long) value from the low and high
32 bit
values.
long filetime = ((long)largeInteger.HighPart << 32) + largeInteger.LowPart;
// Convert to local DateTime. Note that the retrieved value above is in UTC
time
therefore must
// use FromFileTimeUtc.
DateTime dateTime = DateTime.FromFileTimeUtc(filetime);
// Convert a local DateTime to a UTC FILETIME.
dateTime = DateTime.Parse("11/1/03");
filetime = dateTime.ToFileTimeUtc();
// Calculate high and low 32 bit values from 64 bit value.
largeInteger.HighPart = (int)((filetime >> 32) & 0xFFFFFFFF);
largeInteger.LowPart = (int)(filetime & 0xFFFFFFFF);
// Assign updated LargeInteger value to property and commit changes.
properties["accountExpires"].Value = largeInteger;
entry.CommitChanges();
Note that I needed to add a reference to the Active DS Type Library
(Interop.ActiveDs) which provides interoperability with ADSI interfaces
such as
IADsLargeInteger above.
I have also attached a VBScript file that converts a LargeInteger to a Date
value.
It also contains an explanation of the math required to convert from the
FILETIME
64 bit integer time format to the double Date format.

vbscript:

'---------------------------------------------------------------------------
--------
----------------
' LargeIntegerToDate Function
'
' Converts a LargeInteger object representing a FILETIME to a Date data
type.
'
' A FILETIME is a 64-bit integer value that specifies the number of
100-nanosecond
intervals
' since January 1, 1601 (UTC).
'
' A LargeInteger object provides access to the low and high 32 bits of a
64-bit
integer value.
'
' A Date is a 64-bit floating point value that represents the number of
days
since
' December 30, 1899.
'
' Given the low and high 32 bits of a 64-bit value representing the number
of
100-nanosecond
' intervals since January 1, 1601 the following conversions are performed
to
convert this value to
' a value representing the number of days since December 30, 1899 which is
how the
Date data type
' represents time.
'
' The first step is to convert the 64-bit integer value to a 64-bit
floating point
value by
' multiplying the high 32 bits by 4294967296.0 (2^32) and then adding the
low 32
bits. This is
' equivalent to shifting the high 32-bit integer value by 32 bits and then
adding
the low 32-bit
' integer value to form a 64-bit integer value and then converting to a
64-bit
floating point value.
'
' The second step is to perform a unit conversion from 100-nanosecond
intervals to
days. Multiplying
' by 1/10^7 (1E-7) seconds per 100-nanosecond interval converts the value
to
seconds. Multiplying by
' 1/86400 (8.64E-4) days per second converts the value to days. Therefore
multiplying by the
' combined value of 8.64E-11 (8.64E-4 * 1E-7) or dividing by 8.64E11
converts the
value from
' 100-nanosecond intervals to days.
'
' The third and final step is to adjust the epoch by subtracting the
difference
between the epochs
' so that a value of zero (0.0) represents December 30, 1899. As there are
109205
days between
' January 1, 1601 and December 30, 1899, 109205.0 is subtracted from the
value. The
value of 109205
' was arrived at by obtaining the FILETIME value for December 30, 1899
(94353120000000000) and
' converting to days per the unit conversion performed in the second step
above.
'
' Note that the resulting Double data type is converted to a Date data type
by the
CDate() function.
'
' Author: Mark Oluper
' Date: September 6, 2003
'---------------------------------------------------------------------------
--------
----------------

Function LargeIntegerToDate(objTime)
LargeIntegerToDate = CDate(((4294967296.0 * objTime.HighPart +
objTime.LowPart) /
8.64E11) - 109205.0)
End Function


Function LargeIntegerToDouble(objLargeInteger)
LargeIntegerToDouble = 4294967296.0 * objLargeInteger.HighPart +
objLargeInteger.LowPart
End Function


'The following is some example code that uses the conversion function. The
example
is an excerpt for printing
'object properties. Note that overflow must be handled. The example simply
converts
to Double instead for
'display purposes.

'Set objLargeInteger = objPropertyValue.LargeInteger
'Select Case objPropertyEntry.Name
' Case "accountExpires","lastLogon","lastLogonTimestamp","pwdLastSet"
' On Error Resume Next
' vntValue = LargeIntegerToDate(objLargeInteger)
' If Err.Number = 6 Then 'Overflow
' vntValue = LargeIntegerToDouble(objLargeInteger)
' End If
' On Error GoTo 0
' Case Else
' vntValue = LargeIntegerToDouble(objLargeInteger)
'End Select


Jason Tyler
Microsoft Developer Support
Loading...