larmina
Goto Top

Windows Domain Controller - Benutzergruppen mit PHP hinzufügen und löschen mit Fokus auf Druckerverteilung per GPO

Problemstellung
Drucker werden per GPO verteilt, aber man muss die Benutzer aufwändig in die entsprechenden Gruppen einsortieren, was Zeit kostet und wenig Spaß macht.

Problemlösung
Den Benutzern ermöglichen sich selbst in die Druckergruppen einzusortieren.

Kurzbeschreibung der Lösung
Für jeden Drucker werden zwei Gruppen erstellt (P_Druckername und SP_Druckername, steht für printer und standard printer).
Diese werden aus dem Domain Controller ausgelesen und in ein HTML-Formular geschrieben zum auswählen.
Nachdem der Benutzer ausgewählt hat welche Drucker er benötigt schickt er das Formular ab und die entsprechenden Gruppen werden in den Domain Controller geschrieben.

Grundvorraussetzungen
- Einen Webserver
- PHP mit LDAP Klick
- Ein Windows Domain Controller
- Ein Printserver
- Für jeden Drucker zwei Gruppen


Alles fängt mit der Verbindungsherstellung zum Domain Controller an (Hier noch mit ein bisschen Fehlermeldung/Abfang):
// using ldap bind
$ldaprdn  = '';   // ldap rdn or dn  
$ldappass = '';  // associated password  

// connect to ldap server
$ldapconn = ldap_connect("do.mä.ne")  
or die("Could not connect to LDAP server.");  


// these parameters are required for accessing the AD
ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, 0);


if ($ldapconn)
{
    // binding to ldap server
    $ldapbind = ldap_bind($ldapconn, $ldaprdn, $ldappass);

    // verify binding
    if ($ldapbind) {
        echo "LDAP bind successful... <br>";  
    } else {
        echo "LDAP bind failed...";  
    }
}

Danach setzt du den Ordner in den deine Nutzer einsortiert sind als Basis für die Suche im LDAP Tree:
$basedn = "OU=Nutzer, OU=Unternutzergruppe, dc=do, dc=mä, dc=ne"; // Für jeden punkt der Domäne ist eigenes dc notwendig  

Danach setzen wir die Filtereinstellungen die bestimmen was uns angezeigt wird. Hier könnten wir auch nach PCs (objectClass=computer) in der Domäne und einigem anderen suchen. Wir brauchen in diesem Projekt nur objectClass=user und objectClass=group.

Das ist die Suche nach übereinstimmendem Anfang vom Benutzernamen, Rest Wildcard ( * ). Das brauche ich, weil manche bei uns auch zwei Benutzernamen haben an denen dann hinten einfach ein 2 angehängt wird. Kann man aber auch weglassen wenn es immer und auf jeden Fall eindeutig ist.
$filter = "(&(samaccountname=".$accountname."*)(ObjectClass=user))";  

Jetzt greifen wir das erste mal das LDAP an.
// Suche starten:
$search=ldap_search($ldapconn,$basedn,$filter);
// Ergebnis der Suche in der Variablen $info ablegen:
$info = ldap_get_entries($ldapconn, $search);

In $info haben wir jetzt ein riesiges mehrdimensionales Array.
In diesem stehen alle Daten zu allen Benutzern die wir gefunden haben. Wir können uns das ganze mit
<pre>
print_r($info);
</pre>
einigermaßen sauber anschauen aber es ist trotzdem noch verwirrend und erschlägt einen ein bisschen. Bei den meisten Dingen muss man auch erstmal googeln was es denn ist. Ich bin nach den Inhalten gegangen und habe dann geschaut wie der Knoten heißt den ich angreifen muss.


Um die Daten die wir bekommen haben auszulesen fragen wir auch noch die Anzahl der Einträge die wir zurückbekommen haben ab.
$anzahl = ldap_count_entries($ldapconn,$search); // Anzahl der Ergebnisse für Schleifendurchlauf abspeichern

