#!/usr/bin/perl -w
# This script (sophomorix-device) is maintained by Rüdiger Beck
# It is Free Software (License GPLv3)
# If you find errors, contact the author
# jeffbeck@web.de  or  jeffbeck@linuxmuster.net

# modules
use strict;
use warnings;
use Getopt::Long;
Getopt::Long::Configure ("bundling");
use Sophomorix::SophomorixConfig;
use Net::LDAP;
use Net::DNS;
use Net::MAC;
use Date::Calc qw(check_date);
use Data::Dumper;
$Data::Dumper::Indent = 1;
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Useqq = 1;
$Data::Dumper::Terse = 1; 
use JSON;
use File::Basename qw( basename
                       dirname
                     ); 
use Sophomorix::SophomorixBase qw(
                                 print_line
                                 print_title
                                 unlock_sophomorix
                                 lock_sophomorix
                                 log_script_start
                                 log_script_end
                                 log_script_exit
                                 backup_auk_file
                                 get_passwd_charlist
                                 get_plain_password
                                 check_options
                                 config_sophomorix_read
                                 result_sophomorix_init
                                 result_sophomorix_add
                                 result_sophomorix_add_log
                                 result_sophomorix_add_summary
                                 result_sophomorix_check_exit
                                 result_sophomorix_print
                                 dns_query_ip
                                 json_dump
                                 filelist_fetch
                                 );
use Sophomorix::SophomorixSambaAD qw(
                                 AD_school_create
                                 AD_get_passwd
                                 AD_bind_admin
                                 AD_unbind_admin
                                 AD_user_create
                                 AD_computer_create
                                 AD_computer_update
                                 AD_group_create
                                 AD_group_update
                                 AD_group_kill
                                 AD_group_addmember
                                 AD_group_removemember
                                 AD_get_schoolname
                                 AD_get_name_tokened
                                 AD_get_AD_for_device
                                 AD_get_full_devicedata
                                 AD_get_full_groupdata
                                 AD_object_search
                                 AD_user_kill
                                 AD_computer_kill
                                 AD_dns_get
                                 AD_dns_nodecreate_update
                                 AD_dns_zonecreate
                                 AD_dns_kill
                                 AD_dns_zonekill
                                 AD_gpo_listall
                                    );



my @arguments = @ARGV;

# ===========================================================================
# Optionen verarbeiten
# ==========================================================================
$Conf::log_level=1;
my $help=0;
my $info=0;

my $json=0;
my $list_files=0;

my $dry_run=0;
my $no_sync=0;
my $sync=0;

my $birthdate="1970-01-01";
my $add_example_line="";

# schools that have been created in this script
my $role="";
my $school="";
my $prefix="";

# if this counter goes up, sophomorix-device will stop
# after checking the files 
my $error_in_files=0; 

# rooms that have been created while the script is running
my %room_created=();
my $dump_files=0;
my $dump_ad=0;
my $print=0;

# where data from workstation file is saved
my @domcomputers_file=(); # list ordered
my %devices_file=();

my $dns_test=0;
my $dns_show=0;
my $dns_kill=0;


my %host_seen=();
my %mac_seen=();
my %ip_seen=();


# Computers
my @adding_computers=();
my @updating_computers=();
my @killing_computers=();
my @killing_computer_rooms=();

# Rooms
my @updating_rooms=();

# host groups
my @adding_host_groups=();
my @killing_host_groups=();

# dnsZones
my @adding_dnszones=();
my @killing_dnszones=();

# dnsNodes
my @adding_dnsnodes=();
my @killing_dnsnodes=();
my @updating_dnsnodes=();

# devicegroups
my @adding_devicegroups=();
my %adding_devicegroups=();

my @killing_devicegroups=();
my %killing_devicegroups=();

my %addmembers_devicegroups=();
my %killmembers_devicegroups=();

my %update_computer=(); # device -> count
my %update_room=();
my %update_dnsnode=();

my %pxe = qw(ml ok
            );

my $device="";
my $room="";

my %ms_key_ok = ();
$ms_key_ok{"---"}="ok";
$ms_key_ok{""}="ok";


# Parsen der Optionen
my $testopt=GetOptions(
       "help|h" => \$help,
       "i|info" => \$info,
       "json|j+" => \$json,
       "verbose|v+" => \$Conf::log_level,
       "school=s" => \$school,
       "list-files" => \$list_files,
       "p|print" => \$print,
       "dump-ad" => \$dump_ad,
       "dump-files" => \$dump_files,
       "no-sync|nosync" => \$no_sync,
       "sync" => \$sync,
       "dry-run" => \$dry_run,
       "dns-test" => \$dns_test,
       "dns-kill" => \$dns_kill,
       "dns-show" => \$dns_show,
       "device|d=s" => \$device,
       "room|r=s" => \$room,
       "add-example-line=s" => \$add_example_line,
          );


my %sophomorix_result=&result_sophomorix_init("sophomorix-device");
# Prüfen, ob Optionen erkannt wurden
&check_options($testopt,\%sophomorix_result,$json);

# Reading Configuration
my ($ldap,$root_dse) = &AD_bind_admin(\@arguments,\%sophomorix_result,$json);
my $root_dns=&AD_dns_get($root_dse);
my %sophomorix_config=&config_sophomorix_read($ldap,$root_dse,\%sophomorix_result);
my ($smb_admin_pass)=&AD_get_passwd($DevelConf::sophomorix_file_admin,
                                     $DevelConf::secret_file_sophomorix_file_admin);

my @filelist=&filelist_fetch({filetype=>"devices",
                              sophomorix_config=>\%sophomorix_config,
                            });

my $dgr_type=$sophomorix_config{'INI'}{'TYPE'}{'DGR'};

# set linuxmuster.local as a dnsZone needed in files
$devices_file{'dnsZone'}{$root_dns}="seen";

if ($info==0 and 
    $list_files==0 and 
    $dry_run==0 and 
    $no_sync==0 and 
    $sync==0 and 
    $add_example_line eq "" and
    $dump_files==0 and 
    $dump_ad==0 and
    $print==0 and
    $dns_test==0 and
    $dns_kill==0 and
    $dns_show==0
    ){
    $help=1;
}

