Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?

NT Logon Detail List

by BravoTwoZero (Scribe)
on Aug 24, 2004 at 03:07 UTC ( #385302=sourcecode: print w/replies, xml ) Need Help??
Category: NT Admin
Author/Contact Info BravoTwoZero

We needed a way to find and sort all domain users by account status (enabled, disabled, etc) and/or last logoff time. It could be cleaner, simpler and take less time to run (45 minutes with 7 domain controllers). This takes the logon, adds details from Exchange and calculates the most recent last logoff time before dumping the whole mess to stdout as a comma-delimited list.

The only variable you need to change is the $ldapsrv scalar near the beginning. Everything else will (hopefully) just work.

Good luck... hope it helps... muchos gracias to everyone who ever wrote something related... I'm sure I looked at it a dozen times... feel free to improve it!

2004-08-25: Added pdc/bdc lookup (so you don't have to manually change the array) and fixed a problem with the headers. Thanks to Marza's domain disk space check program for the pointer to the obvious routine for adding the pdc and bdcs generically.

2004-11-16: Tweaked a lot of little stuff

  • Changed output to comma-separated from tab-separated
  • Wrapped the data in double-quotes for the csv output
  • Added password age variable (for auditing)
  • Left my company-specific employee ID attrib in place with an explanation (since someone may find it useful)
# This script runs against the PDC to get a user list. Then, the user 
# array has a last logoff lookup to each DC as well as a lookup agains
# your Exchange server. Yes, it's ugly and amateurish. It outputs a li
# to stdout as a comma-delimited list, suitable for piping to a file f
# sorting in the spreadsheet of your choice.
# You'll need:
# Win32::AdminMisc from 
# Net::LDAP 
# Win32::NetAdmin
# In my case, I stuck with AS 5.6 so the roth ppm install would work. 
# because I've seen the question before, install the perl-ldap package
# with PPM3, not perldap.
# A word about last logoff times. They ain't gospel, or at least I hav
# yet to convince myslef that they are. Only a domain logon counts in 
# that date. Authentication for Exchange and other apps won't roll the
+ clock.
# And, by example, real road warriors may go weeks without a logon bec
# of the cached nt logon on their laptops.
# I've also added a column for password age. It is presented in second
# divided by 86400 to turn it into days and truncated to remove any
# decimals. I don't care if my password is 29.4 days old. I just care 
+that it's
# 29 days old, because our auditors only care that it's 29 days old.
# As always, YMMV.
# But you knew the job was dangerous when you took it.
# Bok bok bok bok!
# (Superchicken... look it up, Fred)

use Win32::AdminMisc;
use Net::LDAP;
use Win32::NetAdmin;

# ldap variables... everything else is automatic
# don't forget to make this your ldap server's FQDN!
my $ldapsrv = '';
my $ldapbase = 'c=US';
my @attrs  =     ( 
        'Extension-Attribute-1' # see comment below

# The main reason for the LDAP lookup in our case is to tie an 
# employee ID that we add as a custom attribute to our Exchange
# accounts. You can leave it alone, and the value will be null
# for you. The only side effect will be an empty column in your
# spreadsheet. Or, comment it out here and in the two other following
# locations (search for the comment EA1 to find them).

my $PDC;
my $domain = Win32::DomainName or die "Unable to obtain the domain nam

Win32::NetAdmin::GetServers($PDC, $domain, SV_TYPE_DOMAIN_BAKCTRL, \@b

my @dc = (@pdc, @bdc);

my $server = "\\\\$pdc[0]";
# EA1: Here's one... delete 
# \"Emplid\", (including the following comma)
# if you don't want an empty column where my employee IDs would normal
+ly be
print "\"Logon\",\"Username\",\"Emplid\",\"Details\",\"Title\",\"Depar
+tment\",\"Office\",\"Status\",\"Last Logoff\",\"Password Age\"\n";

my %hash;
    Win32::AdminMisc::GetUsers($server, "" , \@array)
        or die "GetUsers() failed: $!";

my %months = (
    "Jan" => "01",
    "Feb" => "02",
    "Mar" => "03",
    "Apr" => "04",
    "May" => "05",
    "Jun" => "06",
    "Jul" => "07",
    "Aug" => "08",
    "Sep" => "09",
    "Oct" => "10",
    "Nov" => "11",
    "Dec" => "12"

@keys = sort {lc $a cmp lc $b} (@array);

foreach $key (@keys) {
    my $logoff;
    my $emplid;
    my $title;
    my $dept;
    my $office;
    my $ldap;
    my $mesg;
    $value = $key;
        or die "UserGetAttributes() failed: $!";

# using Net::LDAP, not PerLDAP

    my $filter = "(uid=$key)";
    $ldap = Net::LDAP->new( $ldapsrv ) or die "$@";
    $mesg = $ldap->bind ;
    $mesg = $ldap->search(
                    base   => $ldapbase,
                    filter => $filter,
            attrs  => @attrs
    foreach $entry ($mesg->entries) { 
# EA1: Here's another...  comment out the line below if you don't want
# an empty column where my employee IDs would normally be
        chomp( $emplid = $entry->get_value('Extension-Attribute-1') );
    chomp( $title = $entry->get_value('title') );
    chomp( $dept = $entry->get_value('department') );
    chomp( $office = $entry->get_value('physicalDeliveryOfficeName') )
    $mesg = $ldap->unbind;

    my $logoff = "0";
    foreach $dc (@dc) {
        Win32::AdminMisc::UserGetMiscAttributes("\\\\$dc", $key, \%Has
            or die "UserGetMiscAttributes() failed: $!";
        if ($Hash{USER_LAST_LOGON} gt $logoff) {
            $logoff = $Hash{USER_LAST_LOGON};
    $logoff = localtime($logoff);
    @lf = split(" ", $logoff);
    $yr = $lf[4];
    $mo = $lf[1];
    $da = $lf[2];
    if (10 > $da) { $da = "0" . $da; }
    $logoff = join("", $yr, $months{$mo}, $da);

    $passwordAgeDay = sprintf("%.0f", $passwordAge/86400);

# EA1: Here's the last... delete 
# \"$emplid\", (including the following comma)
# if you don't want an empty column where my employee IDs would normal
+ly be
    print "\"$key\",\"$fullname\",\"$emplid\",\"$comment\",\"$title\",
    if (513 =~ $flags) {
        print "\"enabled\",";
    } elsif (515 =~ $flags) {
        print "\"!DISABLED\",";
    } elsif (579 =~ $flags) {
        print "\"!nochpw\",";
    } elsif (66049 =~ $flags) {
        print "\"!noexpw\",";
    } elsif (66113 =~ $flags) {
        print "\"!noexpw, nochpw\",";
    } elsif (66115 =~ $flags) {
        print "\"!DISABLED\",";
    } else {
        print "\"$flags\",";
    print "\"$logoff\",\"$passwordAgeDay\"\n";

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: sourcecode [id://385302]
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others taking refuge in the Monastery: (6)
As of 2020-09-28 19:16 GMT
Find Nodes?
    Voting Booth?
    If at first I donít succeed, I Ö

    Results (144 votes). Check out past polls.