Problem
You want to find the users whose passwords are about to expire.
Solution
Using a command-line interface
> dsquery user -stalepwd <NumDaysSinceLastPwdChange>
You can also use the FindExpAcc joeware tool with the following syntax:
> findexpacc -pwd
Using Perl
#!perl
# This code finds the user accounts whose password is about to expire
# —— SCRIPT CONFIGURATION ——
# Domain and container/OU to check for accounts that are about to expire
my $domain = ‘<DomainDNSName>’;
my $cont = ”; # set to empty string to query entire domain
# Or set to a relative path in the domain, e.g. cn=Users
# Days since password change
my $days_ago = <NumDaysSinceLastPwdChange> # e.g. 60;
# —— END CONFIGURATION ——–
use strict;
use Win32::OLE;
$Win32::OLE::Warn = 3;
use Math::BigInt;
# Need to convert the number of seconds from $day_ago
# to a large integer for comparison against pwdLastSet
my $past_secs = time – 60*60*24*$days_ago;
my $intObj = Math::BigInt->new($past_secs);
$intObj = Math::BigInt->new($intObj->bmul(’10 000 000′));
my $past_largeint = Math::BigInt->new(
$intObj->badd(‘116 444 736 000 000 000’));
$past_largeint =~ s/^[+-]//;
# Setup the ADO connections
my $connObj = Win32::OLE->new(‘ADODB.Connection’);
$connObj->{Provider} = “ADsDSOObject”;
# Set these next two if you need to authenticate
# $connObj->Properties->{‘User ID’} = ‘<User>’;
# $connObj->Properties->{‘Password’} = ‘<Password>’;
$connObj->Open;
my $commObj = Win32::OLE->new(‘ADODB.Command’);
$commObj->{ActiveConnection} = $connObj;
$commObj->Properties->{‘Page Size’} = 1000;
# Grab the default domain naming context
my $rootDSE = Win32::OLE->GetObject(“LDAP://$domain/RootDSE”);
my $rootNC = $rootDSE->Get(“defaultNamingContext”);
# Run ADO query and print results
$cont .= “,” if $cont and not $cont =~ /,$/;
my $query = “<LDAP://$domain/$cont$rootNC>;”;
$query .= “(&(objectclass=user)”;
$query .= “(objectcategory=Person)”;
$query .= “(!useraccountcontrol:1.2.840.113556.1.4.803:=2)”;
$query .= “(pwdLastSet<=$past_largeint)”;
$query .= “(!pwdLastSet=0));”;
$query .= “cn,distinguishedName;”;
$query .= “subtree”;
$commObj->{CommandText} = $query;
my $resObj = $commObj->Execute($query);
die “Could not query $domain: “,$Win32::OLE::LastError,”\n”
unless ref $resObj;
print “\
nUsers who haven’t set their passwd in $days_ago days or longer:\n”;
my $total = 0;
while (!($resObj->EOF)) {
print “\t”,$resObj->Fields(“distinguishedName”)->value,”\n”;
$total++;
$resObj->MoveNext;
}
print “Total: $total\n”;
Discussion
When a Windows-based client logs on to Active Directory, a check is done against the domain password policy and the user’s pwdLastSet attribute to determine if the user’s password has expired. If it has, the user is prompted to change it. In a pure Windows-based environment, this notification process may be adequate, but if you have a lot of nonWindows-based computers that are joined to an Active Directory domain (e.g., Kerberos-enabled Unix clients), or you have a lot of application and service accounts, you’ll need to develop your own user password expiration notification process. Even in a pure Windows environment, cached logins present a problem because when a user logs into the domain with cached credentials (i.e., when the client is not able to reach a domain controller), this password expiration notification check is not done.
The process of finding users whose passwords are about to expire is a little complicated. Fortunately, the new dsquery user command helps by providing an option for searching for users that haven’t changed their password for a number of days (-stalepwd).The downside to the dsquery user command is that it will not only find users whose password is about to expire, but also users that must change their password at next logon (i.e., pwdLastSet = 0).The Perl solution does not suffer from this limitation.