Files
Clean-Workstations/cleanworkstations.pl
2025-08-03 16:45:43 +00:00

268 lines
8.4 KiB
Perl

use Net::LDAP;
use Net::Ping;
use Date::Calc 'Add_Delta_DHMS';
use Getopt::Std;
#use POSIX;
sub usage{
print <<EOF;
"cleanworkstations.exe" will list or delete the older worstation accounts (workstations should connect to a DC once a week so 13 days would be the max time you should be out of contact with a workstation.
ex: cleanworkstations.exe -d domain -l ou -b base -y days -u username -p password -x -h
Where:
domain: Name of the closest Domain Controller to the machine
the script is being run from. This will get a list
of all DC's as well as use this machine to authenticate
from and delete from if you choose to purge accounts.
(Default is dc-00.mathworks.com)
*ou: Name of the OU to delete records from. ex... "AH"
Only required if no base is defined.
(no default defined)
*base: Alternate base DN: Defaults to
"ou=workstations,ou=$location,dc=ad,dc=mathworks,dc=com"
You will get an error if you don't define the base and
also don't provide a location OU.
*x: Flag to tell program if it should delete all accounts
over #days old otherwise it will just list accounts
that would have been deleted (Default is 30)
*days: Number of days since the machine has been seen that
will be up on the report or be deleted (30 day default)
!username: The admins netbios username that has rights to delete
accounts. (Only used if you are deleting accounts)
!password: Coresponding password for the administrator running
the script. (Only used if you are deleting accounts)
*h: Prints this help file
All *'s are optional and will be have the correct defaults for each domain
if left out.
All !'s are optional and are only used if you are going to programatically
delete accounts.
EOF
exit( 1 );
}
getopts( 'xhl:d:y:b:u:p:' );
my $delete = $Getopt::Std::opt_x;
my $location = $Getopt::Std::opt_l;
my $domain = ( length( $Getopt::Std::opt_d ) ) ? $Getopt::Std::opt_d : "dc-00.mathworks.com";
my $base = ( length( $Getopt::Std::opt_b ) ) ? $Getopt::Std::opt_b : "ou=workstations,ou=$location,dc=ad,dc=mathworks,dc=com";
my $days = ( length( $Getopt::Std::opt_y ) ) ? $Getopt::Std::opt_y : 30;
my $username = $Getopt::Std::opt_u;
my $password = $Getopt::Std::opt_p;
my $help = $Getopt::Std::opt_h;
chomp $domain;
chomp $location;
$domain = lc($domain);
$location = lc($location);
if (( $help ) || (((!length($location)) && ($base eq "ou=workstations,ou=$location,dc=ad,dc=mathworks,dc=com") )))
#if (( $help ) || (((!length($location)) && ($base = "ou=workstations,ou=,dc=ad,dc=mathworks,dc=com")))
{
usage();
exit( 1 );
};
print "\nDC = $domain\nWorkstation OU = $location\nNumber Days = $days\nDelete? = $delete\n\n";
# I think this can be 1 hour off due to changes in GMT over daylight savings etc...
#$changefromNTepoch = 11644473600; #diference between 1601 and 1970 in seconds
$changefromNTepoch = 116444736000000000; #diference between 1601 and 1970 in 100 milliseconds
$time = time;
$dc= $domain;
$~ = OutputFormat;
$^ = OutputFormat_Top;
$= = 9999;
$outfile = "expiredworkstations.$location.txt";
print "Retrieving list of domain controllers";
getDCs();
print "\nComplete list of domain controllers obtained from $dc:\n";
foreach $DC ( sort @DCs ) { print "$DC\n";};
print "\nQuerying domain controllers (Can take a while - one tick per DC)";
foreach $DC ( sort @DCs ) {
print ".";
if (ping($DC)) {
# my $base = "ou=workstations,ou=$location,dc=ad,dc=mathworks,dc=com";
my $search = "(&(objectCategory=computer))";
my $ldap = new Net::LDAP("$DC", port=>389) or die "$@";
$ldap->bind(version=>3);
my $msg = $ldap->search(
base => "$base",
filter=>"$search",
callback => \&callback);
die $msg->error if $msg->code;
sub callback {
my ($search, $entry) = @_;
$entry = $search->shift_entry;
my @results=();
if (defined $entry and $entry->get_value("cn") and $entry->get_value("lastLogon")) {
$cn = $entry->get_value("cn");
$lastlogon = $entry->get_value("lastLogon");
push @output, "$cn,$lastlogon,$DC";
}
}
$ldap->unbind;
}
else { print "\n\nCannot access $dc!!!! Exiting program, will not be accurate unless all dc\'s are accessible.\n\n"; exit }
}
print "\n\n";
foreach $line ( sort @output) {
my @data = split/,/, $line;
my @pdata = split/,/, $pline;
if (@data[0] eq @pdata[0] && @data[1] > @pdata[1]) {
pop @outputdata;
push @outputdata, "@data[0],@data[1],@data[2]";
}
if (@data[0] ne @pdata[0]) {
push @outputdata, "@data[0],@data[1],@data[2]";
}
$pline = $line;
}
open (outfile,">$outfile") || die "Can't open output file: $!\n"; # open results to print out
if ($delete) {
# bind to DC anon and look up DN of admin running delete option
$ldap = new Net::LDAP("$domain", port=>389) or die "$@";
$ldap->bind( version => 3 );
my $search = "(&(cn=$username)(objectClass=user))";
my $attrib = "distinguishedName";
my $result = $ldap->search(base=>"dc=ad,dc=mathworks,dc=com", filter=>"$search",attrs=>"$attrib");
die $result->error if $result->code;
my @entries = $result->entries;
foreach $entry (@entries) { @dn = $entry->get(distinguishedName);};
$admindn= @dn[0];
print "\nAdminDN:$admindn\n";
$ldap->unbind;
# Rebind as admin in order to delete objects
$ldap = new Net::LDAP("$domain", port=>389) or die "$@";
$ldap->bind( version => 3,
dn => $admindn,
password => $password,
);
};
foreach $line ( sort @outputdata) {
my @data = split/,/, $line;
$netbiosname = @data[0];
$lastloggedon = dateconv(@data[1]);
$rawdate = @data[1];
$logondc = @data[2];
$cutofftime = $days*24*60*60;
$age = ($time - (($data[1]-$changefromNTepoch)/10000000));
if ( $age > $cutofftime )
{
$kill = "Delete";
$fulldn = "cn=$netbiosname,$base";
if ($delete)
{
# delete record from AD
$ldap->delete( $fulldn ) or die "$@";
}
write;
write outfile;
}
else {
write;
};
$kill = "";
};
close (outfile);
if ($delete ) {$ldap->unbind;};
sub ping {
my ($host) = @_;
my $p = Net::Ping->new("icmp");
return 1 if ($p->ping($host, 2));
return 0;
}
sub getDCs {
my $base = "ou=domain controllers,dc=ad,dc=mathworks,dc=com";
my $search = "(&(objectCategory=computer))";
my $ldap = new Net::LDAP("$dc", port=>389) or die "$@";
$ldap->bind(version=>3);
my $msg = $ldap->search(
base => "$base",
filter=>"$search",
callback => \&dccallback);
die $msg->error if $msg->code;
sub dccallback {
my ($search, $entry) = @_;
$entry = $search->shift_entry;
my @results=();
if (defined $entry and $entry->get_value("cn")) {
print ".";
$cn = $entry->get_value("cn");
push @DCs, "$cn";
}
}
}
sub dateconv {
my ($vt_filetime) = @_;
# Disregard the 100 nanosecond units (rounding might be better)
$vt_filetime = substr($vt_filetime, 0, 11);
my $days = int( $vt_filetime / (24*60*60) );
my $hours = int( ($vt_filetime % (24*60*60)) / (60*60) );
my $mins = int( ($vt_filetime % (60*60)) / 60 );
my $secs = $vt_filetime % 60 ;
my @date = Add_Delta_DHMS(1601, 1, 1, 0, 0, 0, $days, $hours, $mins, $secs);
$year = sprintf("%02d", @date[0]);
$month = sprintf("%02d", @date[1]);
$day = sprintf("%02d", @date[2]);
$hour = sprintf("%02d", @date[3]);
$minute = sprintf("%02d", @date[4]);
$second = sprintf("%02d", @date[5]);
return "$year-$month-$day $hour:$minute:$second";
}
format OutputFormat_Top =
Host Name Last Logon Logon DC delete?
-----------------------------------------------------------------
.
format OutputFormat =
@<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<< ^<<<<<<<<<<
$netbiosname $lastloggedon $logondc $kill
.
format outfile_TOP =
Host Name
---------------------------------------------------------------------------------------------------------------------
.
format outfile =
@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<< ^<<<<<<<<<<
$fulldn $lastloggedon $logondc $kill
.