A Unix NIS to AD account synchronization script

For shops where Unix NIS is still the authoritative source of accounts, and where AD is implemented, the following script synchronizes NIS accounts to AD.

This tip was submitted to the SearchWin2000.com tip exchange by member Kevin Crandall. Please let other users know how useful it is by rating it below.


For shops where Unix NIS is still the authoritative source of accounts, and where AD is implemented, we developed the following script to synchronize NIS accounts to Active Directory using the following prerequisite software:

  • ActiveState Perl for Win32 installed on a Win2k AD DC
  • OpenSSH for Win32 from NetworkSimplicity with Bundle-libnet installed on the same Win2k DC
  • SSH for Unix
  • Unix2DOS for Unix
  • A Unix account especially for SSH; we chose to create an NIS account named ypadsync
  • Active Directory up and running well
  • An OU in which to place new accounts; in our case, we named the OU 'Fresh_Users_from_NIS'

    The script runs as a Scheduled Task on an AD DC under a domain admin account. All scripts must reside in the same folder on the DC and be ACLed appropriately.

    Exclude files are included for accounts in Unix and AD that are not human -- e.g. service, dbadmin, etc., to avoid constant announcements of lasting discrepancies.

    Variables where [] exist must be specified for your configuration.

    A team treated this task as a project and produced requirements, scope and effort.

    Code:

    : This is the NT batch file to schedule on the DC:
    : Script #1, the one to be scheduled, is an NT batch file.
    : go.cmd

    : Pull NIS information from NIS slave to a text file on the AD DC.

    SSH [unix yp master or slave hostname] -l ypadsync -i
    d:\winnt\ad_sync\id_dsa -C "ypcat passwd|unix2dos" > 
    d:\winnt\ad_sync\passwd
    

    : Invoke script that parses the NIS information into simple text file.

    start /w yppasswd.pl

    : Invoke the script that compares NIS account information with AD accounts. Add accounts where necessary, inform where there are inconsistencies and inform when AD contains an account that no longer exists in NIS, so a human can delete the AD account.

    actual.pl


    #Script 2 actual.pl

    #!/usr/bin/perl

    =Program overview

    VERSION AS OF 9/5/2002, 1600

    The object of this program is to keep the account status of accounts in AD and NIS the same. This program compares the state of accounts in AD and a light version of yppasswd. The program is divided into three parts. The first part established all the variables. This sets up the search object used to get the ADsPath of every account in AD and create a table with them in it.

    The second part reads the account information from AD and NIS and places it in two arrays -- @YP and @AD.

    The third part of the program compares the entries in the two arrays. It starts at the top of @AD and compares each entry agains every entry in @YP. It then goes the other way. The accounts found not to sync up are remembered to a sysadmin through an e-mail. If the account does not exist in AD, but it does in NIS, it is created in the Fresh_Users_from_NIS OU so that it can be dealt with later.

    This program requires that the Bundle-libnet be installed. An easy way to install it is with PPM. The sript has only been tested using ActivePerl from ActiveState.

    Structure of variables

    @AD[x]=userprincipalname (AD username), fullname, locked, sAMAccountName (compatibility name, the one used by NT),account path (path to the account in AD)

    :@YP[x]=username,fullname,locked

    The exclusion file format is one username per line, that is all.

    =cut
    use Win32::OLE;
    use Win32::OLE::Enum;
    use net::smtp;
    
    # Constants
    
    my $smtpserver="[ip address of smtp server]";
    my $adminmail="[e-mail address]";
    my $defaultpwd="[default AD password for account delivery]";
    open(YPFILE,'<','d:\winnt\ad_sync\passwdlite');
    open(EXCLUSION,'<','d:\winnt\ad_sync\exclusion.ad');
    
    # SMTP connection.
    
    $smtp=Net::SMTP::->new($smtpserver);
    
    # Connect to AD.
    
    $userbucket = 
    Win32::OLE->GetObject
    ("LDAP://jax.org/OU=Fresh_Users_from_NIS,dc=jax,dc=org") 
    or die "Unable to connect to domain.\n";
    
    # Create search object.
    
    $searchconn=Win32::OLE->new("ADODB.Connection") or die 
    Win32::OLE->LastError();
    $searchcont=Win32::OLE->new("ADODB.Command") or die Win32::OLE->LastError
    ();
    $searchconn->{Provider}="ADsDSOObject" or die "unable to set search
    provider";
    $searchconn->{Open}="","dc=jax,dc=org","";
    if ($searchconn->{state} ne 1) {
        die "search not active, unable to continue";
    }
    $searchcont->LetProperty("ActiveConnection",$searchconn);
    $searchcont->SetProperty("Properties","Page Size",9999);
    
    $searchcont->{CommandText}="<LDAP://jax.org/dc=jax,dc=org>;(&
    (ObjectClass=User)(ObjectCategory=person));ADsPath;subtree";
    
    # Execute search.
    
    $testasdftest=$searchcont->execute() or die Win32::OLE->LastError();
    
    ###### Get information from AD. ######
    
    $index=0; #start counter
    while (!$testasdftest->EOF) {
        $accountpath=$testasdftest->fields->item(ADsPath)->Value;
        $i=Win32::OLE::->GetObject($testasdftest->fields->item(ADsPath)-
    >Value);
    
    # Adjust for accounts that have their sAMAccountName (NT username) set
    and not the userprincipalname (AD username).
    
        if ($i->{userprincipalname} eq undef or $i->{userprincipalname}
    eq "") {
            $userprincipalname=$i->{sAMAccountName};
        }
        else {
            $userprincipalname=$i->{userprincipalname};
        }
    
    # Adjust for accounts that have no fullname entry.
        
    if ($i->{fullname} eq undef or $i->{fullname} eq "") {
            $fullname=$userprincipalname;
        }
        else {
            $fullname=$i->{fullname};
        }
    
    # Check and see if account is disabled or not. Account is disabled if
    AccountDisabled is not equal to 0; we want a true or false.
    
    #print $i->{AccountDisabled};
        if ($i->{AccountDisabled} eq 0) {
            $locked=0;
        }
        else {
            $locked=1;
        }
    
    # Add to array of users the entry for this user.
        
    $AD[$index]=["$userprincipalname","$fullname","$locked","$i->
    {sAMAccountName}","$accountpath"];
    
    # Advance counter and query results.
    
    #print $index,"  ",$userprincipalname,"\n";
        $index ++;
        $testasdftest->movenext;
    }
    
    ###### Get information from YP file. ######
    
    # Start counter.
    $index=0;
    
    # Set temp = to anything except undef.
    
    $temp=1;
    while ($temp ne undef) {
        $temp=<YPFILE>;
        chomp $temp;
    
    # Write user entry into YP array.
        $YP[$index]=[split(/:/,$temp)];
    
    # Advance counter.
        
    $index ++;
    }
    
    ###### Get information from exclusion file. ######
    
    $exclusion=1;
    $counterexc=0;
    
    while ($exclusion ne undef) {
        $exclusion=<EXCLUSION>;
        chomp $exclusion;
        @exclusion_array[$counterexc]=split(/:/,$exclusion);
        $counterexc ++;
    }
    #print @exclusion_array,"\n";
    
    ###### ADD ALL OBJECTS IN THE FRESH USER BUCKET TO THE EXCLUSION LIST. #####
    
    
    # Create search object.
    $searchconn1=Win32::OLE->new("ADODB.Connection") or die 
    Win32::OLE->LastError();
    $searchcont1=Win32::OLE->new("ADODB.Command") or die 
    Win32::OLE-≶LastError();
    $searchconn1->{Provider}="ADsDSOObject" or die "unable to set search
    provider";
    $searchconn1->{Open}="","dc=jax,dc=org","";
    if ($searchconn1->{state} ne 1) {
        die "search not active, unable to continue";
    }
    $searchcont1->LetProperty("ActiveConnection",$searchconn1);
    $searchcont1->SetProperty("Properties","Page Size",9999);
    $searchcont1->{CommandText}
    ="<LDAP://jax.org/OU=Fresh_Users_from_NIS,dc=jax,dc=org>;(&
    (ObjectClass=User)(ObjectCategory=person));ADsPath";
    
    # Execute search.
    
    my @thingtoadd=undef;
    $testasdftest1=$searchcont1->execute() or die Win32::OLE->LastError();
    my $ad_exc_list=0;
    while (!$testasdftest1->EOF) {
        $accountpath=$testasdftest1->fields->item(ADsPath)->Value;
        
    $i1=Win32::OLE::->GetObject($testasdftest1->fields->item(ADsPath)-
    >Value);
        $userprincipalname1=$i1->{userprincipalname};
        @thingtoadd=split('@',$userprincipalname1);
        @exclusion_array[$counterexc]=$thingtoadd[0];
        $counterexc ++;
        $testasdftest1->movenext;
        $ad_exc_list ++;
    }
    
    ###### Active Directory information processing. ######
    
    print "\n\nActive Directory Information\n\n";
    
    # Loop through entire AD array.
    foreach $index (0..$#AD) {
    
    # If username is in the exclusion list, skip.
        if (excheck() eq 1) {
            next;
        }
        else {
            $notice=1;
    
    # Compare current entry in AD to every entry in YP
    
            foreach $index2 (0..$#YP) {
                if (join('@',$YP[$index2][0],"jax.org") eq
    $AD[$index][0] or 
    $YP[$index2][0] eq $AD[$index][3]) {
    
    # If the account exists, avoid sending e-mail about it.
                    $notice=0;
                    if ($AD[$index][2] ne 
    $YP[$index2][2]) {
    
    ## E-MAIL OUT SAYING THAT ACCOUNT STATE DOES NOT MATCH, actually, to avoid 
    sending the same notice twice. Let the YP processing section take care of 
    this. ##
    
                        print "username $AD[$index][0] exists in 
    both but has different locked states\n";
                    }
                    else {
    #DEBUG print "$AD[$index][0] exists in both\n";
                    }
                }
                else {
    
    # Here just in case any action is needed here, there should not be any.
    
                }
            }
    
    # If account exists in AD but not YP send mail.
    
            if ($notice eq 1) {
                $smtp->mail();
                $smtp->to("$adminmail");
                $smtp->data();
                $smtp->datasend("From: YP-AD Script <yp-ad@jax.org>\n");
                $smtp->datasend("To: it-core-rotation\n");
                $smtp->datasend("Subject: Account exists in AD but not NIS\n");
                $smtp->datasend("$AD[$index][0] in 
    $AD[$index][4] has no matching NIS account\n");
                $smtp->datasend();
                $smtp->dataend();
            }
        }
    }
    
    ###### Yellow pages information processing. ######
    
    # This is the same thing as for AD but reversed and with an account 
    creation if account does not exist in AD.
    
    print "\n\nYellow Pages Information\n\n";
    my $skipped=0;
    my $created=0;
    my $notcreated=0;
    my $excheckypcounter=0;
    foreach $index (0..$#YP) {
        if ($YP[$index][0] ne undef) {
            $create=1;
            if (excheckyp()) {
                $skipped ++;
                $create=0;
            }
            else {
    #       print "$YP[$index][0]:$YP[$index][1]:$YP[$in
    dex][2]\n";
                foreach $index2 (0..$#AD) {
    
    ##### Problem must be here!!!!!!!!!
    
                    if (join('@',$YP[$index][0],"jax.org") eq 
    $AD[$index2][0] or $YP[$index][0] eq 
    $AD[$index2][3]) {
                        $create=0;
                        if ($YP[$index][2] ne
    $AD[$index2][2]) {
    
    ## E-MAIL OUT SAYING THAT ACCOUNT STATE DOES NOT MATCH. ##
    
                            $smtp->mail();
                            $smtp->to($adminmail);
                            $smtp->data();
                            $smtp->datasend("From: YP-AD Script 
    <yp-ad@jax.org>\n");
                            $smtp->datasend("To: it-core-rotationn");
                            $smtp->datasend("Subject: Account status does
    not match\n");
                            $smtp->datasend("$YP[$index][0]\n");
                            if ($YP[$index][2] eq 0) {
                                $smtp->datasend("NIS = Enabled\nAD = Disabled");
                            }
                            else {
                                $smtp->datasend("NIS = Disabled\nAD = Enabled");
                            }
                            $smtp->datasend();
                            $smtp->datasend();
                            print "\nAccount status on $YP[$index][0] does 
    not match\n";
                        }
                    }
                    else {
                    }
                }
                if ($create eq 1) {
                    $created ++;
                    chomp $YP[$index][1];
                    @nameparts=split(/ /,$YP[$index][1]);
    #                print length($YP[$index][1]);
                    if (length($YP[$index][1]) > 50) {
                        substr($YP[$index][1],50)="";
                    }
    #                print length($YP[$index][1]);
    #                print "Nameparts: $nameparts[0] 
    $nameparts[$#nameparts]\n";
                    $newuser = 
    $userbucket->Create("User",join('',"cn=",$YP[$index][1],"
    ",$YP[$index][0]));
                    
    $newuser->{userprincipalname}=join
    ('@',$YP[$index][0],"jax.org");
                    $newuser->{sAMAccountNAme}=$YP[$index][0];
                    $newuser->SetInfo;
    #  DEBUG            print Win32::OLE->LastError();
                    $newuser->GetInfo;
                    $newuser->{firstname}=$nameparts[0];
                    $newuser->{lastname}=$nameparts[$#nameparts];
                    $newuser->{fullname}=$YP[$index][1];  
                    $newuser->{setpassword}=$defaultpwd;
                    print "Account Created: 
    $newuser->{Fullname}:$newuser->
    {userprincipalname}:$YP[$index][0]\n";
                    $newuser->SetInfo;
    #  DEBUG           
    print Win32::OLE->LastError();
                    $smtp->mail();
                    $smtp->to($adminmail);
                    $smtp->data();
                    $smtp->datasend("From: YP-AD Script <yp-ad@jax.org>\n");
                    $smtp->datasend("To: it-core-rotation\n");
                    $smtp->datasend("Subject: New Account Created\n");
                    $smtp->datasend("$YP[$index][1] was created in the Fresh_Users_from_NIS bucket\n");
                    $smtp->datasend();
                    $smtp->dataend();
                }
                else {
                }
            }
        }
    }
    print "Summary of Actionsn";
    print "Skipped, Created, Not Created, Num in AD, Num in AD exclude 
    list, num in YP\n";
    print "$skipped, $created, $notcreated, $#AD, $ad_exc_list, 
    $excheckypcounter\n";
                            $smtp->mail();
                            $smtp->to("elijahmm@jax.org");
                            $smtp->data();
                            $smtp->datasend("From: YP-AD Script
    <yp-ad@jax.org>\n");
                            $smtp->datasend("To: it-core-rotation\n");
                            $smtp->datasend("Subject: ypad summary\n");
                            $smtp->datasend("Skipped, Created, Not Created, 
    Num in AD, Num in AD exclude list, num in YP\n");
                            $smtp->datasend("$skipped, $created, 
    $notcreated, $#AD, $ad_exc_list, $excheckypcounter\n");
                            $smtp->datasend();
                            $smtp->dataend();
    
    # Close SMTP connection.
    
    $smtp->quit;
    
    sub excheck {
        foreach $excheckcounter (0..$#exclusion_array) {
            if (join('@',$exclusion_array[$excheckcounter],"jax.org") eq
    $AD[$index][0] or $exclusion_array[$excheckcounter] eq
    
    $AD[$index][3]) {
    #            print "Excluded $exclusion_array[$excheckcounter]\n";
                return 1;
            }
        }
    }
    
    sub excheckyp {
        foreach $excheckcounter (0..$#exclusion_array) {
            if ( $exclusion_array[$excheckcounter] eq 
    $YP[$index][0]) {
    #            print "Excluded $exclusion_array[$excheckcounter]\n";
                $excheckypcounter ++;
                return 1;
            }
        }
    }
    
    sub debugexclusion {
        foreach $excheckcounter (0..$#exclusion_array) {
            if ( $exclusion_array[$excheckcounter] eq 
    $YP[$index][0]) {
                print ":$exclusion_array[$excheckcounter]\n";
            }
            else {
                print "Not found but should have been\n";
            }
        }
    }
    

    # Script #3

    =program overview

    Create a lite password file from yppasswd.

    new file format
    username:fullname:locked

    The exclusion file format is one username per line, that is all.

    =cut
    
    open(YPPASSWD,'<','d:\winnt\ad_sync\passwd');
    open(PASSWDLIGHT,'>','d:\winnt\ad_sync\passwdlite');
    open(EXCLUSION,'<','d:\winnt\ad_sync\exclusion');
    
    $temp=1;
    $exclusion=1;
    
    $counter=0;
    
    while ($exclusion ne undef) {
        $exclusion=<EXCLUSION>;
        chomp $exclusion;
        @exclusion_array[$counter]=split(/:/,$exclusion);
        $counter ++;
    }
    
    while ($temp ne undef) {
        $temp=<YPPASSWD>;
        chomp $temp;
        @YP=split(/:/,$temp);
        @temp2=split(/-/,$YP[1]);
        if (excheck() eq 1) {
            next;
        }
        else {
            if ($temp2[0] eq "LOCKED") {
                $locked=1;
            }
            else {
                $locked=0;
            }
            if ($YP[4] eq undef) {
                $YP[4]=$YP[0];
            }
            @comma_test=split(',',$YP[0]);
            $YP[0]=$comma_test[0];
            foreach $comma_counter (1..$#comma_test) {
                $YP[0].=$comma_test[$comma_counter];
            }
            @comma_test=split(',',$YP[4]);
            $YP[4]=$comma_test[0];
            foreach $comma_counter (1..$#comma_test) {
                $YP[4].=$comma_test[$comma_counter];
            }
            if ($YP[0] ne undef) {
                print PASSWDLIGHT "$YP[0]:$YP[4]:$locked\n";
    #            print "$YP[0]:$YP[4]:$locked\n";
            }
        }
    }
    
    sub excheck {
        foreach $excheckcounter (0..$#exclusion_array) {
            if ($YP[0] eq $exclusion_array[$excheckcounter]) {
                return 1;
            }
        }
    }
    

    Example of exclusion lists:

    File exclusion.ad:

  • Administrator
  • Guest
  • Admin
  • TsInternetUser

    File exclusion:

  • Root
  • Admindba
  • Guest
  • Smdba
  • This was first published in June 2003

    Dig deeper on Microsoft Active Directory Scripting

    Pro+

    Features

    Enjoy the benefits of Pro+ membership, learn more and join.

    0 comments

    Oldest 

    Forgot Password?

    No problem! Submit your e-mail address below. We'll send you an email containing your password.

    Your password has been sent to:

    -ADS BY GOOGLE

    SearchServerVirtualization

    SearchCloudComputing

    SearchExchange

    SearchSQLServer

    SearchWinIT

    SearchEnterpriseDesktop

    SearchVirtualDesktop

    Close