Nun haben wir alles was wir brauchen um das erste mal das Array $info zu durchlaufen und uns das rauszuziehen was wir brauchen.
In diesem Fall sind es die Gruppen in denen der Benutzer ist.
Da wir aber nicht alle Gruppen benötigen sondern nur die die mit P_ oder SP_ beginnen in den Buffer schreiben wollen haben wir zwei Zähler.
$j = 0; // Zähler für Schleifendurchläufe
$i = 0; // Zähler für Weggespeichertes

Nun initialisieren wir das Bufferarray
$array_ldapentries_username = array();

Dieses Array ist mehrdimensional und hat als ersten Index eine laufende Nummer.
Dazu kommt dann der jeweilige Wert der weggeschrieben wird, so dass im Laufe der Schleife ein Array entsteht das so aussieht:
array[lfdnummer][CNPfad],
array[lfdnummer][Benutzername],
array[lfdnummer][mitgliedschaften][gruppenmitgliedschaften]

Die Schleife die unser Array füllt sieht so aus
while ($j<=$anzahl)
{
    $kk = 0; //Für die Mitgliedschaften (Extra Dimension) gibt es den Zähler $kk  
    foreach($info[$j]["memberof"] as $key)  
    {
        if($kk == 0)
        {
            $kk = 1;
            continue; // Wird benötigt um den Countwert der die Anzahl der gruppen angibt nicht im Array zu haben, dieser Wert kommt automatisch vom DC mit
        }
        $array_ldapentries_username[$i]["Mitgliedschaften"][$kk] = $key;  
        $kk = $kk + 1;
    }
    if($kk == 0)
    {
        $j = $j + 1;
        continue;
    }
  
   $array_ldapentries_username[$i]["CNPfad"] = utf8_decode($info[$j]["distinguishedname"]);   
   $array_ldapentries_username[$i]["Name"] = utf8_decode($info[$j]["name"]);  

    $j = $j + 1;
    $i = $i + 1;
}

Nun haben wir ein Array mit den Benutzernamen, den CNPfaden und den Mitgliedschaften jedes Users auf den unsere Suche zugetroffen hat.
Um nun die Gruppe des Standarddruckers zu finden und im HTMLFormular anzuzeigen laufen wir mit foreach das Array durch und speichern das Ergebnis in eine neue Variable. Um nicht nacher nochmal das gleiche für die normalen Druckergruppen tun zu müssen laufen diese in der gleichen Schleife mit in ein Array.
$i = 0; // Zähler für Nichtstandardgruppe
foreach($array_ldapentries_username as $key1)
{
    foreach($key1["Mitgliedschaften"] as $key)  
    {
        if(substr($key, 0, 6) == "CN=SP_")  
        {
            $key_temp = explode(",",$key);  
            $key_temp = substr($key_temp,3); // entfernt CN=
            $standardprinter = $key_temp;
        }
       
        if(substr($key, 0, 5) == "CN=P_")  
        {
            $key_temp = explode(",",$key);  
            $key_temp = substr($key_temp,3); // entfernt CN=
            $array_printergroups_now[$i] = $key_temp;
            $i = $i + 1;
        }
    }
}

Diese Variablen lassen wir nun fürs erste stehen und suchen uns aus dem DC eine komplette Liste der P_ und SP_Gruppen.
Diese werden benutzt um im HTMLFormular Checkboxen für die P_Gruppen und ein Dropdownfeld für die SP_Gruppen zu füllen.

Wir beginnen wieder mit dem setzen des Basedn und der Filter
$basedn = "CN=Groups, dc=do, dc=mä, dc=ne";  
$filter = "(cn=SP_*)"; // Suche nach Printergruppen  

Dann starten wir die Suche und speichern das Ergebnis und die Zahl der zurückgegebenen Werte ab
// Suche starten:
$search=ldap_search($ldapconn,$basedn,$filter);

// Ergebnis der Suche in der Variablen $info ablegen:
$info = ldap_get_entries($ldapconn, $search);
$anzahl = ldap_count_entries($ldapconn,$search); // Anzahl der Ergebnisse für Schleifendurchlauf abspeichern

