Recipe 6.28 Finding Users Who Have Not Logged On Recently

This recipe requires the Windows Server 2003 domain functional level.

6.28.1 Problem

You want to determine which users have not logged on recently.

6.28.2 Solution Using a graphical user interface
  1. Open the Active Directory Users and Computers snap-in.

  2. In the left pane, right-click on the domain and select Find.

  3. Beside Find, select Common Queries.

  4. Select the number of days beside Days since last logon.

  5. Click the Find Now button. Using a command-line interface
> dsquery user -inactive <NumWeeks> Using Perl
# This code finds the users that have not logged in over a period of time
# Domain and container/OU to check for inactive accounts
my $domain   = '<DomainDNSName>';  # e.g.
my $cont     = 'cn=Users'; # set to empty string to query entire domain
                           # Or set to a relative path in the domain:
                           #    e.g. cn=Users
# Number of weeks a user needs to be inactive to be returned
my $weeks_ago = <NumWeeks>;  # e.g. 4
# ------ END CONFIGURATION ---------

use strict;
use Win32::OLE;
   $Win32::OLE::Warn = 3;
use Math::BigInt;

# Need to convert the number of seconds since $weeks_ago
# to a large integer for comparison against lastLogonTimestamp
my $past_secs = time - 60*60*24*7*$weeks_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'}   = '<UserUPNOrDN>'; 
# $connObj->Properties->{'Password'}  = '<Password>';
my $commObj                         = Win32::OLE->new('ADODB.Command');
$commObj->{ActiveConnection}        = $connObj;
$commObj->Properties->{'Page Size'} = 1000;

# Grab the default domain name
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 .=    "(lastlogontimestamp<=$past_largeint));";
$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 that have been inactive for $weeks_ago weeks or more:\n";
my $total = 0;
while (!($resObj->EOF)) {
   my $cn  = $resObj->Fields(0)->value;
   print "\t",$resObj->Fields("distinguishedName")->value,"\n";
print "Total: $total\n";

6.28.3 Discussion

As I talked about in Recipe 6.27, in Windows Server 2003 a new attribute on user objects called lastLogonTimestamp contains the approximate last time the user logged on. Using this to find the users that have not logged on in a number of weeks is much easier than the option with Windows 2000, where we would need to query every domain controller in the domain.

The GUI and CLI solutions are straightforward, but the Perl solution is a little more complicated. The code is very similar to that of Recipe 6.27, and I suggest reading that if you are curious about the large integer conversions going on.

6.28.4 See Also

Recipe 6.23 for more on computing large integer timestamps and Recipe 6.27 for more on finding a user's last logon timestamp

    Chapter 3. Domain Controllers, Global Catalogs, and FSMOs
    Chapter 6. Users
    Appendix A. Tool List