Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid

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 examining the Monastery: (2)
As of 2021-01-19 06:38 GMT
Find Nodes?
    Voting Booth?