Active Directory user password expiration date .NET/OU Group Policy
Asked Answered
F

4

14

I have searched the site for information and found this: ASP.NET C# Active Directory - See how long before a user's password expires

which explains how to get the value of when the password expires as per Domain Policy.

My question is this: what if the user has an OU Group Policy that has a different MaxPasswordAge value, overriding the one specified in Domain Group Policy? How to programatically get the OU's Group Policy Object?

Edit: To make this question a little bit more clear, I am adding this edit. What I am after is to being able to tell when user's password expires. As far as I understand that date value can either be governed by domains local policy or by group object policy. I have a Linq2DirectoryService Provider that translates Linq to Ldap queries. So an LDAP query to get the date expiration value would be optimal for this subj. If you answer includes what objects wrappers supported by .net are included into this equation - it would be a dead on answer!

Flowerlike answered 21/9, 2010 at 20:44 Comment(1)
No comments...? How about Group Policy Management Console, my environment is Server 2003, does anybody have expertise with that piece of software. Help people!Flowerlike
D
15

Let me start with http://support.microsoft.com/kb/323750 which contains Visual Basic and VBScript examples and http://www.anitkb.com/2010/03/how-to-implement-active-directory.html which outlines how the maxPwdAge OU setting impacts computers, not users. It also has a comment pointing to AloInfo.exe as a tool from MS that can be used to get password ages.

Here is the example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices;

namespace LDAP
{
    class Program
    {
        static void Main(string[] args)
        {
            string domainAndUsername = string.Empty;
            string domain = string.Empty;
            string userName = string.Empty;
            string passWord = string.Empty;
            AuthenticationTypes at = AuthenticationTypes.Anonymous;
            StringBuilder sb = new StringBuilder();

            domain = @"LDAP://w.x.y.z";
            domainAndUsername = @"LDAP://w.x.y.z/cn=Lawrence E."+
                        " Smithmier\, Jr.,cn=Users,dc=corp,"+
                        "dc=productiveedge,dc=com";
            userName = "Administrator";
            passWord = "xxxpasswordxxx";
            at = AuthenticationTypes.Secure;

            DirectoryEntry entry = new DirectoryEntry(
                        domain, userName, passWord, at);

            DirectorySearcher mySearcher = new DirectorySearcher(entry);

            SearchResultCollection results;
            string filter = "maxPwdAge=*";
            mySearcher.Filter = filter;

            results = mySearcher.FindAll();
            long maxDays = 0;
            if(results.Count>=1)
            {
                Int64 maxPwdAge=(Int64)results[0].Properties["maxPwdAge"][0];
                maxDays = maxPwdAge/-864000000000;
            }

            DirectoryEntry entryUser = new DirectoryEntry(
                        domainAndUsername, userName, passWord, at);
            mySearcher = new DirectorySearcher(entryUser);

            results = mySearcher.FindAll();
            long daysLeft=0;
            if (results.Count >= 1)
            {
                var lastChanged = results[0].Properties["pwdLastSet"][0];
                daysLeft = maxDays - DateTime.Today.Subtract(
                        DateTime.FromFileTime((long)lastChanged)).Days;
            }
            Console.WriteLine(
                        String.Format("You must change your password within"+
                                      " {0} days"
                                     , daysLeft));
            Console.ReadLine();
        }
    }
}
Doy answered 15/11, 2010 at 4:47 Comment(9)
Ah, re-reading I notice that your server environment is 2003, so you don't get to use the Fine Grained Password Policies outlined in technet.microsoft.com/en-us/library/cc770394%28WS.10%29.aspx since it requires 2008+. I believe the article listed is a solution that covers any scenario you have.Doy
yes I am familiar with that example to read the maxPwdAge value through the filter. But my question is..does the value get overwritten if a group policy object that governs passwords exists for a user? If that value does not get overwritten by the fact that there is a gpo defined for that specific user, than the code you provided is no good. I am to verify that now..Flowerlike
was able to return to the task. As suspected your code will not suffice for a general password expiaration detection. The reason being a GPO can override Default Domain Policy at any level. What needs to be used, looks like, is this: pinvoke.net/default.aspx/advapi32.lsaopenpolicyFlowerlike
I will take another look then, and see if I can modify the existing code to handle the GPO.Doy
@LarrySmithmier Do you have an alternate approach for what dexter is asking?Chimkent
@Chimkent I didn't do a pinvoke version. Are you in need of something more than the above? I haven't looked at this recently, there is probably an easier/better way to do it now.Doy
@Chimkent take a look at #9769444 and see if it works for you.Doy
@LarrySmithmier Is there a chance that user (in username) does not have a right to see maxPwdAge (except the ones that affects him)?Lousy
I am able to get maxPwdAge. But pwdLastSet threw "Index was out of bound" exception. I debugged the code and saw there is no pwdLastSet property available.Shrieval
M
11

