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!

Community
  • 1
  • 1
dexter
  • 7,063
  • 9
  • 54
  • 71
  • No comments...? How about Group Policy Management Console, my environment is Server 2003, does anybody have expertise with that piece of software. Help people! – dexter Sep 27 '10 at 16:05

4 Answers4

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();
        }
    }
}
rae1
  • 6,066
  • 4
  • 27
  • 48
Larry Smithmier
  • 2,711
  • 2
  • 23
  • 30
  • 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 http://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. – Larry Smithmier Nov 15 '10 at 04:51
  • 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.. – dexter Nov 22 '10 at 20:10
  • 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: http://www.pinvoke.net/default.aspx/advapi32.lsaopenpolicy – dexter Nov 26 '10 at 18:56
  • I will take another look then, and see if I can modify the existing code to handle the GPO. – Larry Smithmier Nov 29 '10 at 02:36
  • @LarrySmithmier Do you have an alternate approach for what dexter is asking? – FMFF Oct 07 '13 at 16:45
  • @FMFF 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. – Larry Smithmier Oct 07 '13 at 17:45
  • 1
    @FMFF take a look at http://stackoverflow.com/questions/9768944/how-can-i-find-out-an-adusers-password-expiry-date-or-days-left-until-password and see if it works for you. – Larry Smithmier Oct 07 '13 at 17:56
  • @LarrySmithmier Is there a chance that user (in username) does not have a right to see maxPwdAge (except the ones that affects him)? – a.farkas2508 Apr 03 '14 at 09:36
  • 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. – soccer7 Oct 09 '15 at 09:43
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");
    }
}
Gerke Geurts
  • 632
  • 4
  • 9
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");
    }
Mukesh Kumar
  • 166
  • 2
  • 5
  • 13
  • 1
    MS recommends against using DirectoryEntry.InvokeGet (see https://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentry.invokeget(v=vs.110).aspx). I have posted an alternate solution. – Tawab Wakil May 16 '18 at 14:42
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.

Tawab Wakil
  • 1,737
  • 18
  • 33
  • 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! – Gerke Geurts May 29 '18 at 15:45
  • 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. – Tawab Wakil May 30 '18 at 16:52
  • 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. – Gerke Geurts May 31 '18 at 20:03
  • As far as implementation of InvokeGet is concerned, see https://github.com/dotnet/corefx/blob/master/src/System.DirectoryServices/src/System/DirectoryServices/DirectoryEntry.cs#L832. – Gerke Geurts May 31 '18 at 20:11
  • 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. – Tawab Wakil May 31 '18 at 22:05