Dann wieder die Zuweisung zu den Arrays
// Hinweis: utf8_decode dekodiert Umlaute richtig.
    $j = 0; // Zähler für Schleifendurchläufe
    $array_standard_printergroups = array();
    while ($j<=$anzahl)
    {
        if(strlen($info[$j]["description"]) < 2)  
        {
            $j = $j + 1;
            continue;
        }                                           
        $array_standard_printergroups[$j]["Name"]        = utf8_decode($info[$j]["cn"]);  
        $array_standard_printergroups[$j]["Gruppenname"] = utf8_decode($info[$j]["distinguishedname"]);  
        $array_standard_printergroups[$j]["Abteilung"]     = utf8_decode($info[$j]["description"]);  
       
        $j = $j + 1;
       
    }

Im Anschluss die P_Gruppen
// Suche nach Printergruppen
    $basedn = "CN=Groups, dc=do, dc=mä, dc=ne";  
    $filter = "(cn=P_*)"; // Suche nach Printergruppen mit Wildcard  

    // Suche starten:
    $search=ldap_search($ldapconn,$basedn,$filter);


    // Ergebnis der Suche in der Variablen $info ablegen:
    $info = ldap_get_entries($ldapconn, $search);

    $anzahl = ldap_count_entries($ldapconn,$search); // Anzahl der Ergebnisse für Schleifendurchlauf abspeichern
  
    // Hinweis: utf8_decode dekodiert Umlaute richtig.
    $j = 0; // Zähler für Schleifendurchläufe
    $array_printergroups = array();
    while ($j<=$anzahl)
    {
        if(strlen($info[$j]["description"]) < 2)  
        {
            $j = $j + 1;
            continue;
        }                                           
        $array_printergroups[$j]["Name"]        = utf8_decode($info[$j]["cn"]);  
        $array_printergroups[$j]["Gruppenname"] = utf8_decode($info[$j]["distinguishedname"]);  
        $array_printergroups[$j]["Abteilung"]     = utf8_decode($info[$j]["description"]);  
       
        $j = $j + 1;
    }

Jetzt haben wir endlich alles was wir für unser HTML-Formular brauchen und können anfangen es zu erstellen und zu füllen.
Die einzelnen HTML Elemente sparen wir uns hier, da es sonst ein wenig unübersichtlich wird.
Erstellt wird ein Dropdownfeld für die Standarddrucker und Checkboxen für die normalen Drucker.
Angezeigt wird nur der Abteilungsname, für die Interne Verarbeitung wird aber der komplette Name der Druckergruppe als Value übergeben
<form name="printers" method="POST" action="?page=printergroups&sent=1">  
<input type="hidden" name="username">  
<select name="druckergruppe">  
<?php
    foreach($array_standard_printergroups as $key)
    {
        $selected = ""; // Gibt selected zurück wenn User in Druckergruppe  
        if($standardprinter == $key["Name"])  
        {
            $selected = " SELECTED";  
        }
    ?>
      <option value="<?php echo $key["Gruppenname"];?>"<?php echo $selected;?>>  
      <?php
              echo $key["Abteilung"];?><?php $temp_gruppenname = explode(',',$key["Gruppenname"]);  
            $temp_gruppenname = substr($temp_gruppenname,6);
            echo ' ('.$temp_gruppenname.")";  
        ?>
       </option>
    <?php
    }
?>
</select>
<?php
$i = 0;
foreach($array_printergroups as $key)
{   
    $i = $i + 1;
    $selected = ""; // Gibt selected zurück wenn User in Druckergruppe  
    if(in_array($key["Name"], $array_printergroups_now))  
    {
        $selected = " CHECKED";  
    }
    ?> 
    <label for="check<?php echo $i;?>">   
    <input type="checkbox" name="druckercheckbox" value="<?php echo $key["Gruppenname"];?>" <?php echo $selected;?> id="check<?php echo $i;?>">     
    <?php echo $key["Abteilung"];?>  
    <?php $temp_gruppenname = explode(',',$key["Gruppenname"]);  
        $temp_gruppenname = substr($temp_gruppenname,5);
        echo ' ('.$temp_gruppenname.")";  
    ?>
    </label>
    <?php
}
?>
<input type="submit" value="Speichern" />  
</form>