The following code worked for me to get the password expiration date on both domain and local user accounts:

public static DateTime GetPasswordExpirationDate(string userId, string domainOrMachineName)
{
    using (var userEntry = new DirectoryEntry("WinNT://" + domainOrMachineName + '/' + userId + ",user"))
    {
        return (DateTime)userEntry.InvokeGet("PasswordExpirationDate");
    }
}
Mel answered 20/7, 2011 at 8:41 Comment(7)
I got Error : Exception has been thrown by the target of an invocation.Ginsberg
@GerkeGeurts What does userId typically look like? Is this the same as samAccountName?Hopeless
userId is indeed the SAM account name.Mel
userEntry.InvokeGet("PasswordExpirationDate"); returns and Object not a Date time, is it safe to just cast it?Santanasantayana
Yep, it is safe to cast. I have updated the code sample accordingly.Mel
MS recommends against using DirectoryEntry.InvokeGet (see msdn.microsoft.com/en-us/library/…). I have posted an alternate solution.Dor
Some of the other answers failed at server but it it worked.Hungarian
G
4

Use following method to get expiration date of the account-

public static DateTime GetPasswordExpirationDate(string userId)
    {
        string forestGc = String.Format("GC://{0}", Forest.GetCurrentForest().Name);
        var searcher = new DirectorySearcher();
        searcher = new DirectorySearcher(new DirectoryEntry(forestGc));
        searcher.Filter = "(sAMAccountName=" + userId + ")";
        var results = searcher.FindOne().GetDirectoryEntry();
        return (DateTime)results.InvokeGet("PasswordExpirationDate");
    }
Giffin answered 12/7, 2017 at 11:19 Comment(1)
MS recommends against using DirectoryEntry.InvokeGet (see msdn.microsoft.com/en-us/library/…). I have posted an alternate solution.Dor
D
1

Some of the previous answers rely on the DirectoryEntry.InvokeGet method, which MS says should not be used. So here's another approach:

public static DateTime GetPasswordExpirationDate(UserPrincipal user)
{
    DirectoryEntry deUser = (DirectoryEntry)user.GetUnderlyingObject();
    ActiveDs.IADsUser nativeDeUser = (ActiveDs.IADsUser)deUser.NativeObject;
    return nativeDeUser.PasswordExpirationDate;
}

You'll need to add a reference to the ActiveDS COM library typically found at C:\Windows\System32\activeds.tlb.

Dor answered 16/5, 2018 at 14:23 Comment(5)
In effect this solution does exactly the same as using DirectoryEntry.InvokeGet("PasswordExpirationDate"). Both call into the ActiveDS COM interface. However, it does so in a much more complicated manner!Mel
I cannot confirm or deny that point. But the link I provided authoritatively says InvokeGet should not be used, whereas I could not find a similarly explicit statement regarding the COM interface.Dor
The use of InvokeGet is discouraged because it bypasses the property caching that is effective when using DirectoryEntry.Properties. Because the PasswordExpirationDate property is not stored in the DirectoryEntry.Properties collection, the InvokeGet method must be used to access it.Mel
As far as implementation of InvokeGet is concerned, see github.com/dotnet/corefx/blob/master/src/….Mel
All good points. Then the only reason to use my answer might be if performance was the overriding concern or if you needed to implement some custom behavior. But these reasons should be rare.Dor

© 2022 - 2024 — McMap. All rights reserved.