Ein sehr leistungsfähiges System zur Administration von Windows-Netzwerken ist das seit Windows Server 2000 von Microsoft bereitgestellte Active Directory System (ADS). Es ermöglicht, die Verwaltung von Benutzern, Computern und anderen Ressourcen über eine zentrale Datenbank vorzunehmen. Diese ist auf den sog. Domain-Controllern (Windows Servern mit zusätzlichen Funktionen) installiert. Microsoft stellt sowohl mit den Server-Betriebssystemen (Windows 2008 und 2012) als auch im Ressource-Kit eine ganze Reihe von Werkzeugen zur Verwaltung des Active-Directory zur Verfügung.
Trotzdem bleiben manche Wünsche zur komfortablen Administration bestimmter Aufgaben unerfüllt. Obwohl die originäre Unterstützung von Microsoft sich auf C++ und Visual Basic beschränkt, bieten sich hier auch dem ambitionierten Delphi-Programmierer Möglichkeiten für eigene Softwareentwicklungen. Die benötigten Informationen für das ADS-Interface (ADSI) findet man im Windows-SDK. Um das ADSI mit Delphi zu verwenden benötigt man außerdem geeignete Interface-Units zu den Windows-Schnittstellen. In den weiter unten beschriebenen Programmen werden dazu die Funktionen aus der JEDI Code Library verwendet. Einige weitere interessante Informationen zu den Active Directory Service Interfaces findet man auf der Webseite von Agnisoft.
Nachfolgend soll anhand von einigen Beispielprogrammen näher erläutert werden, welche Möglichkeiten sich hier dem Windows-Administrator bieten. Die Programme werden sowohl als ausführbare Dateien als auch im Quelltext bereitgestellt:
Benutzer an der Domäne authenfizieren Domänenbenutzer anlegen Kursbenutzer anlegen Domänenbenutzer löschen
Ausführbare Programme (deutsch und englisch, 3,28 MB) | Quelltexte (800 kB) |
Häufig möchte man z.B. bei Web-Anwendungen den Zugang nur bestimmten Benutzern ermöglichen. Dazu kann man eine eigene Benutzerverwaltung verwenden. Wenn jedoch bereits ein Windows Active Directory vorhanden ist, ist es viel bequemer, dies für die Authentifizierung des Benutzers zu verwenden.
Eine solche Authentifizierung in den für Web-Anwendungen benutzten Programmierumgebungen, wie z.B.PHP oder Perl zu realisieren, erfordert einige Vorkenntnisse. Viel einfacher ist es, das hier vorgestellte Konsolenprogramm AuthAds zu integrieren. Es wird mit den erforderlichen Parametern aufgerufen und liefert als ExitCode einen Wert, der Aufschluss darüber gibt, ob der Benutzer berechtigt ist. Dabei kann zusätzlich zur Authentifizierung auch noch die Zugehörigkeit zu einer Gruppe geprüft werden.
AuthAds <domain> <username> <password> [<group>] [<coded password>]
Werden nur die ersten drei Parameter angegeben, wird geprüft, ob es sich um einen
in der Domäne registrierten Benutzer handelt. Ist zusätzlich eine Gruppe genannt,
wird auch noch geprüft, ob der Benutzer dieser Gruppe angehört. Durch Anlegen einer
Gruppe im ADS kann so sehr einfach gesteuert werden, wer die jeweilige
Web-Anwendung benutzen darf.
Wenn <password> = base64 ist, wird der Parameter <coded password>
benötigt. Er stellt das base64-(bzw. mime-)kodierte Kennwort dar. In diesem Fall
muss der Parameter <group> immer angegeben werden. Falls keine Gruppe
geprüft werden soll, muss hier eine Leerstring ("") stehen.
Die in der ADS-Administration enthaltene Funktion zum Hinzufügen eines Domänenbenutzers bietet nur wenig Komfort bei der Eingabe. Alle Zusatzangaben müssen für jeden neuen Benutzer von Hand eingegeben werden. Für die Eingabe der Passwörter, sowie die Zuordnung zu bestimmten Organisationseinheiten (OU) und Gruppen sind zusätzliche Schritte erforderlich.
Das nachfolgend beschriebene Delphi-Programm erledigt dies alles in einem Schritt. Die innerhalb einer Domäne immer gleichbleibenden Angaben können voreingestellt werden.
Die wichtigsten Teile des Programms werden nachstehend näher erläutert |
Verbinden mit der Domäne: Diese und auch die nachfolgenden Abfragen basieren auf der LDAP-Syntax. Die Variable FDomain steht für den Namen der Domäne, mit der eine Verbindung hergestellt werden soll. Über die Funktion ADsGetObject wird ein Object root zurückgegeben, das es ermöglicht, weitere Informationen über die Domäne zu ermitteln, z.B. über Get('distinguishedName') den vollständigen Namen der Domäne (DC=aaa, DC=bbb, DC=ccc, DC=ddd,). Dieser kann dann mit der Funktion ToPrincipal in die übliche Nomenklatur (aaa.bbb.ccc.ddd) umgesetzt werden. Anschließend wird ein weiteres Object DSearch vom Typ IDirectorySearch erzeugt, as für die im nächsten Schritt beschriebene Suche nach Organisationseinheiten und Gruppen benötigt wird. Zur Initialisierung der Suche müssen zunächst über SetSearchPreference die Einstellungen für die Suche vorgenommen werden. |
... uses ..., JwaAdsHlp, JwaAdsTLB, JwaAdsErr; ... function TfrmDomUser.InitDomainData : boolean; const szLen = 256; var bind : widestring; opt : ads_searchpref_info; hr : HResult; ptrResult : THandle; col : ads_search_column; dwErr : DWord; szErr,szName : array[0..szLen-1] of WideChar; sd,sn,sp : string; root : IADs; DSearch : IDirectorySearch; function ToPrincipal(cs : string) : string; var i : integer; t : string; begin t:=''; while length(cs)>0 do begin i:=pos('DC=',cs); if i>0 then begin delete(cs,1,3); if length(t)=0 then t:=ReadNxtStr(cs,',') else t:=t+'.'+ReadNxtStr(cs,','); end else cs:=''; end; Result:=t; end; begin ... bind:='LDAP://'+FDomain; try ADsGetObject(PWideChar(Bind),IID_IADs,pointer(root)); Context:=root.Get('distinguishedName'); DNSName:=ToPrincipal(Context); edtDNSName.Text:='@'+DNSName; ... ADsGetObject(PWideChar(bind),IID_IDirectorySearch,pointer(DSearch)); ... with opt do begin dwSearchPref:=ADS_SEARCHPREF_SEARCH_SCOPE; vValue.dwType:=ADSTYPE_INTEGER; vValue.Integer:=ADS_SCOPE_SUBTREE; end; with DSearch do begin if Failed(SetSearchPreference(@opt,1)) then begin ADsGetLastError(dwErr,@szErr,szLen,@szName[0],szLen); ShowMessage(WideCharToString(szErr)+sLineBreak+WideCharToString(szName)); btnMakeUser.Enabled:=false; exit; end; ... |
Abfragen der Organisationseinheiten: ExecuteSearch ist eine Methodes des Objekts IDirectorySearch und wird hier mit dem Suchfilter organizationalUnit aufgerufen. In der While-Schleife werden nacheinander über die Variable col zunächst die Beschreibungen (Descriptions) der Organisationseinheiten der Domäne abgefragt. Wenn eine gültige Beschreibung gefunden wurde, werden der zugehörige Name und der ADS-Pfad ermittelt. Alle Informationen werden dann in der Item-Liste der Combo-Box cbxOU als Objekte gespeichert. Beim Anlegen eines neuen Benutzers kann aus dieser Liste die zugehörige Organisationseinheit ausgewählt werden. |
... uses ..., JwaAdsHlp, JwaAdsTLB, JwaAdsErr; ... type TADsObject = class (TObject) FName,FDesc,FPath : string; constructor Create (AName,ADesc,Apath : string); end; const ColCount = 3; ColNames : array[0..ColCount-1] of WideString = ('Name','Description','ADsPath'); ... function TfrmDomUser.InitDomainData : boolean; ... // see code snippet above // Organisationseinheiten suchen cbxOU.Clear; ExecuteSearch('(objectClass=organizationalUnit)',@ColNames,ColCount,ptrResult); hr:=GetNextRow(ptrResult); while (hr<>S_ADS_NOMORE_ROWS) do begin sd:=''; sd:=''; sp:=''; if Succeeded(GetColumn(ptrResult,ColNames[1],col)) then begin with col do if pADsValues<>nil then sd:=pAdsvalues^.CaseExactString; FreeColumn(@col); if (length(sd)>0) and (sd[1]='-') then sd:=''; end; if length(sd)>0 then begin if Succeeded(GetColumn(ptrResult,ColNames[0],col)) then begin with col do if pADsValues<>nil then sn:=pAdsvalues^.CaseIgnoreString; FreeColumn(@col); end; if Succeeded(GetColumn(ptrResult,ColNames[2],col)) then begin with col do if pADsValues<>nil then sp:=pAdsvalues^.CaseIgnoreString; FreeColumn(@col); end; if length(sp)>0 then cbxOU.Items.AddObject(sd,TADsObject.Create(sn,sd,sp)); end; hr:=GetNextRow(ptrResult); end; ... |
Abfragen der Gruppen: Die Abfrage der Domänen-Gruppen erfolgt ganz ähnlich. Bei der Suchanfrage werden alle vorgegebenen Gruppen (vom Typ Builtin) ausgeschlossen. Das Suchergebnis wird in einer Listbox lbxAg gespeichert. Beim Anlegen eines neuen Benutzers können aus dieser Liste die Gruppen ausgewählt werden, denen der Benutzer angehören soll. |
// Gruppen suchen lbxAg.Clear; ExecuteSearch('(&(objectClass=group)(!CN=Builtin))',@ColNames,ColCount-1,ptrResult); hr:=GetNextRow(ptrResult); while (hr<>S_ADS_NOMORE_ROWS) do begin sd:=''; sd:=''; sp:=''; if Succeeded(GetColumn(ptrResult,ColNames[1],col)) then begin with col do if pADsValues<>nil then sd:=pAdsvalues^.CaseExactString; FreeColumn(@col); end; if length(sd)>0 then begin if Succeeded(GetColumn(ptrResult,ColNames[0],col)) then begin with col do if pADsValues<>nil then sn:=pAdsvalues^.CaseIgnoreString; FreeColumn(@col); end; if Succeeded(GetColumn(ptrResult,ColNames[2],col)) then begin with col do if pADsValues<>nil then sp:=pAdsvalues^.CaseIgnoreString; FreeColumn(@col); end; if length(sp)>0 then lbxAg.Items.AddObject(sd,TADsObject.Create(sn,sd,sp)); end; hr:=GetNextRow(ptrResult); end; ... |
Prüfen, ob der Benutzer bereits eingetragen ist: Bevor der neue Benutzer eingetragen werden kann, muss geprüft werden, ob es bereits einen mit diesem Namen gibt. Dazu wird eine Suchanfrage mit SearchUser (s.u.) gestartet. Gibt es den Benutzer schon, werden nur die zugehörigen Einträge im ADS angezeigt. Im anderen Fall kann ein neuer Benutzer mit den gemachten Vorgaben angelegt werden. |
procedure TfrmDomUser.btnMakeUserClick(Sender: TObject); var ADsCont : IADsContainer; ADsOU : IADsOU; User : IDispatch; Grp : IAdsGroup; bind : widestring; ap,s : string; i,n : integer; const SpecChars : set of char = [',','/',';']; function ReplaceSpecChar(s : string) : string; var t : string; i : integer; begin t:=''; for i:=1 to length(s) do if (s[i] in SpecChars) then t:=t+'\'+s[i] else t:=t+s[i]; Result:=t; end; begin if CheckHomeDir and (length(edtKontoName.Text)>0) then begin if rbtGlobal.Checked then bind:='LDAP://'+FDomain+'/CN=Users,'+Context else with cbxOU do bind:=(Items.Objects[ItemIndex] as TAdsObject).FPath; // prüfen, ob der Benutzer bereits eingetragen ist ap:=SearchUser (edtKontoName.text); if length(ap)>0 then begin ADsGetObject(PWideChar(ap),IID_IADsUser,pointer(user)); with User as IADsUser do begin GetInfo; try edtFullName.Text:=FullName; except edtFullName.Text:=''; end; ErrorDialog(Caption,'Der Benutzer '+edtFullname.Text+' ('+edtKontoName.Text+'@'+DNSName+')'+ sLineBreak+'ist bereits eingetragen!'); ... end else if ConfirmDialog (Caption,'Neuen Benutzer: '+edtKontoName.Text+edtDNSName.Text+ sLineBreak+'unter: '+bind+' anlegen?') then begin // noch nicht vorhanden ADsGetObject(PWideChar(bind),IID_IADsContainer,pointer(ADsCont)); User:=ADsCont.Create('user','CN='+ReplaceSpecChar(edtFullname.Text)); ... end; end; ADsCont:=nil; Pwd:=''; btnPwd.Font.Color:=clRed; end; |
Nach einem Benutzer suchen: Die nachfolgende Routine sucht nach dem Anmeldenamen (sAMAccountName) eines Benutzers und liefert diesen als String zurück. |
function TfrmDomUser.SearchUser (CommonName : string) : string; var DSearch : IDirectorySearch; opt : ads_searchpref_info; ptrResult : THandle; col : ads_search_column; begin Result:=''; ADsGetObject('LDAP://'+FDomain,IDirectorySearch,DSearch); with opt do begin dwSearchPref:=ADS_SEARCHPREF_SEARCH_SCOPE; vValue.dwType:=ADSTYPE_INTEGER; vValue.Integer:=ADS_SCOPE_SUBTREE; end; with DSearch do begin if Succeeded(SetSearchPreference(@opt,1)) then begin ExecuteSearch(PWideChar('(&(objectClass=user)(sAMAccountName='+CommonName+'))'),@ColNames,ColCount,ptrResult); if GetNextRow(ptrResult)<>S_ADS_NOMORE_ROWS then begin if Succeeded(GetColumn(ptrResult,ColNames[2],col)) then begin with col do if pADsValues<>nil then Result:=pAdsvalues^.CaseIgnoreString; FreeColumn(@col); end; end end end; end; |
Anzeige eines bereits eingetragenen Benutzers: Die Benutzerangaben werden über die Methode GetInfo abgeholt. Bei der Abfrage der möglichen Eigenschaften (siehe dazu Windows-SDK) kommt es zu einem Fehler, wenn eine Eigenschaft nicht verfügbar ist. Daher muss jedes Mal das Konstrukt try ... except .. end; verwendet werden. |
... ADsGetObject(PWideChar(ap),IID_IADsUser,pointer(user)); with User as IADsUser do begin GetInfo; try edtFullName.Text:=FullName; except edtFullName.Text:=''; end; ErrorDialog(Caption,'Der Benutzer '+edtFullname.Text+' ('+edtKontoName.Text+'@'+DNSName+')'+ sLineBreak+'ist bereits eingetragen!'); try edtFirstName.Text:=FirstName; except edtFirstName.Text:=''; end; try edtLastName.Text:=LastName; except edtLastName.Text:=''; end; try edtDescription.Text:=Description; except edtDescription.Text:=''; end; try edtKontoName.Text:=Get('sAMAccountName'); except edtKontoName.Text:=''; end; try edtRoom.Text:=OfficeLocations; except edtRoom.Text:=''; end; try edtPhone.Text:=TelephoneNumber; except edtPhone.Text:=''; end; try chbDisabled.Checked:=AccountDisabled; except chbDisabled.State:=cbGrayed; end; try s:=Profile; with hcbProfile do begin Text:=s; AddItem(ReplaceUsername(s)); end; except end; try s:=HomeDirectory; with hcbHomeDir do begin Text:=s; AddItem(ReplaceUsername(s)); end; except end; try s:=Get('homeDrive'); with cbHomeDrive do begin n:=Items.IndexOf(s); if n<0 then n:=0; ItemIndex:=n; end; except end; try ADsGetObject(PWideChar(Parent),IID_IADsOU,pointer(ADsOU)); rbtOU.Checked:=true; with cbxOU do ItemIndex:=Items.IndexOf(ADsOU.Description); except rbtGlobal.Checked:=true; cbxOU.ItemIndex:=-1; end; ADsOU:=nil; end; ... |
Anlegen eines neuen Benutzers: Zunächst muss ein ADS-Context über ADsGetObject geholt werden, mit dem dann der neue Domänen-Benutzer über ADsCont.Create angelegt werden kann. Anschließend werden die Eigenschaften des Benutzers gesetzt. Für diejenigen, für die keine explizite Eigenschaft vorgesehen (siehe Windows-SDK) ist, muss die Funktion Put verwendet werden (z.B. Put('sAMAccountName',...)). Zum Abschluss werden alle Information mit SetInfo im ADS gespeichert. Anschließend wird dann noch die Mitgliedschaft in den Gruppen eingetragen (Grp.Add): |
... ADsGetObject(PWideChar(bind),IID_IADsContainer,pointer(ADsCont)); User:=ADsCont.Create('user','CN='+ReplaceSpecChar(edtFullname.Text)); with User as IADsUser do begin // set Mandatory attributes Put('sAMAccountName',edtKontoName.Text); // set Optional attributes FullName:=edtFullName.Text; if length(edtFirstName.Text)>0 then FirstName:=edtFirstName.Text; LastName:=edtLastName.Text; Description:=edtDescription.Text; with hcbHomeDir do if length(Text)>0 then HomeDirectory:=ReplacePlaceholder(Text); with cbHomeDrive do if ItemIndex>0 then Put('homeDrive',Items[ItemIndex]); with hcbProfile do if length(Text)>0 then Profile:=ReplacePlaceholder(Text); Put('userPrincipalName',edtKontoname.Text+'@'+DNSName); try SetInfo; // Speichern except on E:EOleException do ShowExceptionError(E); end; // additional attributes if length(Pwd)=0 then begin if not chbDisabled.Checked then begin // Kennwort Put('pwdLastSet',0); // ändern bei nächster Anmeldung s:=StringReplace(DefPwd,'#',edtKontoname.Text,[]); try SetPassword (s); except on E:EOleException do ShowExceptionError(E); end; end; Put('userAccountControl',0); end else begin try SetPassword (Pwd); except on E:EOleException do ShowExceptionError(E); end; if NoExpire then Put('userAccountControl',ADS_UF_DONT_EXPIRE_PASSWD) else Put('userAccountControl',0); end; AccountDisabled:=chbDisabled.Checked; if length(edtRoom.Text)>0 then OfficeLocations:=edtRoom.Text; if length(edtPhone.Text)>0 then TelephoneNumber:=edtPhone.Text; EmailAddress:=edtKontoname.Text+'@'+DNSName; try SetInfo; // Speichern except on E:EOleException do ShowExceptionError(E); end; end; // Arbeitsgruppen zuordnen with lbxAG do if SelCount>0 then for i:=0 to Items.Count-1 do if Selected[i] then begin if succeeded(ADsGetObject((Items.Objects[i] as TADsObject).FPath,IID_IADsGroup,Grp)) then Grp.Add((user as IAdsUser).AdsPath); end; Grp:=nil; ... MessageDlg('In '+FDomain+' wurde ein neuer Benutzer angelegt:'+sLineBreak +edtFullname.Text+'('+edtKontoname.Text+'@'+DNSName+')', mtInformation,[mbOK],0); user:=nil; end; ADsCont:=nil; ... end ... |
An Schulen und Universitäten kommt es sehr häufig vor, dass für bestimmte Kurse
mehrere Benutzer Zugriff zu den PCs eines Lernraums benötigen. Der administrative
Aufwand hierfür gestaltet sich besonders gering, wenn diese PCs in eine
Windows-Domäne integriert sind. Es sind dann lediglich einige neue Domänenbenutzer
anzulegen. Durch Zuordnung zu einer geeigneten Organisationseinheit erhalten sie
automatisch die passenden Gruppenrichtlinien und durch Aufnahme in eine passende Gruppe
die benötigten Zugriffsrechte.
Das nachfolgend beschriebene Programm automatisiert diesen Vorgang, indem es eine
beliebig vorgebbare Anzahl von Benutzern (z.B. user01 .. user09) anlegt und diese
einer auswählbaren Organisationseinheit und einer oder mehreren Gruppen zuordnet.
Die Laufzeit des Kontos kann außerdem zeitlich begrenzt werden (z.B. auf ein Semester).
Passwörter für die Konten werden automatisch erzeugt. Alle Benutzerinformation können außerdem
in Etikettenform ausgedruckt werden, um die benötigten Anmeldeinformation
an den jeweiligen Kursbenutzer weitergeben zu können.
Zu den weiteren Einstellungen siehe hier.
Außerdem ist es möglich vorhandene Konten mit neuen Passwörtern zu versehen.
Besonderheiten des Programms |
Setzen der Eigenschaft: Benutzer kann Kennwort nicht ändern Diese Eigenschaft kann nicht zusammen mit den oben beschriebenen Benutzerinfos gesetzt werden. Der Mechanismus ist wesentlich komplizierter und erfordert einen Zugriff auf die Access Control Lists. |
// Set "User Cannot Change Password" // see sample in "Windows Platform SDK" IU:=(User as IADsUser).Get('ntSecurityDescriptor'); IU.QueryInterface(IID_IADsSecurityDescriptor,SecDesc); ACL:=IADsAccessControlList(SecDesc.DiscretionaryAcl); IU:=ACL._NewEnum; if succeeded(IU.QueryInterface(IID_IEnumVARIANT,Enum)) then begin while (Succeeded(ADsEnumerateNext(Enum,1,VarArr,lNumEl)))and (lNumEl>0) do begin if Succeeded(IDispatch(varArr).QueryInterface(IID_IADsAccessControlEntry,ACE)) then with ACE do begin if UpperCase(ObjectType)=UpperCase(CHANGE_PASSWORD_GUID) then begin // nachfolgend sind die länderspez. Namen zu benutzen: // z.B. "Jeder" und "NT-AUTORITÄT\SELBST" // Die Ermittlung dieser Namen erfolgt über die Funktion // GetAccountName in WinApi (s.o.) if Trustee=TrEvrOne then begin // 'Everyone' // Modify the ace type of the entry. if UserCannotChangePassword then AceType:=ADS_ACETYPE_ACCESS_DENIED_OBJECT else AceType:=ADS_ACETYPE_ACCESS_ALLOWED_OBJECT; end; // TrSelf (s.o.) enthält nicht den Teil "NT AUTHORITY\" (bzw. "NT-AUTORITÄT\") // Ich habe keine Infos gefunden, wie das anders zu machen geht // Da die Strings so nicht mit Trustee verglichen werden können, // wird der Teil vor "\" entfernt j:=AnsiPos('\',Trustee); if j>0 then begin s:=AnsiRightStr(Trustee,length(Trustee)-j); if s=TrSelf then begin // 'NT AUTHORITY\SELF' // Modify the ace type of the entry. if UserCannotChangePassword then AceType:=ADS_ACETYPE_ACCESS_DENIED_OBJECT else AceType:=ADS_ACETYPE_ACCESS_ALLOWED_OBJECT; end; end; end; end; end; // Update the ntSecurityDescriptor property. (User as IADsUser).Put ('ntSecurityDescriptor',SecDesc); //Commit the changes to the server. (User as IADsUser).SetInfo; end; ... |
Wenn ein Domänenbenutzer aus dem Active Directory entfernt werden soll, ist es
meist auch erforderlich die dazu gehörigen persönlichen Daten zu löschen. Sind diese
in einem Verzeichnis abgelegt, das dem Anmeldenamen des Benutzers entspricht kann
das vorliegenden Programm diese automatisch in einem einstellbaren Bereich
(z.B. einer Freigabe auf einem Server) finden.
Das Programm liest beim Start eine Liste der aktuellen Domänenbenutzer ein. Der
Administrator wählt den zu entfernenden Benutzer aus und stellt ein, ob auch die
zugehörigen Daten gelöscht werden sollen. Auf Knopfdruck wird dies dann nach
einer Bestätigungsabfrage ausgeführt.
Besonderheiten des Programms |
Liste der Benutzer erstellen: ExecuteSearch ist eine Methode des Objekts IDirectorySearch und wird hier mit dem Suchfilter user, wobei computer und Builtin ausgeschlossen werden, aufgerufen. In der While-Schleife werden nacheinander über die Variable col die Namen der Benutzer (Name), die Kontonamen sAMAccountName und die ADS-Pfade ADsPath abgefragt. |
type TADsObject = class (TObject) FName,FDesc,FPath : string; constructor Create (AName,ADesc,Apath : string); end; const UserCols = 'Name,sAMAccountName,ADsPath,userAccountControl,mail'; ... procedure StringToPwArray (SListe : string; var PwArr : TPWideArray; var Count : integer); var s : string; begin Count:=0; while length(SListe)>0 do begin s:=ReadNxtStr(SListe,Comma); if length(s)>0 then begin SetLength(PwArr,Count+1); GetMem(PwArr[Count],256); StringToWideChar(s,PwArr[Count],256); inc(Count) end; end; end; ... function TfrmDelUser.LoadUserList (const OU : string) : boolean; var hr : HResult; ptrResult : THandle; colnames : TPWideArray; col : ads_search_column; sd,sn,sp,sm : string; ColCount,ns : integer; ok : boolean; begin try with DSearch do begin // Benutzer suchen UserList.Clear; StringToPwArray(UserCols,ColNames,ColCount); ExecuteSearch('(&(objectCategory=person)(objectClass=user))',@ColNames[0],ColCount,ptrResult); hr:=GetNextRow(ptrResult); while (hr<>S_ADS_NOMORE_ROWS) do begin sd:=''; sd:=''; sp:=''; sn:=''; sm:=''; ns:=4; if Succeeded(GetColumn(ptrResult,ColNames[1],col)) then begin with col do if pADsValues<>nil then sd:=pAdsvalues^.CaseExactString; FreeColumn(@col); if (length(sd)>0) and (sd[1]='-') then sd:=''; end; if length(sd)>0 then begin if Succeeded(GetColumn(ptrResult,ColNames[0],col)) then begin with col do if pADsValues<>nil then sn:=pAdsvalues^.CaseIgnoreString; FreeColumn(@col); end; if Succeeded(GetColumn(ptrResult,ColNames[2],col)) then begin with col do if pADsValues<>nil then sp:=pAdsvalues^.CaseIgnoreString; FreeColumn(@col); end; if Succeeded(GetColumn(ptrResult,ColNames[3],col)) then begin with col do if pADsValues<>nil then begin if (pAdsvalues^.Integer and ADS_UF_ACCOUNTDISABLE)<>0 then ns:=1 else ns:=0; end; FreeColumn(@col); end; if Succeeded(GetColumn(ptrResult,ColNames[4],col)) then begin with col do if pADsValues<>nil then sm:=pAdsvalues^.CaseIgnoreString; FreeColumn(@col); end; if length(OU)=0 then ok:=TextPos('ou',sp)=0 else ok:=TextPos(OU,sp)>0; if (length(sn)>0) and ok then begin UserList.AddObject(sn,TADsObject.Create(sn,sd,sp,sm,ns)); end; end; hr:=GetNextRow(ptrResult); end; lvUser.Items.Count:=UserList.Count; Result:=true; end; except Result:=false; end; end; ... |
Eigenschaften des Benutzers anzeigen: Beim Klick auf einen Benutzer in der Liste werden seine gespeicherten Eigenschaften angezeigt (siehe auch oben). |
procedure TfrmDelUser.ShowUserData (AIndex : integer); var ap : string; ADsOU : IADsOU; begin if (AIndex=0) and (AIndex<UserList.Count) then begin cbUserData.Checked:=false; lbUserDirs.Clear; ap:=(UserList.Objects[AIndex] as TADsObject).FPath; if Failed(ADsGetObject(PWideChar(ap),IID_IADsUser,pointer(user))) then Exit; with User as IADsUser do begin GetInfo; try edtFullName.Text:=FullName; except edtFullName.Text:=''; end; try edtFirstName.Text:=FirstName; except edtFirstName.Text:=''; end; try edtLastName.Text:=LastName; except edtLastName.Text:=''; end; try edtDescription.Text:=Description; except edtDescription.Text:=''; end; try edtKontoName.Text:=Get('sAMAccountName'); except edtKontoName.Text:=''; end; try edtRoom.Text:=OfficeLocations; except edtRoom.Text:=''; end; try edtPhone.Text:=TelephoneNumber; except edtPhone.Text:=''; end; try AccDisabled:=AccountDisabled; with btToggleAccount do begin Enabled:=true; Glyph:=nil; if AccDisabled then begin Caption:=_('Enable account'); ImageList.GetBitmap(0,Glyph); end else begin Caption:=_('Disable account'); ImageList.GetBitmap(1,Glyph); end; end; except AccDisabled:=false; btToggleAccount.Enabled:=false; end; lmDisabled.Visible:=AccDisabled; laDisabled.Visible:=AccDisabled; try ADsGetObject(PWideChar(Parent),IID_IADsOU,pointer(ADsOU)); OUPath:=ADsOU.AdsPath; except OuPath:=''; end; ADsOU:=nil; end; end; end; |
Suche nach den Benutzerdaten: Es wird nach einem Unterverzeichnis mit dem Namen des Benutzers gesucht. |
function TfrmDelUser.SearchDir (Base,SubDir,UserName : string) : string; var DirInfo : TSearchRec; Findresult : integer; sd : string; begin Result:=''; if (length(SubDir)=0) then sd:=Base else if (SubDir[1]='\') then sd:=Base+SubDir else sd:=IncludeTrailingPathDelimiter(Base)+SubDir; FindResult:=FindFirst(IncludeTrailingPathDelimiter(sd)+'*.*',faDirectory+faReadOnly+faHidden+faSysfile,DirInfo); while (length(Result)=0) and (FindResult=0) do with DirInfo do begin if NotSpecialDir(Name) then begin if SameFileName(DirInfo.Name,UserName) then Result:=IncludeTrailingPathDelimiter(sd)+DirInfo.Name else Result:=SearchDir(Base,IncludeTrailingPathDelimiter(SubDir)+DirInfo.Name,UserName); end; FindResult:=FindNext(DirInfo); end; FindClose(DirInfo); end; ... |
Löschen des Benutzers und der Daten: Zunächst wird der Benutzer gelöscht (ADsCont.Delete('user','CN='...), dann die zugehörigen Daten (DeleteDirectories). |
procedure TfrmDelUser.RemoveUser (const ABind : string); var s : string; dc,fc,i,n : integer; ADsCont : IADsContainer; ... begin ADsGetObject(PWideChar(ABind),IID_IADsContainer,pointer(ADsCont)); try ADsCont.Delete('user','CN='+ReplaceSpecChar(edtFullName.Text)); s:=Format(_('User: %s was removed from ADS!'),[edtFullName.Text]); //'Der Benutzer: %s wurde aus dem ADS entfernt!'; if cbUserData.Checked then begin StatusWindow.ShowStatus(BottomLeftPos(btnSearch,Point(0,-150)),_('Deleting user data'),'',true,5); dc:=0; fc:=0; s:=s+sLineBreak+_('Following user data were deleted:'); //'Folgende Benutzerdaten wurden gelöscht:' with lbUserDirs do for i:=0 to Items.Count-1 do if Selected[i] then begin StatusWindow.Status:=Items[i]; Application.ProcessMessages; // Löschen Benutzerverzeichnis einschließlich aller Unterverzeichnisse und Dateien DeleteDirectories(Items[i],'',dc,fc); s:=s+sLineBreak+_(' Directory: ')+Items[i]; //' Verzeichnis: ' end; StatusWindow.Close; dec(dc); if dc>0 then begin s:=s+sLineBreak+IntToStr(dc); if dc=1 then s:=s+_(' Subdirectory') //' Unterverzeichnis' else s:=s+_(' Subdirectories') //' Unterverzeichnisse'; end else s:=s+sLineBreak+_('No subdirectories found'); //'keine Unterverzeichnisse gefunden' if fc>0 then begin s:=s+sLineBreak+IntToStr(fc); if fc=1 then s:=s+_(' file') //' Datei' else s:=s+_(' files'); //' Dateien'; end; end ; ... |