Ab hier gehen wir davon aus, dass der Nutzer seine Drucker korrekt gesetzt hat und auf Speichern geklickt hat.

Mit dem übergebenen Benutzernamen starten wir eine neue Suche nach dem Benutzernamen
$basedn = "OU=User, dc=do, dc=mä, dc=ne";  
$filter = "(&(samaccountname=".$username."*)(ObjectClass=user))"; // Suche nach Benutzernamen mit Kürzel (Rest Wildcard)  

// Suche starten:
$search=ldap_search($ldapconn,$basedn,$filter);
// Ergebnis der Suche in der Variablen $info ablegen:
$info = ldap_get_entries($ldapconn, $search);

$anzahl = ldap_count_entries($ldapconn,$search); // Anzahl der Ergebnisse für Schleifendurchlauf abspeichern

Wie vorher speichern wir das Ergebnis der Suche in einem Array ab
$j = 0; // Zähler für Schleifendurchläufe
        $i = 0; // Zähler für weggespeichertes
        $array_ldapentries_username= array(); //array wird initialisiert
        //print_r($info);
        while ($j<=$anzahl)
        {
            $kk = 0;//Für die Mitgliedschaften (Extra Dimension) gibt es den Zähler $kk
            foreach($info[$j]["memberof"] as $key)  
            {
                if($kk == 0)
                {
                    $kk = 1;
                    continue; // Wird benötigt um den Countwert der die Anzahl der gruppen angibt nicht im Array zu haben, dieser Wert kommt automatisch vom DC mit
                }
                $array_ldapentries_username[$i]["Mitgliedschaften"][$kk] = $key;  
                $kk = $kk + 1;
            }
            if($kk == 0)
            {
                $j = $j + 1;
                continue;
            }
           
            $array_ldapentries_username[$i]["CNPfad"] = $info[$j]["distinguishedname"];//iconv("UTF-8", "iso-8859-1", $info[$j]["distinguishedname"]);  
            $array_ldapentries_username[$i]["Name"] = utf8_decode($info[$j]["name"]);  

            $j = $j + 1;
            $i = $i + 1;
        }

Um die Gruppen zu ändern müssen wir sie löschen und neu hinzufügen. Damit nur die P_ und SP_ Gruppen gelöscht werden lesen wir alle Gruppen aus in denen der Nutzer ist und speichern die Gruppen die gelöscht werden müssen (P_ und SP_) in einem Array.
$i = 0;
        foreach($array_ldapentries_username as $key)
        {
            $accountcn    = $key["CNPfad"];  
            //echo $accountcn;
            $j = 0;
            foreach($key["Mitgliedschaften"] as $key2)  
            {
                //echo substr($key2, 0, 5);
                if(substr($key2, 0, 5) == "CN=P_")  
                {
                    $buffer_delete_groups[$i][$j] = $key2;
                    $array_accountcn[$i] = $accountcn;
                    $j = $j + 1;
                }
                if(substr($key2, 0, 6) == "CN=SP_")  
                {
                    $buffer_delete_groups[$i][$j] = $key2;
                    $array_accountcn[$i] = $accountcn;
                    $j = $j + 1;
                }
            }
            if($j >0)
            {
                $i = $i + 1;
            }
        }

Das eben erzeugte Array lassen wir nun durch eine Schleife laufen und löschen alle Gruppen die im Array stehen aus dem Benutzer raus.
$i = 0;
        foreach($array_accountcn as $key)
        {
            //print_r($key);
            //print_r($buffer_delete_groups["0"]); 
            //echo $buffer_delete_groups[$i];
            //echo '<br>'; 
            $group = $buffer_delete_groups[$i];
            //print_r($group);
            foreach ($group as $key123)
            {
                $accountcn2["member"] = $key;//iconv("iso-8859-1", "UTF-8", $key); // ldap_mod_del verlangt ein Array als 3. Parameter  
                ldap_mod_del($ldapconn, $key123, $accountcn2);
                //echo "<br>"; 
                //echo "<br>"; 
                //echo "<br>"; 
            }
            $i = $i + 1;
        }