############################################################################
# --help
############################################################################
if ($help==1) {
   # Scriptname ermitteln
   my @list = split(/\//,$0);
   my $scriptname = pop @list;
   # Befehlbeschreibung
   print('
sophomorix-device adds Devices to the system:
    DomainComputerAccount
    DNS entries 

Options:
  -h / --help
  -v / --verbose
  -vv / --verbose --verbose

List processed files and exit:
  --list-files

Information about devices in AD:
  -i / --info                              (overview over all schools)
  -i / --info --school <school>            (overview over school <school>)

Full information about one/some devices:
  --info --device <device1>,<device2>,...

Full information about one/some devices:
  --info --room <room1>,<room2>,...        (detailed view of room1,...)


Synchronize the devices:
  sophomorix-device --sync

Just do a syntax check:
  sophomorix-device --dry-run  (returns a json object with syntax check results)

Just show what would be done (but do not do it):
  sophomorix-device --no-sync

Show and append example device line:
  --add-example-line /path/to/devices.csv

Managing DNS 
  --dns-test  (Do DNS queries for devices in *.devices.csv)
  --dns-show  (show DNS-Nodes and DNS-Zones created by sophomorix)
  --dns-kill  (remove all DNS-Nodes and DNS-Zones shown by --dns-show)

Dumping data:                     
  --dump-files -j                  (dump contents of user files)
  --dump-ad -j                     (dump AD data)


Please see the sophomorix-device(8) man pages for full documentation
');
   print "\n";
   exit 0;
}



############################################################################
# --add-example-line
############################################################################
if ($add_example_line ne ""){
    print "\n";
    my $source=$sophomorix_config{'INI'}{'PATHS'}{'TEMPLATES'}."/devices.csv.template";
    system("cat $source");
    system("cat $source >> $add_example_line");
    print "\n";

    exit;
}



############################################################################
# --list-files (does not modify system)
############################################################################
if ($list_files==1){
    print "\n";
    &print_title("Reading the following device files:");
    foreach my $file (@filelist){
        print "   * $file\n";
    }
    exit;
}



############################################################################
# --dns-test     (does not modify system)
############################################################################
if ($dns_test==1){
    my $res   = Net::DNS::Resolver->new;
    &print_title("Testing if DNS works for devices in these files:");
    foreach my $device_file (@filelist){
        print "   * $device_file\n";
    }
    foreach my $device_file (@filelist){
        my $filename = basename($device_file);
        $school=$sophomorix_config{'FILES'}{'DEVICE_FILE'}{$filename}{'SCHOOL'};
        open(DEVICES,"$device_file") || 
            die "ERROR: $device_file not found!";
        my $device_line_count=0;  
        while(<DEVICES>){
            $device_line_count++;
            &analyze_device_line($_,$device_file,$filename,$school,$device_line_count);
        }
        close(DEVICES);
    }
    # --dump-files
    if ($dump_files==1){
        &json_dump({json => $json,
                    jsoninfo => "DEVICES_FILE",
                    jsoncomment => "All devices read from all user files",
                    log_level => $Conf::log_level,
                    hash_ref=>\%devices_file,
                    sophomorix_config=>\%sophomorix_config,
                  });
    }
    foreach my $host (@domcomputers_file){
        my $file_ip=$devices_file{'device'}{$host}{"IPv4"};
        my ($dns_result,$message)=&Sophomorix::SophomorixBase::dns_query_ip($res,$host);
        if ($file_ip eq $dns_result){
            print "OK:    $host has IP $file_ip\n";
        } else {
            print "ERROR: $host has IP $file_ip ($dns_result)\n";
        }
    }
    exit;
}



############################################################################
# --dns-show / --dns-kill
############################################################################
if ($dns_show==1 or $dns_kill==1){
    my $res   = Net::DNS::Resolver->new;
    my @zonelist=();
    my %zonehash=();

    # Fetch dnsZones and their DN
    my $base="DC=DomainDnsZones,".$root_dse;
    my $filter="(& (objectclass=dnsZone) (name=*) )";
    my $mesg = $ldap->search(
                      base   => $base,
                      scope => 'sub',
                      filter => $filter,
                      attr => ['cn']
                            );
    my $max_zone = $mesg->count;
    # All dns zones found on this server
    for( my $index = 0 ; $index < $max_zone ; $index++) {
        my $entry = $mesg->entry($index);
        my $zone_name=$entry->get_value('name');
        my $role="";
        if (defined $entry->get_value('sophomorixRole')){
            $role=$entry->get_value('sophomorixRole');
        }
        if ($zone_name eq $root_dns){ 
            # do not add $root_dns cause it will be prepended
            print "   * Skipping dnsZone $zone_name\n";
        } elsif ($role eq $sophomorix_config{'INI'}{'DNS'}{'DNSZONE_ROLE'}) {
            push @zonelist, $zone_name;
        } else {
            print "   * Skipping dnsZone $zone_name\n";
        }
        my ($count,$dn_dns_zone,$cn_dns_zone,$info)=
            &AD_object_search($ldap,$root_dse,"dnsZone",$zone_name);
        $zonehash{$zone_name}{'DN'}=$dn_dns_zone;
        $zonehash{$zone_name}{'CN'}=$cn_dns_zone;
        $zonehash{$zone_name}{'count'}=$count;
    }

    # add the $root_dns zone as first entry (will not be deleted later)
    unshift @zonelist, $root_dns;

    # print output starts here
    my $zone_count=1;

    foreach my $zone (@zonelist){
        &Sophomorix::SophomorixBase::print_title("dnsZone $zone_count) $zone:");
        # dnsNodes in this dnsZone
        my $base=$zonehash{$zone}{'DN'};
        my $filter="(&(objectclass=dnsNode) (sophomorixRole=*))";
        my $mesg = $ldap->search(
                      base   => $base,
                      scope => 'sub',
                      filter => $filter,
                      attr => ['cn','name']
                            );
        my $max_node = $mesg->count;
        for( my $index = 0 ; $index < $max_node ; $index++) {
            my $entry = $mesg->entry($index);
            my $name=$entry->get_value('name');
            my $cn=$entry->get_value('cn');
            my ($ipv4,$message)=&Sophomorix::SophomorixBase::dns_query_ip($res,$cn);
            print "   * $cn (name=$name)  ->  $ipv4\n";
            if ($dns_kill==1){
                &AD_dns_kill({ldap=>$ldap, 
                              root_dse=>$root_dse,
                              root_dns=>$root_dns,
                              dns_zone=>$zone,
                              dns_node=>$name,
                              dns_ipv4=>$ipv4,
                              smb_admin_pass=>$smb_admin_pass,
                              sophomorix_config=>\%sophomorix_config,
                            });
           }
        }
        if ($zone eq $root_dns){
            # not deleting the $root_dns
        } else {
            if ($dns_kill==1){
                &AD_dns_zonekill({ldap=>$ldap, 
                                  root_dse=>$root_dse,
                                  dns_zone=>$zone,
                                  smb_admin_pass=>$smb_admin_pass,
                                  sophomorix_config=>\%sophomorix_config,
                                });
            }
        }
        $zone_count++;
    }
    exit;
}



# --dump-ad
if ($dump_ad==1){
    my ($ref_AD_device) = &AD_get_AD_for_device({ldap=>$ldap,
                                                 root_dse=>$root_dse,
                                                 root_dns=>$root_dns,
                                                 sophomorix_config=>\%sophomorix_config,
                                               });
    &json_dump({json => $json,
                jsoninfo => "SEARCH",
                jsoncomment => "AD Content",
                hash_ref=>$ref_AD_device,
                sophomorix_config=>\%sophomorix_config,
               });
}



# --print
if ($print==1){
    my ($ref_AD_device) = &AD_get_AD_for_device({ldap=>$ldap,
                                                 root_dse=>$root_dse,
                                                 root_dns=>$root_dns,
                                                 sophomorix_config=>\%sophomorix_config,
                                               });
    &print_devices_latex($ref_AD_device);
    exit;
}



# all --info option go here
if ($info==1 and $room ne ""){
    ##################################################
    # --info --room <room>
    my $ref_groups=&AD_get_full_groupdata({ldap=>$ldap,
                                           root_dse=>$root_dse,
                                           root_dns=>$root_dns,
                                           grouplist=>$room,
                                           sophomorix_config=>\%sophomorix_config,
                                          });

    #print Dumper($ref_groups);
    my $jsoninfo="ROOM";
    my $jsoncomment="Room";
    &json_dump({json => $json,
                jsoninfo => $jsoninfo,
                jsoncomment => $jsoncomment,
                object_name => $school,
                log_level => $Conf::log_level,
                hash_ref => $ref_groups,
                sophomorix_config => \%sophomorix_config,
               });


    exit;
} elsif ($info==1 and $device ne ""){
    ##################################################
    # --info --device <device>
    my $ref_devices=&AD_get_full_devicedata({ldap=>$ldap,
                                             root_dse=>$root_dse,
                                             root_dns=>$root_dns,
                                             devicelist=>$device,
                                             sophomorix_config=>\%sophomorix_config,
                                            });
    #print Dumper($ref_devices);
    my $jsoninfo="DEVICE";
    my $jsoncomment="One sophomorix device";
    &json_dump({json => $json,
                jsoninfo => $jsoninfo,
                jsoncomment => $jsoncomment,
                object_name => $device,
                log_level => $Conf::log_level,
                hash_ref => $ref_devices,
                sophomorix_config => \%sophomorix_config,
               });
    exit;
} elsif ($info==1){
    ##################################################
    # --info (overview)
        my ($ref_AD_device) = &AD_get_AD_for_device({ldap=>$ldap,
                                                     root_dse=>$root_dse,
                                                     root_dns=>$root_dns,
                                                     sophomorix_config=>\%sophomorix_config,
                                                   });
        my $jsoninfo="DEVICES";
        my $jsoncomment="The sophomorix devices";
        &json_dump({json => $json,
                    jsoninfo => $jsoninfo,
                    jsoncomment => $jsoncomment,
                    object_name => $school,
                    log_level => $Conf::log_level,
                    hash_ref => $ref_AD_device,
                    sophomorix_config => \%sophomorix_config,
                   });
    exit;
} else {
    # skip info stuff
}



################################################################################
# fetching data from AD for sync
################################################################################
my ($ref_AD_device) = &AD_get_AD_for_device({ldap=>$ldap,
                                             root_dse=>$root_dse,
                                             root_dns=>$root_dns,
                                             sophomorix_config=>\%sophomorix_config,
                                           });


############################################################################
# reading and checking files for sync
############################################################################
foreach my $device_file (@filelist){
    my $filename = basename($device_file);
    $school=$sophomorix_config{'FILES'}{'DEVICE_FILE'}{$filename}{'SCHOOL'};
    &print_title("Reading $device_file");
    &result_sophomorix_add_log(\%sophomorix_result,"Reading $device_file");
    open(DEVICES,"$device_file") || 
         die "ERROR: $device_file not found!"; 
    my $device_line_count=0;  
    while(<DEVICES>){
        $device_line_count++;
        &analyze_device_line($_,$device_file,$filename,$school,$device_line_count);
    }
    # sort some lists
    foreach my $room (keys %{$devices_file{'room'}}) {
        @{ $devices_file{'room'}{$room}{'HOSTLIST'} } = sort @{ $devices_file{'room'}{$room}{'HOSTLIST'} };
        @{ $devices_file{'room'}{$room}{'MACLIST'} } = sort @{ $devices_file{'room'}{$room}{'MACLIST'} };
        @{ $devices_file{'room'}{$room}{'IPv4LIST'} } = sort @{ $devices_file{'room'}{$room}{'IPv4LIST'} };

    }
    # sort device_by_school lists
    foreach my $school (@{ $sophomorix_config{'LISTS'}{'SCHOOLS'} }){
        if ($#{ $devices_file{'LISTS'}{'devices_by_school'}{$school} } > 0){
            @{ $devices_file{'LISTS'}{'devices_by_school'}{$school} } = 
                sort @{ $devices_file{'LISTS'}{'devices_by_school'}{$school} };
        }
    }
    close(DEVICES);
}



############################################################################
# Stop if there were errors in the file
############################################################################
&log_script_start(\@arguments,\%sophomorix_result,\%sophomorix_config);
if ($error_in_files > 0){
    # syntax errors -> terminate  
    print "\n";
    print "There were errors in the files.\n";
    print "Please fix them first\n";
    print "\n";

    &result_sophomorix_add_summary({
                     NAME=>"ADD", 
                     RESULT=>$error_in_files, 
                     RESULT_TYPE => "integer",
                     DESCRIPTION_POST => " errors at parsing device files", 
                     DESCRIPTION_PRE => "Errors at parsing device files:", 
                     FORMAT_TYPE => 1,
                     sophomorix_result=>\%sophomorix_result,
			       });
    &log_script_exit("ERROR parsing device files: $error_in_files errors.",1,1,0,
                     \@arguments,\%sophomorix_result,\%sophomorix_config,$json);
} else {
    # no syntax errors -> log and go on
    &result_sophomorix_add_log(\%sophomorix_result,"All *.devices.csv files without syntax error");
    if ($dry_run==1){
        &log_script_end(\@arguments,\%sophomorix_result,\%sophomorix_config,$json);
    }
}


############################################################################
# Decide what must be done
############################################################################
# computer rooms to kill
foreach my $room (keys %{$ref_AD_device->{'room'}}) {
    if (not exists $devices_file{'room'}{$room}){
        # host is not in file anymore
        &push_kill_computer_room($room);
    }
}



# computer accounts to kill
foreach my $device (keys %{$ref_AD_device->{'computer'}}) {
    if (not exists $devices_file{'computer'}{$device}){
        # host is not in file anymore
        &push_kill_computer($device);
    }
}



# computer accounts to add
foreach my $device (keys %{$devices_file{'computer'}}) {
    if (not exists $ref_AD_device->{'computer'}{$device} ){
        # nonexisting --> add account
        &push_add_computer($device);
        #&push_update_computer($device); # update ????
    }
}



# computer accounts to update
foreach my $device (keys %{$devices_file{'computer'}}) {
    if (exists $ref_AD_device->{'computer'}{$device} ){
        my $update=0;
        # update
        if($Conf::log_level>=3){
            print "\n"; 
            print "Test for update $device\n";
            print "Files  9: $devices_file{'computer'}{$device}{'sophomorixRole'}\n";
            print "AD     9: $ref_AD_device->{'compter'}{$device}{'sophomorixRole'}\n";
            print "Files 15: $devices_file{'computer'}{$device}{'sophomorixComment'}\n";
            print "AD    15: $ref_AD_device->{'computer'}{$device}{'sophomorixComment'}\n";
        }
        # sophomorixRole
        if ($devices_file{'computer'}{$device}{'sophomorixRole'} eq $ref_AD_device->{'computer'}{$device}{'sophomorixRole'}){
            # no update if equal
        } elsif ($devices_file{'computer'}{$device}{'sophomorixRole'} eq "" and
                 $ref_AD_device->{'computer'}{$device}{'sophomorixRole'} eq $sophomorix_config{'INI'}{'GLOBAL'}{'COMPUTERROLE_DEFAULT'}
	    ){
            # no update, both are on default value
        } else {
            $update_computer{$device}{'REPLACE'}{'sophomorixRole'}=$devices_file{'computer'}{$device}{'sophomorixRole'};
            $update++;
        }

        # sophomorixComputerIP
        if (defined $ref_AD_device->{'computer'}{$device}{'sophomorixComputerIP'}){
            if ($devices_file{'computer'}{$device}{'sophomorixComputerIP'} eq $ref_AD_device->{'computer'}{$device}{'sophomorixComputerIP'}){
                # no update if equal
            } else {
                $update_computer{$device}{'REPLACE'}{'sophomorixComputerIP'}=$devices_file{'computer'}{$device}{'sophomorixComputerIP'};
                $update++;
            }
        }

        # sophomorixComputerMAC
        if (defined $ref_AD_device->{'computer'}{$device}{'sophomorixComputerMAC'}){
            if ($devices_file{'computer'}{$device}{'sophomorixComputerMAC'} eq $ref_AD_device->{'computer'}{$device}{'sophomorixComputerMAC'}){
                # no update if equal
            } else {
                $update_computer{$device}{'REPLACE'}{'sophomorixComputerMAC'}=$devices_file{'computer'}{$device}{'sophomorixComputerMAC'};
                $update++;
            }
        }

        # device group at device
        if (defined $ref_AD_device->{'computer'}{$device}{'sophomorixIntrinsic1'}){
            if ($devices_file{'computer'}{$device}{'DGR_FROM_FILE'} eq $ref_AD_device->{'computer'}{$device}{'sophomorixIntrinsic1'}){
                # no update if equal
            } else {
                $update_computer{$device}{'REPLACE'}{'sophomorixIntrinsic1'}=$devices_file{'computer'}{$device}{'DGR_FROM_FILE'};
                $update++;
            }
        } else {
           $update_computer{$device}{'REPLACE'}{'sophomorixIntrinsic1'}=$devices_file{'computer'}{$device}{'DGR_FROM_FILE'};
                $update++;
	}

        # sophomorixComputerRoom
        if (defined $ref_AD_device->{'computer'}{$device}{'sophomorixComputerRoom'}){
            if ($devices_file{'computer'}{$device}{'sophomorixComputerRoom'} eq $ref_AD_device->{'computer'}{$device}{'sophomorixComputerRoom'}){
                # no update if equal
            } else {
                $update_computer{$device}{'REPLACE'}{'sophomorixComputerRoom'}=$devices_file{'computer'}{$device}{'sophomorixComputerRoom'};
                $update_computer{$device}{'REPLACE'}{'sophomorixAdminClass'}=$devices_file{'computer'}{$device}{'sophomorixComputerRoom'};
                $update++;
            }
        }

        # sophomorixComment
        if ($devices_file{'computer'}{$device}{'sophomorixComment'} eq $ref_AD_device->{'computer'}{$device}{'sophomorixComment'}){
            # no update if equal
        } elsif ($devices_file{'computer'}{$device}{'sophomorixComment'} eq "" and
                 $ref_AD_device->{'computer'}{$device}{'sophomorixComment'} eq "---"
	    ){
            # no update, both are on default value
        } else {
            $update_computer{$device}{'REPLACE'}{'sophomorixComment'}=$devices_file{'computer'}{$device}{'sophomorixComment'};
            $update++;
        }
        
        if ($update>0){
            $update_computer{$device}{'COUNT'}=$update; # count how many attributes must be updated
            &push_update_computer($device);
        }
    }
}



# host groups to kill
foreach my $host_group (keys %{$ref_AD_device->{'host_group'}}) {
    if (not exists $devices_file{'host_group'}{$host_group}){
        # host_group is not in file anymore
        &push_kill_host_group($host_group);
    }
}



# host groups to add
foreach my $host_group (keys %{$devices_file{'host_group'}}) {
    if (not exists $ref_AD_device->{'host_group'}{$host_group} ){
        # nonexisting --> add group
        &push_add_host_group($host_group);
    }
}



# rooms to update
foreach my $room (keys %{$devices_file{'room'}}) {
    if (exists $ref_AD_device->{'room'}{$room} ){
        my $update=0;
        my $devices_items;
        my $AD_items;

        # HOSTNAME
        $devices_items=$#{ $devices_file{'room'}{$room}{'HOSTLIST'} }+1;
        $AD_items=$#{ $ref_AD_device->{'room'}{$room}{'sophomorixRoomComputers'} }+1;
        if ($devices_items==$AD_items){
            # lists have the same length and are sorted
            my $count=0;
            foreach my $item (@{ $ref_AD_device->{'room'}{$room}{'sophomorixRoomComputers'} }){
                my $other_item=$devices_file{'room'}{$room}{'HOSTLIST'}[$count];
                if ($item ne $other_item){
                    $update++;
                    last;
                }
                $count++;
            }
        } else {
            # update if lists have different lengths
            $update++;
        }

        # IP
        $devices_items=$#{ $devices_file{'room'}{$room}{'MACLIST'} }+1;
        $AD_items=$#{ $ref_AD_device->{'room'}{$room}{'sophomorixRoomMACs'} }+1;
        if ($devices_items==$AD_items){
            # lists have the same length and are sorted
            my $count=0;
            foreach my $item (@{ $ref_AD_device->{'room'}{$room}{'sophomorixRoomMACs'} }){
                my $other_item=$devices_file{'room'}{$room}{'MACLIST'}[$count];
                if ($item ne $other_item){
                    $update++;
                    last;
                }
                $count++;
            }
        } else {
            # update if lists have different lengths
            $update++;
        }

        # HOSTNAME
        $devices_items=$#{ $devices_file{'room'}{$room}{'IPv4LIST'} }+1;
        $AD_items=$#{ $ref_AD_device->{'room'}{$room}{'sophomorixRoomIPs'} }+1;
        if ($devices_items==$AD_items){
            # lists have the same length and are sorted
            my $count=0;
            foreach my $item (@{ $ref_AD_device->{'room'}{$room}{'sophomorixRoomIPs'} }){
                my $other_item=$devices_file{'room'}{$room}{'IPv4LIST'}[$count];
                if ($item ne $other_item){
                    $update++;
                    last;
                }
                $count++;
            }
        } else {
            # update if lists have different lengths
            $update++;
        }

        if ($update>0){
            $update_room{$room}{'COUNT'}=$update; # count how many attributes must be updated
            &push_update_room($room);
        }
    }
}



# dnsNodes to kill
foreach my $dns_node (keys %{$ref_AD_device->{'dnsNode'}{$sophomorix_config{'INI'}{'DNS'}{'DNSNODE_KEY'}}}) {
    if (not exists $devices_file{'dnsNode'}{$dns_node}){
        # host is not in file anymore
        &push_kill_dnsnode($dns_node);
    }
}



# dnsNodes to add
foreach my $dns_node (keys %{$devices_file{'dnsNode'}}) {
    if (not exists $ref_AD_device->{'dnsNode'}{$sophomorix_config{'INI'}{'DNS'}{'DNSNODE_KEY'}}{$dns_node} ){
        # nonexisting --> add account
        &push_add_dnsnode($dns_node);
    } elsif (exists $ref_AD_device->{'dnsNode'}{$sophomorix_config{'INI'}{'DNS'}{'DNSNODE_KEY'}}{$dns_node} ){
        my $ip_system=$ref_AD_device->{'dnsNode'}{$sophomorix_config{'INI'}{'DNS'}{'DNSNODE_KEY'}}{$dns_node}{'IPv4'};
        my $ip_file=$devices_file{'dnsNode'}{$dns_node};
        if ($ip_system ne $ip_file){
            # recreate dns entry
            print "WARNING: $dns_node exists with $ip_system, but should be $ip_file\n";
            &push_kill_dnsnode($dns_node);
            &push_add_dnsnode($dns_node);
        } else {
            if($Conf::log_level>=3){
                print "   OK: $dns_node exists with $ip_system",
                      " (= $ip_file from file)\n";
            }
        }
    }
}



# dnsNodes to update
foreach my $dns_node (keys %{$devices_file{'dnsNode'}}) {
    if (exists $ref_AD_device->{'dnsNode'}{$sophomorix_config{'INI'}{'DNS'}{'DNSNODE_KEY'}}{$dns_node}){
        my $update=0;
        # sophomorixAdminFile
        if ($ref_AD_device->{'dnsNode'}{$sophomorix_config{'INI'}{'DNS'}{'DNSNODE_KEY'}}{$dns_node}{'sophomorixAdminFile'} ne 
            $devices_file{'device'}{$dns_node}{'FILENAME'}){
            $update++;
        }
        # sophomorixComment
        if ($ref_AD_device->{'dnsNode'}{$sophomorix_config{'INI'}{'DNS'}{'DNSNODE_KEY'}}{$dns_node}{'sophomorixComment'} ne 
            $devices_file{'device'}{$dns_node}{'sophomorixComment'}){
            $update++;
        }
        # sophomorixDnsNodename
        if ($ref_AD_device->{'dnsNode'}{$sophomorix_config{'INI'}{'DNS'}{'DNSNODE_KEY'}}{$dns_node}{'sophomorixDnsNodename'} ne 
            $devices_file{'device'}{$dns_node}{'sophomorixDnsNodename'}){
            $update++;
        }
        # sophomorixRole
        if ($ref_AD_device->{'dnsNode'}{$sophomorix_config{'INI'}{'DNS'}{'DNSNODE_KEY'}}{$dns_node}{'sophomorixRole'} ne 
            $devices_file{'device'}{$dns_node}{'sophomorixRole'}){
            $update++;
        }
        # sophomorixSchoolname
        if ($ref_AD_device->{'dnsNode'}{$sophomorix_config{'INI'}{'DNS'}{'DNSNODE_KEY'}}{$dns_node}{'sophomorixSchoolname'} ne 
            $devices_file{'device'}{$dns_node}{'SCHOOL'}){
            $update++;
        }
        # sophomorixComputerIP
        if ($ref_AD_device->{'dnsNode'}{$sophomorix_config{'INI'}{'DNS'}{'DNSNODE_KEY'}}{$dns_node}{'sophomorixComputerIP'} ne 
            $devices_file{'device'}{$dns_node}{'IPv4'}){
            $update++;
        }

        if ($update>0){
            $update_dnsnode{$dns_node}{'COUNT'}=$update; # count how many attributes must be updated
            &push_update_dnsnode($dns_node);
        }
    }
}



# dnsZones to kill
foreach my $dns_zone (keys %{$ref_AD_device->{'dnsZone'}{$sophomorix_config{'INI'}{'DNS'}{'DNSZONE_ROLE'}}}) {
    if (not exists $devices_file{'dnsZone'}{$dns_zone}){
        # dns node is not needed anymore, no host
        &push_kill_dnszone($dns_zone);
    }
}



# dnsZones to add
foreach my $dns_zone (keys %{$devices_file{'dnsZone'}}) {
    if (not exists $ref_AD_device->{'dnsZone'}{$sophomorix_config{'INI'}{'DNS'}{'DNSZONE_ROLE'}}{$dns_zone} and
        not exists $ref_AD_device->{'dnsZone'}{'otherdnsZone'}{$dns_zone}){
        # nonexisting --> add dnszone
        &push_add_dnszone($dns_zone);
    }
}



# devicegroups to kill
foreach my $dgr (keys %{$ref_AD_device->{$dgr_type}}) {
    if (not exists $devices_file{$dgr_type}{$dgr} ){
        # dwg is not needed anymore
        &push_kill_devicegroup($dgr);
    }
}



# devicegroups to create
foreach my $dgr (keys %{$devices_file{$dgr_type}}) {
    if (not exists $ref_AD_device->{$dgr_type}{$dgr} ){
        # nonexisting --> add dgr
        &push_add_devicegroup($dgr);
    }
}



# devicegroup members to add
foreach my $dgr (keys %{$devices_file{$dgr_type}}) {
    # go through all dgr in files
    foreach my $member_file (keys %{$devices_file{$dgr_type}{$dgr}{'member'}}) {
        # go through all members of dgr in files
        # skip if this device has no account
        # the data in $devices_file{'device'}{<device>} uses the small letter name
        if ($devices_file{'device'}{$member_file}{'ACCOUNT'} eq "FALSE"){
            next;
        }
        if (not exists $ref_AD_device->{$dgr_type}{$dgr}{'member_sAMAccountName'}{$member_file}){
	    $addmembers_devicegroups{$member_file}=$dgr;
        }
    }
}



# devicegroup members to kill
foreach my $dgr (keys %{$ref_AD_device->{$dgr_type}}) {
    # go through all dgr in AD
    # if the dgr is going to be deleted, skip removing users
    if (exists $killing_devicegroups{$dgr}){
        print "$dgr is going to be killed anyway, skip testing for members to be killed\n";
        next;
    }

    foreach my $member_AD (keys %{$ref_AD_device->{$dgr_type}{$dgr}{'member_sAMAccountName'} }) {
        # go through all members of dgr in AD
        if (not exists $devices_file{$dgr_type}{$dgr}{'member'}{$member_AD}){
	    $killmembers_devicegroups{$member_AD}=$dgr;
        }
    }
}






# --dump-files
if ($dump_files==1){
    &json_dump({json => $json,
                jsoninfo => "DEVICES_FILE",
                jsoncomment => "All devices read from all user files",
                hash_ref=>\%devices_file,
                sophomorix_config=>\%sophomorix_config,
              });
}


############################################################################
# sync or not
############################################################################
if($no_sync==1){
    # show info about what will be done
    my $count=0;

    # computer rooms
    print "\n";
    $count=$#killing_computer_rooms+1;
    &print_title("Computer rooms that must be killed ($count):");
    foreach my $room (@killing_computer_rooms){
        print "KILL: $room\n";
    }

    # Computer Accounts
    print "\n";
    $count=$#killing_computers+1;
    &print_title("Computer accounts that must be killed: ($count)");
    foreach my $ws (@killing_computers){
        print "KILL: $ws\n";
    }
    print "\n";
    $count=$#adding_computers+1;
    &print_title("Computer accounts that must be added: ($count)");
    foreach my $ws (@adding_computers){
        print "ADD: $ws\n";
    }
    # updates of computer attributes
    print "\n";
    $count=$#updating_computers+1;
    &print_title("Computer accounts that must be updated: ($count)");
    foreach my $ws (@updating_computers){
        print "UPDATE: $ws ($update_computer{$ws}{'COUNT'} attributes)\n";
    }

    # Host Groups
    print "\n";
    $count=$#killing_host_groups+1;
    &print_title("Host groups that must be killed: ($count)");
    foreach my $host_group (@killing_host_groups){
        print "KILL: $host_group\n";
    }
    print "\n";
    $count=$#adding_host_groups+1;
    &print_title("Host groups that must be added: ($count)");
    foreach my $host_group (@adding_host_groups){
        print "ADD: $host_group (sophomorixType: $devices_file{'host_group'}{$host_group}{'sophomorixType'})\n";
    }

    # Rooms
    # updates of group room attributes
    print "\n";
    $count=$#updating_rooms+1;
    &print_title("Rooms that must be updated: ($count)");
    foreach my $room (@updating_rooms){
        print "UPDATE: $room ($update_room{$room}{'COUNT'} attributes)\n";
    }

    # killing dnsNodes
    print "\n";
    $count=$#killing_dnsnodes+1;
    &print_title("dnsNodes that must be killed: ($count)");
    foreach my $dns_node (@killing_dnsnodes){
        print "KILL: $dns_node\n";
    }

    # killing  dnsZones
    print "\n";
    $count=$#killing_dnszones+1;
    &print_title("dnsZones that must be killed: ($count)");
    foreach my $dns_zone (@killing_dnszones){
        print "KILL: $dns_zone\n";
    }

    # adding  dnsZones
    print "\n";
    $count=$#adding_dnszones+1;
    &print_title("dnsZones that must be added: ($count)");
    foreach my $dns_zone (@adding_dnszones){
        print "ADD: $dns_zone\n";
    }

    # adding dnsNodes
    print "\n";
    $count=$#adding_dnsnodes+1;
    &print_title("dnsNodes that must be added: ($count)");
    foreach my $dns_node (@adding_dnsnodes){
        print "ADD: $dns_node\n";
    }

    # updating dnsNodes
    print "\n";
    $count=$#updating_dnsnodes+1;
    &print_title("dnsNodes that must be updated: ($count)");
    foreach my $dns_node (@updating_dnsnodes){
        print "UPDATE: $dns_node ($update_dnsnode{$dns_node}{'COUNT'} attributes)\n";
    }

    # devicegroups
    print "\n";
    $count=$#killing_devicegroups+1;
    &print_title("Devicegroups that must be killed ($count):");
    foreach my $dgr (@killing_devicegroups){
        print "KILL: $dgr\n";
    }
    print "\n";
    $count=$#adding_devicegroups+1;
    &print_title("Devicegroups that must be added: ($count)");
    foreach my $dgr (@adding_devicegroups){
        print "ADD: $dgr\n";
    }
    print "\n";
    {
        my @computers=();
        foreach my $member (keys %addmembers_devicegroups){
            push @computers, $member
        }
	@computers=sort @computers;
        $count=$#computers+1;

        &print_title("Devicegroup memberships to add ($count):");
        foreach my $member (@computers){
            print "ADD: $member to $addmembers_devicegroups{$member}\n";
        }
    }
    print "\n";
    {
        my @computers=();
        foreach my $member (keys %killmembers_devicegroups){
            push @computers, $member
        }
	@computers=sort @computers;
        $count=$#computers+1;
        &print_title("Devicegroup memberships to kill ($count):");
        foreach my $member (@computers){
            print "KILL: $member from $killmembers_devicegroups{$member}\n";
        }
    }

    print "\n--no-sync  finished. Nothing changed!\n";
    &log_script_end(\@arguments,\%sophomorix_result,\%sophomorix_config,$json);
} 

&result_sophomorix_check_exit(\%sophomorix_result,\%sophomorix_config,$json);


############################################################
# Synchronizing 
############################################################

if ($sync==1 and $no_sync==0){ # $no_sync==0 -> SYNC
    ############################################################
    # Kill the rooms of computers
    print "\n";
    my $max_room_count_kill=$#killing_computer_rooms+1;
    &print_title("Killing $max_room_count_kill computer rooms:");
    my $kill_computer_room_count=1;
    foreach my $room (@killing_computer_rooms){
        print "   * Killing Computer Room $room ...\n";
        &AD_group_kill({ldap=>$ldap,
                        root_dse=>$root_dse,
                        root_dns=>$root_dns,
                        group=>$room,
                        type=>"room",
                        group_count=>$kill_computer_room_count,
                        sophomorix_config=>\%sophomorix_config,
                  });
    }

    ############################################################
    # Kill computer accounts
    print "\n";
    my $max_computer_count_kill=$#killing_computers+1;

    &print_title("Killing $max_computer_count_kill computer accounts");
    my $computer_count_kill=1;
    foreach my $computer (@killing_computers){
        &AD_computer_kill({ldap=>$ldap,
                           root_dse=>$root_dse,
                           computer=>$computer,
                           computer_count=>$computer_count_kill,
                           max_computer_count=>$max_computer_count_kill,
                           json=>$json,
                           sophomorix_config=>\%sophomorix_config,
                           sophomorix_result=>\%sophomorix_result,
                         });
        $computer_count_kill++;
    }

    ############################################################
    # Add computer accounts
    print "\n";
    my $max_computer_count_create=$#adding_computers+1;
    &print_title("Adding $max_computer_count_create computer accounts:");
    my $computer_count_create=1;
    foreach my $computer (@adding_computers){
        my $school=$devices_file{'computer'}{$computer}{'SCHOOL'};
        my $room_saved=$devices_file{'computer'}{$computer}{'ROOM'};
        my $room_basename=$devices_file{'computer'}{$computer}{'ROOM_BASENAME'};
        my $ws_saved=$devices_file{'computer'}{$computer}{'COMPUTER'};
        my $filename=$devices_file{'computer'}{$computer}{'FILENAME'};
        my $ipv4=$devices_file{'computer'}{$computer}{'sophomorixComputerIP'};
        my $mac=$devices_file{'computer'}{$computer}{'sophomorixComputerMAC'};
        my $dgr_from_file=$devices_file{'computer'}{$computer}{'DGR_FROM_FILE'};

        # create school
        &AD_school_create({ldap=>$ldap,
                           root_dse=>$root_dse,
                           root_dns=>$root_dns,
                           school=>$school,
                           smb_admin_pass=>$smb_admin_pass,
                           sophomorix_config=>\%sophomorix_config,
                           sophomorix_result=>\%sophomorix_result,
                         });
        my $role;
        if ($devices_file{'computer'}{$computer}{'sophomorixRole'} ne ""){
            $role=$devices_file{'computer'}{$computer}{'sophomorixRole'};
        } else {
            $role=$sophomorix_config{'FILES'}{'DEVICE_FILE'}{$filename}{'sophomorixRole'};
        }
        my $sophomorix_comment;
        if ($devices_file{'computer'}{$computer}{'sophomorixComment'} ne ""){
            $sophomorix_comment=$devices_file{'computer'}{$computer}{'sophomorixComment'};
        }

        &AD_computer_create({ldap=>$ldap, 
                             root_dse=>$root_dse,
                             computer_count=>$computer_count_create,
                             max_computer_count=>$max_computer_count_create,
                             name=>$ws_saved,
                             room=>$room_saved,
                             room_basename=>$room_basename,
                             role=>$role,
                             sophomorix_comment=>$sophomorix_comment,
		             type=>$sophomorix_config{'FILES'}{'DEVICE_FILE'}{$filename}{'sophomorixType'},
                             school=>$school,
                             filename=>$filename,
                             ipv4=>$ipv4,
                             mac=>$mac,
			     dgr_from_file=>$dgr_from_file,
                             json=>$json,
                             sophomorix_config=>\%sophomorix_config,
                             sophomorix_result=>\%sophomorix_result,
                           });
        $computer_count_create++;
        # make sure that the room group exists
        if (not exists $room_created{$school}{$room_basename}){
            my $room_description=$room_basename." in ".$school;
            &AD_group_create({ldap=>$ldap,
                              root_dse=>$root_dse,
                              root_dns=>$root_dns,
                              group=>$room_saved,
                              group_basename=>$room_basename,
                              description=>$room_description,
                              school=>$school,
                              status=>"P",
                              type=>$sophomorix_config{'FILES'}{'DEVICE_FILE'}{$filename}{'sophomorixType'},
    	      		      sub_ou=>"OU=".$room_basename.",".$sophomorix_config{'INI'}{'OU'}{'AD_devices_ou'},
                              smb_admin_pass=>$smb_admin_pass,
                              sophomorixRoomIPs=>\@{ $devices_file{'room'}{$room_saved}{'IPv4LIST'} },
                              sophomorixRoomMACs=>\@{ $devices_file{'room'}{$room_saved}{'MACLIST'} },
                              sophomorixRoomComputers=>\@{ $devices_file{'room'}{$room_saved}{'HOSTLIST'} },
                              sophomorix_config=>\%sophomorix_config,
                              sophomorix_result=>\%sophomorix_result,
                            });
            # remember group
            $room_created{$school}{$room_basename}="already created";
        } else {
            print "   * room $room_basename in school $school already created\n";
        }
        # add computer to its room
        &AD_group_addmember({ldap => $ldap,
                             root_dse => $root_dse, 
                             group => $room_saved,
                             addmember => $ws_saved,
                            });   
    }

    ############################################################
    # Updating computer accounts
    print "\n";
    my $max_computer_count_update=$#updating_computers+1;
    &print_title("Updating $max_computer_count_update computer accounts:");
    my $computer_count_update=1;
    foreach my $computer (@updating_computers){
        my $room_saved=$devices_file{'computer'}{$computer}{'ROOM'};
        my $room_basename=$devices_file{'computer'}{$computer}{'ROOM_BASENAME'};
        my $ws_saved=$devices_file{'computer'}{$computer}{'COMPUTER'};
        my $filename=$devices_file{'computer'}{$computer}{'FILENAME'};

        # make sure that the room group exists
        if (not exists $room_created{$school}{$room_basename}){
            my $room_description=$room_basename." in ".$school;
            &AD_group_create({ldap=>$ldap,
                              root_dse=>$root_dse,
                              root_dns=>$root_dns,
                              group=>$room_saved,
                              group_basename=>$room_basename,
                              description=>$room_description,
                              school=>$school,
                              status=>"P",
                              type=>$sophomorix_config{'FILES'}{'DEVICE_FILE'}{$filename}{'sophomorixType'},
                              sub_ou=>"OU=".$room_basename.",".$sophomorix_config{'INI'}{'OU'}{'AD_devices_ou'},
                              smb_admin_pass=>$smb_admin_pass,
                              sophomorixRoomIPs=>\@{ $devices_file{'room'}{$room_saved}{'IPv4LIST'} },
                              sophomorixRoomMACs=>\@{ $devices_file{'room'}{$room_saved}{'MACLIST'} },
                              sophomorixRoomComputers=>\@{ $devices_file{'room'}{$room_saved}{'HOSTLIST'} },
                              sophomorix_config=>\%sophomorix_config,
                              sophomorix_result=>\%sophomorix_result,
                            });
            # remember group
            $room_created{$school}{$room_basename}="already created";
        }
        &AD_computer_update({ldap=>$ldap,
                             root_dse=>$root_dse,
                             computer=>$computer,
                             computer_count=>$computer_count_update,
                             attrs_count=>$update_computer{$computer}{'COUNT'},
                             replace=>\%update_computer,
                             max_computer_count=>$max_computer_count_update,
                             json=>$json,
                             school=>$devices_file{'computer'}{$computer}{'SCHOOL'},
                             room_basename=>$devices_file{'computer'}{$computer}{'ROOM_BASENAME'},
                             filename=>$devices_file{'computer'}{$computer}{'FILENAME'},
                             name=>$devices_file{'computer'}{$computer}{'COMPUTER'},
                             sophomorix_config=>\%sophomorix_config,
                             sophomorix_result=>\%sophomorix_result,
                           });
        # add computer to its room
        &AD_group_addmember({ldap => $ldap,
                             root_dse => $root_dse,
                             group => $room_saved,
                             addmember => $ws_saved,
                            });
        $computer_count_update++;
    }

    ############################################################
    # kill host_groups
    print "\n";
    my $max_host_group_count_kill=$#killing_host_groups+1;
    &print_title("Killing $max_host_group_count_kill host groups:");
    my $host_group_count_kill=1;
    foreach my $host_group (@killing_host_groups){
        print "Kill group $host_group -> Type: $ref_AD_device->{'host_group'}{$host_group}\n";
        &AD_group_kill({ldap=>$ldap,
                        root_dse=>$root_dse,
                        root_dns=>$root_dns,
                        group=>$host_group,
                        type=>$ref_AD_device->{'host_group'}{$host_group},
                        group_count=>$host_group_count_kill,
                        sophomorix_config=>\%sophomorix_config,
                  });
    }

    ############################################################
    # Add host_groups
    print "\n";
    my $max_host_group_count_create=$#adding_host_groups+1;
    &print_title("Adding $max_host_group_count_create host groups:");
    my $host_group_count_create=1;
    foreach my $host_group (@adding_host_groups){
        my $description="Host group for $devices_file{'host_group'}{$host_group}{'COMPUTER_DOLLAR'}";
        my $sub_ou=$devices_file{'host_group'}{$host_group}{'SUB_OU'}.",".
            $sophomorix_config{'INI'}{'OU'}{'AD_devices_ou'};
        # create school
        &AD_school_create({ldap=>$ldap,
                           root_dse=>$root_dse,
                           root_dns=>$root_dns,
                           school=>$devices_file{'host_group'}{$host_group}{'sophomorixSchoolname'},
                           smb_admin_pass=>$smb_admin_pass,
                           sophomorix_config=>\%sophomorix_config,
                           sophomorix_result=>\%sophomorix_result,
                         });
        &AD_group_create({ldap=>$ldap,
                          root_dse=>$root_dse,
                          root_dns=>$root_dns,
                          group=>$host_group,
                          group_basename=>$devices_file{'host_group'}{$host_group}{'HOST_BASENAME'},
                          description=>$description,
                          school=>$devices_file{'host_group'}{$host_group}{'sophomorixSchoolname'},
                          status=>"P",
                          type=>$devices_file{'host_group'}{$host_group}{'sophomorixType'},
     	   		  sub_ou=>$sub_ou,
                          smb_admin_pass=>$smb_admin_pass,
                          sophomorix_config=>\%sophomorix_config,
                          sophomorix_result=>\%sophomorix_result,
                         });
    }

    ############################################################
    # Updating rooms
    print "\n";
    my $max_room_count_update=$#updating_rooms+1;
    &print_title("Updating $max_room_count_update rooms");
    my $room_count_update=1;
    foreach my $room (@updating_rooms){
         &AD_group_update({ldap=>$ldap,
                           root_dse=>$root_dse,
                           dn=>$ref_AD_device->{'room'}{$room}{'DN'},
                           sophomorixRoomIPs=>\@{ $devices_file{'room'}{$room}{'IPv4LIST'} },
                           sophomorixRoomMACs=>\@{ $devices_file{'room'}{$room}{'MACLIST'} },
                           sophomorixRoomComputers=>\@{ $devices_file{'room'}{$room}{'HOSTLIST'} },
                           sophomorix_config=>\%sophomorix_config,
                         });
        $room_count_update++;
    }

    ############################################################
    # Kill dnsNodes
    print "\n";
    my $max_dns_node_count_kill=$#killing_dnsnodes+1;
    &print_title("Killing $max_dns_node_count_kill dnsNodes:");
    my $dns_node_count=1;
    foreach my $dns_node (@killing_dnsnodes){
        &AD_dns_kill({ldap=>$ldap, 
                      root_dse=>$root_dse,
                      root_dns=>$root_dns,
                      dns_zone=>$ref_AD_device->{'dnsNode'}{$sophomorix_config{'INI'}{'DNS'}{'DNSNODE_KEY'}}{$dns_node}{'dnsZone'},
                      dns_node=>$dns_node,
                      dns_ipv4=>$ref_AD_device->{'dnsNode'}{$sophomorix_config{'INI'}{'DNS'}{'DNSNODE_KEY'}}{$dns_node}{'IPv4'},
                      smb_admin_pass=>$smb_admin_pass,
                      sophomorix_config=>\%sophomorix_config,
                    });
        $dns_node_count++;
    }

    ############################################################
    # Kill dnsZones
    print "\n";
    my $max_dns_zone_count_kill=$#killing_dnszones+1;
    &print_title("Killing $max_dns_zone_count_kill dnsZones:");
    my $dns_zone_count=1;
    foreach my $dns_zone (@killing_dnszones){
	print "        $dns_zone\n";
        &AD_dns_zonekill({ldap=>$ldap, 
                          root_dse=>$root_dse,
                          dns_server=>"localhost",
                          dns_zone=>$dns_zone,
                          smb_admin_pass=>$smb_admin_pass,
                          sophomorix_config=>\%sophomorix_config,
                        });
        $dns_zone_count++;
    }

    ############################################################
    # Add dnsZones
    print "\n";
    my $max_dns_zone_count_add=$#adding_dnszones+1;
    &print_title("Adding $max_dns_zone_count_add dnsZones:");
    $dns_zone_count=1;
    foreach my $dns_zone (@adding_dnszones){
        &AD_dns_zonecreate({ldap=>$ldap, 
                            root_dse=>$root_dse,
                            dns_server=>$root_dns,
                            dns_zone=>$dns_zone,
                            smb_admin_pass=>$smb_admin_pass,
                            sophomorix_config=>\%sophomorix_config,
                          });
        $dns_zone_count++;
    }

    ############################################################
    # Add dnsNodes
    print "\n";
    my $max_dns_node_count_add=$#adding_dnsnodes+1;
    &print_title("Adding $max_dns_node_count_add dnsNodes:");
    $dns_node_count=1;
    foreach my $dns_node (@adding_dnsnodes){
        my $filename=$devices_file{'device'}{$dns_node}{'FILENAME'};
        my $school=$devices_file{'device'}{$dns_node}{'SCHOOL'};
        my $role=$devices_file{'device'}{$dns_node}{'sophomorixRole'};
        my $comment=$devices_file{'device'}{$dns_node}{'sophomorixComment'};
        &AD_dns_nodecreate_update({ldap=>$ldap, 
                                   root_dse=>$root_dse,
                                   root_dns=>$root_dns,
                                   create=>"TRUE",
                                   filename=>$filename,
                                   school=>$school,
                                   role=>$role,
                                   comment=>$comment,
                                   dns_node=>$dns_node,
                                   dns_ipv4=>$devices_file{'device'}{$dns_node}{'IPv4'},
                                   smb_admin_pass=>$smb_admin_pass,
                                   sophomorix_config=>\%sophomorix_config,
                                 });
        $dns_node_count++;
    }

    ############################################################
    # Updating dnsNodes
    print "\n";
    my $max_dns_node_count_update=$#updating_dnsnodes+1;
    &print_title("Updating $max_dns_node_count_update dnsNodes:");
    $dns_node_count=1;
    foreach my $dns_node (@updating_dnsnodes){
        my $filename=$devices_file{'device'}{$dns_node}{'FILENAME'};
        my $school=$devices_file{'device'}{$dns_node}{'SCHOOL'};
        my $role=$devices_file{'device'}{$dns_node}{'sophomorixRole'};
        my $comment=$devices_file{'device'}{$dns_node}{'sophomorixComment'};
        &AD_dns_nodecreate_update({ldap=>$ldap, 
                                   root_dse=>$root_dse,
                                   root_dns=>$root_dns,
                                   create=>"FALSE",
                                   filename=>$filename,
                                   school=>$school,
                                   role=>$role,
                                   comment=>$comment,
                                   dns_node=>$dns_node,
                                   dns_ipv4=>$devices_file{'device'}{$dns_node}{'IPv4'},
                                   smb_admin_pass=>$smb_admin_pass,
                                   sophomorix_config=>\%sophomorix_config,
                                 });
       $dns_node_count++;
    }

    ############################################################
    # Kill devicegroups
    print "\n";
    my $max_dgr_count_kill=$#killing_devicegroups+1;
    &print_title("Killing $max_dgr_count_kill devicegroups:");
    my $kill_dgr_count=1;
    foreach my $dgr (@killing_devicegroups){
        print "   * Killing devicegroup $dgr ...\n";
        &AD_group_kill({ldap=>$ldap,
                        root_dse=>$root_dse,
                        root_dns=>$root_dns,
                        group=>$dgr,
                        type=>$dgr_type,
                        group_count=>$kill_dgr_count,
                        sophomorix_config=>\%sophomorix_config,
                  });
    }

    ############################################################
    # Add devicegroups
    print "\n";
    my $max_dgr_count_add=$#adding_devicegroups+1;
    &print_title("Adding $max_dgr_count_add devicegroups:");
    my $dgr_count=0;
    foreach my $dgr (@adding_devicegroups){
        $dgr_count++;
	my $school=$devices_file{'devicegroup'}{$dgr}{'SCHOOL'};

        # create school
        &AD_school_create({ldap=>$ldap,
                           root_dse=>$root_dse,
                           root_dns=>$root_dns,
                           school=>$school,
                           smb_admin_pass=>$smb_admin_pass,
                           sophomorix_config=>\%sophomorix_config,
                           sophomorix_result=>\%sophomorix_result,
                         });
        &AD_group_create({ldap=>$ldap,
                          root_dse=>$root_dse,
                          root_dns=>$root_dns,
                          school=>$school,
                          group=>$dgr,
                          group_basename=>$dgr,
                          description=>$dgr,
                          type=>$dgr_type,
                          status=>"P",
                          sub_ou=>$sophomorix_config{'INI'}{'OU'}{'AD_devicegroup_ou'},
                          smb_admin_pass=>$smb_admin_pass,
                          sophomorix_config=>\%sophomorix_config,
                          sophomorix_result=>\%sophomorix_result,
                         });
    }

    ############################################################
    # Add devicegroup memberships
    print "\n";
    &print_title("Devicegroup members to be added:");
    my $dgr_count_addmember=0;
    foreach my $member (keys %addmembers_devicegroups){
        $dgr_count_addmember++;
        print "ADD: $member to $addmembers_devicegroups{$member}\n";
        &AD_group_addmember({ldap => $ldap, 
                             root_dse => $root_dse, 
                             group => $addmembers_devicegroups{$member},
                             addmember => $member,
                           });   
    }

    ############################################################
    # Kill devicegroup memberships
    print "\n";
    &print_title("Devicegroup members to be killed:");
    my $dgr_count_killmember=0;
    foreach my $member (keys %killmembers_devicegroups){
        $dgr_count_killmember++;
        print "KILL: $member from $killmembers_devicegroups{$member}\n";
        &AD_group_removemember({ldap => $ldap, 
                                root_dse => $root_dse, 
                                group => $killmembers_devicegroups{$member},
                                removemember => $member,
                              });   
    }


    &update_printer_gpos(\%devices_file,\%sophomorix_config,\%sophomorix_result,$json);

    &AD_unbind_admin($ldap);
    &log_script_end(\@arguments,\%sophomorix_result,\%sophomorix_config,$json);
} else {
    print "\nNothing changed. Use --sync to synchronise devices\n\n";
    &log_script_end(\@arguments,\%sophomorix_result,\%sophomorix_config,$json);
}



############################################################
# subs
############################################################
sub analyze_device_line {
    my ($line,$device_file,$filename,$school,$device_line_count) = @_;
    if (/^#/ or /^\s*$/){
        return 0;
    } else {
        chomp($line);
        my ($room,
            $host,
            $dgr,
            $mac,
            $ip,
            $ms_office_key,
            $ms_windows_key,
            $unused,
            $sophomorix_role,
            $unused_2,
            $pxe,
            $option,
            $field_13,
            $field_14,
            $sophomorix_comment)=split(/;/,$line);

        $room=&check_room($room,$line,$filename,$device_file,$device_line_count);
        $host=&check_host($host,$line,$filename,$device_file,$device_line_count);
        $dgr=&check_dgr($dgr,$line,$filename,$device_file,$device_line_count);
        $mac=&check_mac($mac,$line,$filename,$device_file,$device_line_count);
        $ip=&check_ip($ip,$line);
        $ms_office_key=&check_ms_software_key($ms_office_key,"MS-OFFICE-KEY",$host,$line,$filename,$device_file,$device_line_count);
        $ms_windows_key=&check_ms_software_key($ms_windows_key,"MS-WINDOWS-KEY",$host,$line,$filename,$device_file,$device_line_count);

        my $account_flag;
        my $host_group;
        my $host_group_type;
        ($sophomorix_role,
         $account_flag,
         $host_group,
         $host_group_type)=&check_sophomorix_role($sophomorix_role,$line,$filename,$device_file,$device_line_count);

        $pxe=&check_pxe($pxe,$line,$filename,$device_file,$device_line_count);
        $option=&check_option($option,$line,$filename,$device_file,$device_line_count);

        # use tokened names 
        my $dgr_token=&AD_get_name_tokened($dgr,$school,$dgr_type);
        my $dnsnode_name=&AD_get_name_tokened($host,$school,"dnsnode");
        my $room_token=&AD_get_name_tokened($room,$school,"roomws");
        my $ws_token=&AD_get_name_tokened($host,$school,"computer");
        my $ws_token_account=$ws_token."\$";
        if($Conf::log_level>=3){
            print "\n$line\n";
            print "DNSName:     $host  -->  $dnsnode_name\n";
            print "Room:        $room  -->  $room_token\n";
            print "Computer:    $host  -->  $ws_token\n";
            print "Computer\$:  $host  -->  $ws_token_account\n";
            print "\n";
	}

        # some things are nor allowed to be double:
        # name
        if (exists $devices_file{'device'}{$dnsnode_name}){
            print "Device Name (Field2) is double: $dnsnode_name\n";
            &result_sophomorix_add(\%sophomorix_result,
                                   "ERROR",
                                   -1,
                                   \@arguments,
                                   "$filename (Line: $device_line_count): Device Name (Field2) is double: $dnsnode_name");
            $error_in_files++;
        }

        # save data into hash for reuse
        push @domcomputers_file, $dnsnode_name;
        $devices_file{'device'}{$dnsnode_name}{'ROOM_BASENAME'}=$room;            # 01
        $devices_file{'device'}{$dnsnode_name}{'DGR_FROM_FILE'}=$dgr;             # 03
        $devices_file{'device'}{$dnsnode_name}{'DGR'}=$dgr_token;                 # 03 modified

        $devices_file{$dgr_type}{$dgr_token}{'member'}{$dnsnode_name}=$ws_token_account;
        $devices_file{$dgr_type}{$dgr_token}{'SCHOOL'}=$school;

        $devices_file{'device'}{$dnsnode_name}{'MAC'}=$mac;                       # 04
        $devices_file{'device'}{$dnsnode_name}{'IPv4'}=$ip;                       # 05
        $devices_file{'device'}{$dnsnode_name}{'MS_OFFICE_KEY'}=$ms_office_key;   # 06
        $devices_file{'device'}{$dnsnode_name}{'MS_WINDOWS_KEY'}=$ms_windows_key; # 07
        $devices_file{'device'}{$dnsnode_name}{'ROOM'}=$room_token;
        $devices_file{'device'}{$dnsnode_name}{'ACCOUNT'}=$account_flag;
        $devices_file{'device'}{$dnsnode_name}{'COMPUTER'}=$ws_token;
        $devices_file{'device'}{$dnsnode_name}{'COMPUTER_DOLLAR'}=$ws_token_account;
        $devices_file{'device'}{$dnsnode_name}{'SCHOOL'}=$school;
        $devices_file{'device'}{$dnsnode_name}{'PATH_ABS'}=$device_file;
        $devices_file{'device'}{$dnsnode_name}{'FILENAME'}=$filename;

        if (not defined $option){
            $option="";
        }       
        if (not defined $field_13){
            $field_13="";
        }       
        if (not defined $field_14){
            $field_14="";
        }
 
        # sophomorixComment is a must field and doesn't have to be empty
        if (not defined $sophomorix_comment){
            $sophomorix_comment="---";
        } elsif ($sophomorix_comment eq ""){
            $sophomorix_comment="---";
        }     

        $devices_file{'device'}{$dnsnode_name}{'sophomorixDnsNodename'}=$dnsnode_name;
        $devices_file{'device'}{$dnsnode_name}{'sophomorixRole'}=$sophomorix_role;       # 09
        $devices_file{'device'}{$dnsnode_name}{'OPTION'}=$option;                        # 12
        $devices_file{'device'}{$dnsnode_name}{'FIELD_13'}=$field_13;
        $devices_file{'device'}{$dnsnode_name}{'FIELD_14'}=$field_14;
        $devices_file{'device'}{$dnsnode_name}{'sophomorixComment'}=$sophomorix_comment; # 15

        if ($ip ne "DHCP"){
            # dnsZone
            my @octets=split(/\./,$ip);
            my $dns_zone=$octets[2].".".$octets[1].".".$octets[0].".in-addr.arpa";
            $devices_file{'dnsZone'}{$dns_zone}{'FILENAME'}=$filename;
            $devices_file{'dnsZone'}{$dns_zone}{'SCHOOL'}=$school;
            # dnsNode
            $devices_file{'dnsNode'}{$dnsnode_name}=$ip;
        }


        # decide if host group is needed 
        if ($host_group eq "TRUE"){
            $devices_file{'host_group'}{$dnsnode_name}{'sophomorixType'}=$host_group_type;
            $devices_file{'host_group'}{$dnsnode_name}{'sophomorixSchoolname'}=$school;
            $devices_file{'host_group'}{$dnsnode_name}{'HOST_BASENAME'}=$host;
            $devices_file{'host_group'}{$dnsnode_name}{'COMPUTER_DOLLAR'}=$ws_token_account;
            $devices_file{'host_group'}{$dnsnode_name}{'ROOM'}=$room_token;
            $devices_file{'host_group'}{$dnsnode_name}{'ROOM_BASENAME'}=$room;
            $devices_file{'host_group'}{$dnsnode_name}{'SUB_OU'}="OU=".$host_group_type."-groups";
        }
        $devices_file{'room'}{$room_token}{'HOSTS'}{$dnsnode_name}="seen";
        $devices_file{'room'}{$room_token}{'MAC'}{$mac}="seen";
        $devices_file{'room'}{$room_token}{'IPv4'}{$ip}="seen";

        # lists at room
        push @{ $devices_file{'room'}{$room_token}{'HOSTLIST'} }, $dnsnode_name;
        push @{ $devices_file{'room'}{$room_token}{'MACLIST'} }, $mac;
        push @{ $devices_file{'room'}{$room_token}{'IPv4LIST'} }, $ip;

        # lists by school
        push @{ $devices_file{'LISTS'}{'devices_by_school'}{$school} }, $dnsnode_name;

        if ($account_flag eq "TRUE"){
            $devices_file{'computer'}{$ws_token_account}{'SCHOOL'}=$school;
            $devices_file{'computer'}{$ws_token_account}{'ROOM'}=$room_token;
            $devices_file{'computer'}{$ws_token_account}{'ROOM_BASENAME'}=$room;
            $devices_file{'computer'}{$ws_token_account}{'COMPUTER'}=$ws_token;
            $devices_file{'computer'}{$ws_token_account}{'COMPUTER_DOLLAR'}=$ws_token_account;
            $devices_file{'computer'}{$ws_token_account}{'FILENAME'}=$filename;
            $devices_file{'computer'}{$ws_token_account}{'sophomorixRole'}=$sophomorix_role;
            $devices_file{'computer'}{$ws_token_account}{'sophomorixComment'}=$sophomorix_comment;
            $devices_file{'computer'}{$ws_token_account}{'sophomorixComputerIP'}=$ip;
            $devices_file{'computer'}{$ws_token_account}{'sophomorixComputerMAC'}=$mac;
            $devices_file{'computer'}{$ws_token_account}{'DGR_FROM_FILE'}=$dgr;
            $devices_file{'computer'}{$ws_token_account}{'sophomorixComputerRoom'}=$room_token;
        }
    }
}



sub check_room {
    my ($room,$line,$filename,$device_file,$device_line_count) = @_;
    if($Conf::log_level>=3){
        print "   ROOM:    $room\n";
    }
    # allow a-z0-9-_ lowercase
    if ( $room=~/[^A-Za-z0-9\-]/ ) {
        $error_in_files++;
        my $message="$room (Field:1) contains invalid characters in LINE:$device_line_count of $device_file ";
        &result_sophomorix_add(\%sophomorix_result,"ERROR",-1,"",$message);
    } else {

    }
    return $room;
}



sub check_host {
    my ($host,$line,$filename,$device_file,$device_line_count) = @_;
    if($Conf::log_level>=3){
        print "   HOST:    $host\n";
    }
    # allow a-z0-9-  , convert to lowercase later
    if ( $host=~/[^A-Za-z0-9\-]/ ) {
        print "\n";
        print "LINE: $line\n";
        print "  ERROR: $host (Field2) contains invalid characters in $filename ($device_file)\n\n";
        exit 88;
    } else {
        $host=~tr/A-Z/a-z/; # in Kleinbuchstaben umwandeln
        # correct
        $host_seen{$host}="seen";
    }
    return $host;
}



sub check_dgr {
    my ($dgr,$line,$filename,$device_file,$device_line_count) = @_;
    if($Conf::log_level>=3){
        print "   DGR:     $dgr\n";
    }
    # allow A-Za-z0-9   
    if ( $dgr=~/[^A-Za-z0-9\-_]/ ) {
        print "\n";
        print "LINE: $line\n";
        print "ERROR: $dgr contains invalid characters in $filename\n\n";
        exit 88;
    } else {

    }
    return $dgr;
}



sub check_mac {
    my ($old_mac,$line,$filename,$device_file,$device_line_count) = @_;
    if($Conf::log_level>=3){
       print "   MAC:     $old_mac\n";
    }
    my $mac = Net::MAC->new('mac' => $old_mac , base => 16); 
    my $new_mac = $mac->convert(
          'base'      => 16,   # convert to base 16, if necessary
          'bit_group' => 8,    # 16 bit grouping
          'delimiter' => ':',  # dot-delimited
          'die'       => 1     # die if conversion fails
	);
    # should die when mac is wrong

    # convert to uppercase
    $new_mac=~tr/a-z/A-Z/; # in Grossbuchstaben umwandeln

    # correct
    if (exists $mac_seen{$new_mac}){
        print "\n";
        print "LINE: $line\n";
        print "  ERROR: MAC >$new_mac< is double in $filename!\n\n";
        exit 88;
    } else {
        $mac_seen{$new_mac}="seen";
    }
    if($Conf::log_level>=3){
        print "      OLD: $old_mac\n";
        print "      NEW: $new_mac\n";
    }
    return $new_mac;
}



sub check_ip {
    my ($ip,$line,$filename,$device_file,$device_line_count) = @_;
    if($Conf::log_level>=3){
        print "   IP:      $ip";
    }
    if ($ip eq "DHCP"){
        return $ip;
    }
    # check for 3 .
    my $dots_in_string=$ip=~tr/\.//;
    if($Conf::log_level>=3){
        print " ($dots_in_string dots)\n";
    }
    if ($dots_in_string!=3){
        print "\n";
        print "LINE: $line\n";
        print "  ERROR: >$ip< does not contain 3 dots $filename\n\n";
        exit 88;        
    }
    
    # check for correct octets    
    my @octets = split(/\./,$ip);
    foreach my $octet (@octets){
        if($Conf::log_level>=3){
            print "      Octet:    $octet\n";
        }
        if ( int($octet) < 0 or int($octet) > 255) {
            print "\nERROR: ->$ip<- is invalid Option in ip octet $filename\n\n";
            exit 88;
        } else {

        }
    }

    # check for double ip
    if (exists $ip_seen{$ip}){
        print "\nERROR: IP $ip is double!\n\n";
        exit 88;
    } else {
        $ip_seen{$ip}="seen";
    }
    return $ip;
}



sub check_ms_software_key {
    my ($ms_software_key,$name,$host,$line,$filename,$device_file,$device_line_count) = @_;
    if (exists $ms_key_ok{$ms_software_key}){
        # skip tests, when tagged as 'no key'
        return $ms_software_key;
    }
    if($Conf::log_level>=3){
        print "   $name: $ms_software_key";
    }

    my $minus_in_string=$ms_software_key=~tr/-//;
    if($Conf::log_level>=3){
        print " ($minus_in_string minus signs)\n";
    }
    if ($minus_in_string!=4){
        if($Conf::log_level>=2){
            print "\n";
            print "LINE: $line\n";
            print "  WARNING $host: <$ms_software_key> $name: Not containing 4 minus signs\n\n";
        }
    }

    my @parts = split(/-/,$ms_software_key);
    foreach my $part (@parts){
        if($Conf::log_level>=3){
            print "      Part: <$part> (Length ".length($part).")\n";
        }
        if (length($part)!=5 ) {
            if($Conf::log_level>=2){
                print "\n";
                print "LINE: $line\n";
                print "  WARNING $host: <$ms_software_key> Not a $name:\n";
                print "   * Part: $part (Length ".length($part)." : Not 5 chacters long)\n\n";
            }
        } 
    }
    return $ms_software_key;
}



sub check_sophomorix_role {
    my ($role,$line,$filename,$device_file,$device_line_count) = @_;
    if ($role eq ""){
        $role=$sophomorix_config{'INI'}{'GLOBAL'}{'COMPUTERROLE_DEFAULT'};
    } elsif (not exists $sophomorix_config{'LOOKUP'}{'ROLES_DEVICE'}{$role}) {
        print "\n";
        print "LINE: $line\n";
        print "  ERROR: >$role< is not a valid role in $filename\n";
        print "     Valid sophomorixRole for devices are:\n";
        foreach my $keyname (keys %{$sophomorix_config{'LOOKUP'}{'ROLES_DEVICE'}} ) {
            print "        $keyname\n";
        }
        print "\n";
        exit 88;
    }

    my $key="computerrole.".$role;

    # calculate account flag (mandatory)
    my $account_flag;
    if (defined $sophomorix_config{'INI'}{$key}{'COMPUTER_ACCOUNT'}){
        $account_flag=$sophomorix_config{'INI'}{$key}{'COMPUTER_ACCOUNT'};
    } else {
        print "\n";
        print "ERROR: Could not determine account flag by role $role";
        print "\n";
        exit 88;
    }
    
    # host_group (optional)
    my $host_group="---";
    if (defined $sophomorix_config{'INI'}{$key}{'HOST_GROUP'}){
        $host_group=$sophomorix_config{'INI'}{$key}{'HOST_GROUP'};
    }
    my $host_group_type="---";
    if (defined $sophomorix_config{'INI'}{$key}{'HOST_GROUP_TYPE'}){
	$host_group_type=$sophomorix_config{'INI'}{$key}{'HOST_GROUP_TYPE'};
    }

    return ($role,$account_flag,$host_group,$host_group_type);
}



sub check_pxe {
    my ($pxe,$line,$filename,$device_file,$device_line_count) = @_;
    if($Conf::log_level>=3){
        print "   PXE:     $pxe\n";
    }
    if ( $pxe=~/[0-9]/ ) {
        # this is correct
        return $pxe;
    }
    if ( not exists $pxe{$pxe} ) {
        print "\n";
        print "LINE: $line\n";
        print "  ERROR: >$pxe< is invalid Option in pxe field in $filename\n\n";
        exit 88;
    }
    return $pxe;
}



sub check_option {
    my ($option,$line,$filename,$device_file,$device_line_count) = @_;
    if (not defined $option){
        $option="";
        # ??? warning ???
    }
    if($Conf::log_level>=3){
        print "\n";
        print "LINE: $line\n";
        print "  OPTION: >$option<\n";
    }
    return $option;
}



sub push_kill_computer_room {
    my ($kill) = @_;
    push @killing_computer_rooms, $kill;
}



sub push_add_computer {
    my ($add) = @_;
    push @adding_computers, $add;
}



sub push_update_computer {
    my ($update) = @_;
    push @updating_computers, $update;
}



sub push_kill_computer {
    my ($kill) = @_;
    push @killing_computers, $kill;
}



sub push_add_host_group {
    my ($add) = @_;
    push @adding_host_groups, $add;
}



sub push_kill_host_group {
    my ($kill) = @_;
    push @killing_host_groups, $kill;
}



sub push_update_room {
    my ($update) = @_;
    push @updating_rooms, $update;
}



sub push_add_dnsnode {
    my ($add) = @_;
    push @adding_dnsnodes, $add;
}



sub push_kill_dnsnode {
    my ($kill) = @_;
    push @killing_dnsnodes, $kill;
}



sub push_update_dnsnode {
    my ($update) = @_;
    push @updating_dnsnodes, $update;
}



sub push_add_dnszone {
    my ($add) = @_;
    push @adding_dnszones, $add;
}



sub push_kill_dnszone {
    my ($kill) = @_;
    push @killing_dnszones, $kill;
}



sub push_add_devicegroup {
    my ($add) = @_;
    push @adding_devicegroups, $add;
    $adding_devicegroups{$add}="add";
}



sub push_kill_devicegroup {
    my ($kill) = @_;
    push @killing_devicegroups, $kill;
    $killing_devicegroups{$kill}="kill";
}



sub update_printer_gpos {
    my ($ref_devices_file,$ref_sophomorix_config,$ref_result,$json)=@_;
    print "\n";
    &print_title("Creating Printers.xml for GPO in $ref_sophomorix_config->{'INI'}{'PATHS'}{'GPO_PRINTERS_TARGET_DIR'}");
    my $ref_gpo=&AD_gpo_listall({json=>-1,
                                 sophomorix_config=>$ref_sophomorix_config,
                                 sophomorix_result=>$ref_result,
                               });

    foreach my $school (@{ $ref_sophomorix_config->{'LISTS'}{'SCHOOLS'} }){
        my $template=$ref_sophomorix_config->{'INI'}{'PATHS'}{'GPO_PRINTERS_TEMPLATE'};
        my $target_dir=$ref_sophomorix_config->{'INI'}{'PATHS'}{'GPO_PRINTERS_TARGET_DIR'}."/".$school;
        my $target=$target_dir."/Printers.xml";
        # create output dir
        system("rm -rf $target_dir");
        system("mkdir -p $target_dir");

        # write prefix of Printers.xml
        system("cat $ref_sophomorix_config->{'INI'}{'PATHS'}{'GPO_PRINTERS_TEMPLATE_PRE'} >> $target");

        # write main part of Printers.xml
        my $school_printer_count=0;
        foreach my $device (@{ $ref_devices_file->{'LISTS'}{'devices_by_school'}{$school} }){
            if ($ref_devices_file->{'device'}{$device}{'sophomorixRole'} eq "printer"){
                # only work on printers
                $school_printer_count++;
                my $workgroup=$ref_sophomorix_config->{'samba'}{'smb.conf'}{'global'}{'workgroup'};
                my $printer_uppercase=$ref_devices_file->{'device'}{$device}{'COMPUTER'};
                my $printer_lowercase=$ref_devices_file->{'device'}{$device}{'sophomorixDnsNodename'};
                my $printer_sid=&name_to_sid($printer_lowercase);

                # printserver can be overridden in school.conf
                my $printserver=$ref_sophomorix_config->{'SCHOOLS'}{$school}{'PRINTSERVER'};
                if ($printserver eq ""){
                    $printserver=$ref_sophomorix_config->{'samba'}{'smb.conf'}{'global'}{'netbios name'};
                } else {
		    # make sure / are replaced in sed command correctly
                    #$printserver=~s/\//\\\//g;
		}
                my $replace = "-e \"s/\@\@LINUXMUSTER\@\@/$workgroup/g\" ".
                              "-e \"s/\@\@PRINTSERVER\@\@/$printserver/g\" ".
                              "-e \"s/\@\@PRINTER_LOWERCASE\@\@/$printer_lowercase/g\" ".
                              "-e \"s/\@\@PRINTER_SID\@\@/$printer_sid/g\" ".
                              "-e \"s/\@\@PRINTER\@\@/$printer_uppercase/g\" ";
                my $sed_command="cat ".$template." | sed ".$replace." >> ".$target;
                if($Conf::log_level>=3){
                    print "$sed_command\n";
	        }
                system($sed_command);
	    }
        }

        # write postfix of Printers.xml
        system("cat $ref_sophomorix_config->{'INI'}{'PATHS'}{'GPO_PRINTERS_TEMPLATE_POST'} >> $target");

        # copy the file if necessary
        printf  "School %-14s:%4s printers found\n",$school,$school_printer_count;
        my $lookup_key="sophomorix:school:".$school;
	my $gpo_display_name;
        if (exists $ref_gpo->{'LOOKUP'}{'by_display_name'}{$lookup_key}){
  	    my $gpo_display_name=$ref_gpo->{'LOOKUP'}{'by_display_name'}{$lookup_key};
            my $target_gpo_file=$ref_sophomorix_config->{'samba'}{'smb.conf'}{'sysvol'}{'path'}.
                                "/".$root_dns.
                                "/Policies/".
                                $gpo_display_name.
                                "/User/Preferences/Printers/Printers.xml";
            if ($school_printer_count>0){
  	        print "   copy: $target\n";
  	        print "    ---> $target_gpo_file\n";
                system("cp  $target $target_gpo_file");
            } elsif ($school_printer_count==0) {
  	        print "   rm $target_gpo_file\n";
                system("rm -f $target_gpo_file");
            }
        } else {
            # no GPO dir
            $gpo_display_name="---";
            if ($school_printer_count>0){
                print "   WARNING: Cannot copy Printers.xml to GPO: Printers exist, but no GPO directory\n";
            } elsif ($school_printer_count==0) {
                # do not warn, Printers.xml does not exist, thats fine
            }
        }
    }
    print "\n";
}



sub name_to_sid {
    my ($name)=@_;
    my $string=`wbinfo -n $name`;
    chomp($string);
    my ($sid,@rest)=split(/\s+/,$string);
    return $sid;
}



sub print_devices_latex {
    my ($ref_device)=@_;
    print "--print not working at present\n";
# # ===========================================================================
# # --print     (does not modify system)
# # ===========================================================================
# if ($print==1){
#     if (not -e ${host_workstation}){
#         print "\n$host_workstation does not exist\n\n";
#         exit 1;
#     }

#     my $admins="";
#     if (defined $Conf::admins_print){
#         if ($Conf::admins_print ne ""){
# 	    $admins=$Conf::admins_print;
#         }
#     }

#     my $out_base="/etc/linuxmuster/workstations_formatted";
#     my $out_dir="/etc/linuxmuster";
#     my $out_file="$out_base".".tex";
#     my $out_file_dvi="$out_base".".dvi";
#     my @line=();
#     my $host=0; # counter for the hosts
#     # draw a horizontal line in the table after this many lines of text
#     my $hor_lines=5; 
#     my $line_count=1;

#     print "Writing to $out_file";
#     open(TEX, ">$out_file");

#     print TEX '\documentclass[a4paper, twoside]{article}',"\n",
#           '\usepackage{longtable}',"\n",
#           '\usepackage{ngerman}',"\n",
#           '\usepackage{layout}',"\n",
#           '\usepackage{fancyhdr}',"\n",
#           '\usepackage{lastpage}',"\n",
#           '\setlength{\oddsidemargin}{-5mm}',"\n",
#           '\setlength{\evensidemargin}{-16.8mm}',"\n",
#           '\setlength{\textwidth}{180mm}',"\n",
#           '\setlength{\textheight}{255mm}',"\n",
#           '\setlength{\topmargin}{-20mm}',"\n",
#           '\pagestyle{fancy}',"\n",
#           "\\lhead{$Conf::schul_name}","\n",
#           "\\rhead{Seite \\thepage/\\pageref{LastPage}}","\n",
#           "\\cfoot{$admins}","\n",
#           '\pagestyle{fancy}',"\n",
#           '\renewcommand{\baselinestretch}{1.2}',"\n\n",

#           '\begin{document}',"\n\n",
#           # show layout of page
#           # '\layout',"\n\n",
#           '\begin{longtable}{|r|c|c|c|c|c|} ',"\n",
#           '   \caption*{\large \bfseries Rechner und ',
#           '             Drucker ',
#           "($Conf::schul_name)",
#           '}\\\\ \\hline',"\n",
#           '   \bfseries Nr. & \bfseries DNS-Name & \bfseries Raum & ',
#           '      \bfseries HW-Klasse & \bfseries IP-Adresse & ',
#           '      \bfseries MAC-Adresse \\\\ \hline \hline',"\n",
#           '   \endhead',"\n",
#           '      \hline \multicolumn{6}{r}{Fortsetzung auf der n\"{a}chsten Seite ...}',"\n",
#           '   \endfoot',"\n",
#           '      \hline ',"\n",
#           '   \endlastfoot',"\n";

#     open(IN, "<${host_workstation}");
#     while(<IN>){
#         chomp();
#         my $line=$_;
#         if ($line eq "") {
# 	    next;
#         } elsif(/^\#\#/){
# 	    print "Comment $line \n";
#         } elsif (/^\#/){
# 	    print "Header $line \n";
#             s/^\#//g;
#             if ($line eq "") {
# 	        next;
#             }
#             $line=&latexize_string($line);
#             print TEX "\\hline \\hline  ",
#                  "\\multicolumn{6}{|l|}{\\rule{0mm}{4mm}\\bfseries $line}",
#                  " \\\\ \\hline ","\n";
#         } else {
#             @line = split(/;/);
#             $host++;
#             $host=&latexize_string($host);
#             $line[1]=&latexize_string($line[1]);
#             $line[2]=&latexize_string($line[2]);
#             $line[3]=&latexize_string($line[3]);
#             $line[4]=&latexize_string($line[4]);
#             $line="$host & $line[1] & $line[0] & $line[2]".
#                   " & $line[4] & \\texttt{$line[3]} \\\\ ";
#             print TEX $line; 
#             # Horizontal lines
#              if ($line_count==$hor_lines){
#                  print TEX ' \hline',"\n";
#                  $line_count=0;
#              } else {
#                  print TEX "\n";
#              }
#              $line_count++;
#         }
#     }
#     close(IN);

#     print TEX '\end{longtable}',"\n";
#     print TEX "\\textbf{$host Rechner/Drucker sind im p\"{a}dagogischen ",
#               "Netz ($Conf::schul_name).} \n \n";
#     print TEX "Ausdruck: \\today .",
#               "\n \n",
#               "\\textbf{Die Netzwerkadministratoren:}",
#               "\n",
#               "$admins","\n";
#     print TEX "\n",'\end{document}',"\n";
#     close(TEX);

#     # latex
#     system("cd $out_dir; latex $out_file");
#     system("cd $out_dir; latex $out_file");
#     system("cd $out_dir; dvips $out_file_dvi");
#     exit;
# }

}

