Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How to determine Password Expired (AD) - pwdLastSet, MaxPwdAge, etc.?

1,921 views
Skip to first unread message

M Kancharla

unread,
Jan 28, 2003, 11:13:18 AM1/28/03
to
I apologize if this topic has already been posted. I have not seen any
successful solution to this item on any NGs.

Using the classes in the DirectoryServices namespace, I am trying to
figure out if an user's password expired (or when it's going to be
expired).

Using the LDAP provider to bind to Active Directory.
Using VB.NET/ASP.NET.

I was hoping to use the "pwdLastSet" attribute, get the value (lDate)
in a Long type (using the HighPart and LowPart), and then use the
System.DateTime.FromFileTime (lDate) to get the date when the password
was last set. However, I have not been able to do this, so far:

I have a couple of questions:

Q1:
Why are the values for "pwdLastSet" attribute different when I use the
ADSI Edit tool and when I read the value in code using
oSearchResult.Properties ("pwdLastSet").Item (0)?

Here are a couple of examples:
Both the accounts have "Password Never Expires" unchecked.

Reading through Code: 1675886196
ADSI Edit: 126881599091505776

Reading through Code: -1849430547
ADSI Edit: 126880813886336192

Q2:
Dim oli As Object = oSearchResult.Properties ("pwdLastSet").Item (0)
Dim lDate As Long = (oli.HighPart * &H100000000) + oli.LowPart
PWDLastSetDate = System.DateTime.FromFileTime (lDate)

When I use the above 3 lines, I get the following exception
"Public member 'HighPart' on type 'Long' not found."

I know I have a reference to ActiveDS.tlb. What do I need to get the
HighPart and LowPart working?

Q3:
Is there a way to read the value "MaxPwdAge" (as set in the Domain
Security Policy) using VB.NET? I need to use this to determine if the
password expired.

Joe Kaplan

unread,
Jan 29, 2003, 10:30:57 AM1/29/03
to

The good news is that DirectorySearcher has some "stuff" inside of it that
automatically converts LongIntegers to Int64 (VB.NET Long) for you. So you
don't need to do the conversion; you just cast to a Long and you are all
set. The bad news is that in my experience it returns the wrong value
because it doesn't shift the bits for the high part properly.

However, if you get PwdLastSet using the DirectoryEntry, it is returned as a
COM Object that is an IADsLargeInteger that you can read the HighPart and
LowPart members from. If you do the math to assemble the long value
yourself, you will get the correct value.

I posted a sample function that I use for converting these values that works
without having a reference to ActiveDs.dll or without using VB.NET late
binding (Option Strict On) if you are interested.

http://groups.google.com/groups?selm=Of027QwwCHA.456%40TK2MSFTNGP09&oe=UTF-8&output=gplain

Cheers,

Joe K.

"M Kancharla" <manoj_k...@yahoo.com> wrote in message
news:3705ef6c.0301...@posting.google.com...

Richard Mueller

unread,
Jan 29, 2003, 1:04:02 PM1/29/03
to
Hi,

This link discusses many of the issues involved:

http://msdn.microsoft.com/library/default.asp?
url=/library/en-us/dnclinic/html/scripting09102002.asp

The article suggests using the "PasswordLastChanged"
property of the IADsUser interface. This "property method"
maps to the PwdLastSet attribute, but returns a normal
date value.

To retrieve the domain MaxPwdAge policy, bind to the
domain and retrieve the MaxPwdAge attribute. However, this
is also an Integer8 (64-bit) value. Similar to PwdLastSet,
it represents the number of 100-nanosecond intervals.

In your message, the value you list for PwdLastSet
retrieved from your code cannot be right. It appears to be
the value for LowPart only. The ADSI Edit value looks
good. I don't use DirectoryServices, but in VBScript, I
use:

Set oUser = GetObject("LDAP://cn=Test2,dc=MyDomain,dc=com")
Set oDate = oUser.PwdLastSet
nDate = #1/1/1601" + (((oDate.HighPart * 2 ^ 32) _
+ oDate.LowPart)/600000000 - nBias)/1440

where nBias is the time zone bias from the local machine
registry in minutes.

When I use ADO to return a value I use:

sDate = oRS.Fields("PwdLastSet")
Set oDate = sDate
... etc.

This leads me to think your problem is in typing the
variables. You may need an intermediate variable, as I do
when I use ADO. I don't know what provides the HighPart
and LowPart methods, but I believe it is ADSI.

One thing not discussed in the article I linked is the
fact that PwdLastSet is not replicated. I can find no
documentation, but I assume that the PasswordLastChanged
property method retrieves a value from only one Domain
Controller. I believe an accurate method would query every
DC in the domain for the largest value. This would not be
necessary if you have only one DC, or you can be sure that
the user always authenticates to the same DC when they
logon, and this is the DC you query for
PasswordLastChanged.

As you can see, the subject can get complex.

Richard

>.
>

M Kancharla

unread,
Jan 29, 2003, 3:31:59 PM1/29/03
to
Joe,

Tried your functions...

I get an exception "System.MissingMethodException: Method
System.Int64.HighPart not found." @ the line

highPart = CType(largeIntType.InvokeMember("HighPart",
BindingFlags.GetProperty Or BindingFlags.Public, _
Nothing, largeInteger, Nothing),
Integer).

Any suggestions?

Thanks

Manoj K


"Joe Kaplan" <ilearnedthi...@noway.com> wrote in message news:<OZjtft6xCHA.1896@TK2MSFTNGP11>...

Joe Kaplan

unread,
Jan 29, 2003, 4:40:55 PM1/29/03
to

What did you pass into the function? I needs to be a large integer value
retrieved through a PropertyValueCollection. It should show up in the
debugger as a System.__COMObject runtime type.

It doesn't work with a ResultPropertyValueCollection value.

I have used this successfully with pwdLastSet this way in my code:

dim user as DirectoryEntry
'bind user object with appropriate constructor:
user = new DirectoryEntry(LDAP://myuserdnhere)

Dim pwdLastSet as Object
pwdLastSet = entry.Properties("pwdLastSet").Value

dim pwdDate as DateTime = GetDateFromLargeInteger(pwdLastSet)

I would expect the method to fail if the thing you passed in wasn't actually
an IADsLargeInteger under the hood.

0 new messages