Wir holen uns nun wieder die Nutzerdaten des Nutzers mit einer Abfrage am Ldap
$basedn = "OU=User, dc=do, dc=mä, dc=ne";  
        $filter = "(&(samaccountname=".$username."*)(ObjectClass=user))"; // Suche nach Benutzernamen  
   
       
        // Suche starten:
        $search=ldap_search($ldapconn,$basedn,$filter);
        // Ergebnis der Suche in der Variablen $info ablegen:
        $info = ldap_get_entries($ldapconn, $search);
   
        $anzahl = ldap_count_entries($ldapconn,$search); // Anzahl der Ergebnisse für Schleifendurchlauf abspeichern
   
        //
        //print_r($info); // Print R um zusätzliche Daten in der Array Struktur zu erkennen
        //
        /* In array den benutzercn schreiben */
        $i = 0; // Zähler für weggespeichertes
        $array_ldapentries_username= array();
        while ($i<$anzahl)
        {
           
            $array_ldapentries_username[$i]["CNPfad"] =  $info[$i]["distinguishedname"];  
            $i = $i + 1;
        }

Jetzt ist der Moment in dem wir die vom HTML Formular verschickten Daten abfragen und ebenfalls in ein Array speichern
$i = 1; // Wird auf 1 gesetzt weil 0 für den Drucker aus dem Dropdownfeld reserviert ist
        $group_name = $_POST["druckergruppe"];  
        //echo $group_name;
        foreach($_POST["druckercheckbox"] as $key)  
        {
            $group_name[$i] = $key;
            $i = $i + 1;
        }

Zum Schluss schreiben wir noch das eben erstellte Array mit den neu ausgewählten Druckern ins LDAP zurück und sind fast fertig.
        foreach($array_ldapentries_username as $key)
        {
            foreach($group_name as $key2)
            {
                //echo 'pfad'.$key["CNPfad"]; 
                //echo '<br>'; 
                //echo 'gruppe'.$key2; 
                $group_info["member"] =$key["CNPfad"]; //LDAP Mod add verlangt als 3. Parameter ein Array  
               
                ldap_mod_add($ldapconn, $key2, $group_info);
            }
        }

Jetzt machen wir damit es sauber ist noch die LDAP Verbindung zu und sind fertig.
<?php
$ldapclose = ldap_close($ldapconn);
if ($ldapclose) 
{
  
}
else 
{
    echo "LDAP close failed...";  
}
?>

Ich hoffe es ist verständlich.
Falls nicht: Ich freue mich über Verbesserungsvorschläge, aber bitte seid lieb, es ist meine erste Anleitung ;)

LG Larmina

Nachtrag:
Man kann das ganze natürlich auch Standortbezogen machen, indem man einfach die Druckergruppen Ortsentsprechend benennt und in der Schleife die die Checkboxen und das Dropdown erzeugt einfach prüft ob die Drucker zum aktuell anzuzeigenden Ort passen.
Zum Beispiel:
if(substr($key["Name"],0,6) != "P_ort1")  
{
	continue;	
}

Screenshots:
Einmal die Ansicht auf dem Domain Controller:

Und die Ansicht wie es im Browser aussieht:
webansicht
dcansicht

Content-ID: 317010

Url: https://administrator.de/contentid/317010

Ausgedruckt am: 24.11.2024 um 10:11 Uhr

DerWoWusste
DerWoWusste 07.10.2016 um 10:51:34 Uhr
Goto Top
W-O-W-!

Aber sag mal, warum codest Du nicht eine simple Batch, die dem Nutzer 2 Fragen stellt und gut:

