sourcecode
BravoTwoZero
<CODE>
#!perl
#
# This script runs against the PDC to get a user list. Then, the user list
# array has a last logoff lookup to each DC as well as a lookup against
# your Exchange server. Yes, it's ugly and amateurish. It outputs a list
# to stdout as a comma-delimited list, suitable for piping to a file for
# sorting in the spreadsheet of your choice.
#
# You'll need:
# Win32::AdminMisc from www.roth.net
# Net::LDAP
# Win32::NetAdmin
#
# In my case, I stuck with AS 5.6 so the roth ppm install would work. And,
# 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 have
# yet to convince myslef that they are. Only a domain logon counts in changing
# 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 because
# of the cached nt logon on their laptops.
#
# I've also added a column for password age. It is presented in seconds,
# 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 = 'your.exchangeserver.com';
my $ldapbase = 'c=US';
my @attrs = (
'title',
'department',
'physicalDeliveryOfficeName',
'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 name\n";
Win32::NetAdmin::GetDomainController("","",$PDC);
Win32::NetAdmin::GetServers($PDC,$domain,SV_TYPE_DOMAIN_CTRL,\@pdc);
Win32::NetAdmin::GetServers($PDC, $domain, SV_TYPE_DOMAIN_BAKCTRL, \@bdc);
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 normally be
print "\"Logon\",\"Username\",\"Emplid\",\"Details\",\"Title\",\"Department\",\"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;
Win32::AdminMisc::UserGetAttributes(
$server,
$key,
$fullname,
$password,
$passwordAge,
$privilege,
$homeDir,
$comment,
$flags,
$scriptPath)
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->code;
$mesg = $ldap->unbind;
my $logoff = "0";
foreach $dc (@dc) {
Win32::AdminMisc::UserGetMiscAttributes("\\\\$dc", $key, \%Hash)
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 normally be
print "\"$key\",\"$fullname\",\"$emplid\",\"$comment\",\"$title\",\"$dept\",\"$office\",";
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";
}
</CODE>
<p>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.
<p>The only variable you need to change is the $ldapsrv scalar near the beginning. Everything else will (hopefully) just work.
<p>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!
<p>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.
<p>2004-11-16: Tweaked a lot of little stuff
<ul>
<li>Changed output to comma-separated from tab-separated
<li>Wrapped the data in double-quotes for the csv output
<li>Added password age variable (for auditing)
<li>Left my company-specific employee ID attrib in place with an explanation (since someone may find it useful)
</ul>
NT Admin
<a href="http://www.perlmonks.org/index.pl?node_id=363614">
BravoTwoZero
</a>