1 Wo sitzen sie (Stockwerk und Flurnummer, z.B. Stock3,Flur2=32)?
2 Welche der folgenden Drucker aus Sektion32 hätten Sie gerne (Liste mit Typen und Nummern 1-10)?
Sagen wir mal, der Nutzer aus Stock 3, Flur 2 wählt in 2 dann die Drucker 3, 4 und 8, danach nimmt das Skript diese 3 Codes 323, 324 und 328 und fügt den Nutzer über ein Miniskript (net localgroup /domain /add oder per Powershell add-adgroupmember) zu Druckergruppen DG323, DG324 und DG 328 hinzu und fertig.
Larmina
Larmina 07.10.2016 aktualisiert um 12:11:28 Uhr
Goto Top
Hi,
das hat mehrere Gründe.
- Zum einen haben wir mehrere Terminalserver im Einsatz, die (absichtlich) ohne servergespeicherte Profile betrieben werden, die Nutzer müssten also jeden Tag mindestens einmal das Script ausführen (Bei häufigerer Neuanmeldung dementsprechend mehrfach).
Die User sitzen auch meistens am gleichen Ort, nur die Azubis wechseln die Abteilungen im Wochenrythmus, bei den meisten wäre das Batchscript nur täglicher Mehraufwand.
- Zweitens könnte man das manchen unserer User 10x erklären und sie würden es immernoch falsch machen.
- Drittens hatte ich die Verbindung zum LDAP schon in einem anderen Kontext ausprobiert und musste das nurnoch erweitern
- Und last but not least kann ich besser PHP als Batch face-smile

LG Larmina
DerWoWusste
DerWoWusste 07.10.2016 um 13:11:37 Uhr
Goto Top
Ok, ohne das jetzt weiter zu hinterfragen - wie sieht das denn aus, wenn's fertig ist? Garnier' die Anleitung doch noch durch ein paar Screenshots, bitte.
neueradmuser
neueradmuser 11.10.2016 aktualisiert um 16:00:57 Uhr
Goto Top
ohne jetzt alles zu lesen (aus Zeitmangel)

ich verteile die Drucker auch per GPO und sage beim User anlegen einfach nur in welchem Büro derjenige sitzen wird (jedes Büro hat ne Raumnummer und das ist im AD wieder rum ne Sicherheitsgruppe) ...
dann gibt's eine GPO und die Drucker werden mit Item Level Targetting zugewiesen (auch der Standarddrucker) ... das ganze dann für 2 Druckserver konfiguriert macht eine Drucker-HA ;)

jetzt mit Einführung der VLan's bekomm ich es evtl noch dynamischer hin ... Mein Wunsch wäre das die GPO erkennt in welchem Raum der User ist bzw. welcher Drucker am nächsten steht und den verbindet...

Gruß (ich lese es später trotzdem noch)
Larmina
Larmina 12.10.2016 um 09:38:44 Uhr
Goto Top
Hi nightwishler,
eine sehr interessante Idee, dass sich die GPO aus dem Netz in dem der Rechner sich befindet automatisch den richtigen Drucker zieht.
Das funktioniert aber dann nur bei Fatclients?
Oder hast du eine Idee wie das bei Thinclients die sich per RDP auf einen Terminalserver verbinden funktionieren könnte?

LG Larmina
neueradmuser
neueradmuser 12.10.2016 um 09:48:04 Uhr
Goto Top
Hi, das kommt so ein bisschen auf die Umgebung an würde ich sagen.
wie gesagt, meine Umsetzung finde ich bis jetzt gut und hat mir viel arbeit gespart, jedoch hätte ich es gern komplett dynamisch so nach dem Motto der User sitzt heute mal in dem Büro xy...

theoretisch muss man das Büro "identifizieren" Sprich wenn im Büro ein Switch liegt der evlt eine eigene IP hat das ganze darüber anzapfen (also von welchem Switch kommt der User) ... das sollte mit FC wie auch mit TC gehen.
alternativ über die MAC-Adresse des Switches ?!

hab mir dazu noch nicht sooo viel Gedanken gemacht.
Vielleicht geht's auch über den "HauptSwitch" sprich da wo die Büros "zusammenlaufen" (falls kein eigener Switch im Büro liegt.
Der Gedanke war halt einfach nur eine IP/MAC Kopplung an ein Büro - dann sollte das irgendwie umsetzbar sein - Fertig face-smile

Gruß