Ha PowerShell, akkor objektumok. Az előzőkhez hasonlóan csatlakozzunk a most létrehozott szervezeti egység objektumhoz, és nézzük meg a tagjellemzőit a get-member cmdlet segítségével:
PS C:\> $adou = [ADSI] "LDAP://OU=Emberek,DC=iqjb,DC=w08"
PS C:\> $adou
distinguishedName
-----------------
{OU=Emberek,DC=iqjb,DC=w08}
PS C:\> $adou | get-member
TypeName: System.DirectoryServices.DirectoryEntry
Name MemberType Definition
---- ---------- ----------
description Property System.DirectoryServices.PropertyValueC...
distinguishedName Property System.DirectoryServices.PropertyValueC...
dSCorePropagationData Property System.DirectoryServices.PropertyValueC...
instanceType Property System.DirectoryServices.PropertyValueC...
name Property System.DirectoryServices.PropertyValueC...
nTSecurityDescriptor Property System.DirectoryServices.PropertyValueC...
objectCategory Property System.DirectoryServices.PropertyValueC...
objectClass Property System.DirectoryServices.PropertyValueC...
objectGUID Property System.DirectoryServices.PropertyValueC...
ou Property System.DirectoryServices.PropertyValueC...
uSNChanged Property System.DirectoryServices.PropertyValueC...
uSNCreated Property System.DirectoryServices.PropertyValueC...
whenChanged Property System.DirectoryServices.PropertyValueC...
whenCreated Property System.DirectoryServices.PropertyValueC...
Hát elég furcsa, amit kaptunk. Látjuk a szervezeti egységünk tulajdonságait, de hol vannak a metódusok? Hol a Create? Sajnos a PowerShell-ben nincsen 100%-ban adaptálva a System.DirectoryServices osztály. Ennek több oka van. Az egyik, hogy valójában itt nem színtiszta .NET osztályról van szó, hanem COM objektum is meghúzódik a felszín alatt, és annak metódusait nem olyan egyszerű átemelni. Gondoljunk csak arra, hogy egy ilyen DirectoryEntry típusú objektum lehet felhasználói fiók, számítógép fiók, telephely, csoport, stb., ezeknek mind más és más metódusuk van, ezeknek az adaptálása a PowerShell környezetbe nem olyan egyszerű. Ebből származik a második ok, ami miatt ez nincs adaptálva, az pedig az, hogy a fejlesztők az 1.0 megjelenését nem akarták ezzel késleltetni, várhatóan a 2.0 verzió már precízebb AD támogatást fog nyújtani.
Szerencsére van egy kis menekvési ösvényünk, azaz kikapcsolhatjuk a PowerShell adaptációs rétegét, és megnézhetjük a „színtiszta” .NET objektumot is, ha a psbase nézeten keresztül nézzük az objektumunkat:
PS C:\> $adou.psbase | get-member
TypeName: System.Management.Automation.PSMemberSet
Name MemberType Definition
---- ---------- ----------
...
MoveTo Method System.Void MoveTo(DirectoryEntry n...
RefreshCache Method System.Void RefreshCache(), System....
remove_Disposed Method System.Void remove_Disposed(EventHa...
Rename Method System.Void Rename(String newName)
...
ToString Method System.String ToString()
AuthenticationType Property System.DirectoryServices.Authentica...
Children Property System.DirectoryServices.DirectoryE...
Container Property System.ComponentModel.IContainer Co...
Guid Property System.Guid Guid {get;}
Name Property System.String Name {get;}
NativeGuid Property System.String NativeGuid {get;}
NativeObject Property System.Object NativeObject {get;}
ObjectSecurity Property System.DirectoryServices.ActiveDire...
Options Property System.DirectoryServices.DirectoryE...
Parent Property System.DirectoryServices.DirectoryE...
Password Property System.String Password {set;}
Path Property System.String Path {get;set;}
Properties Property System.DirectoryServices.PropertyCo...
SchemaClassName Property System.String SchemaClassName {get;}
SchemaEntry Property System.DirectoryServices.DirectoryE...
Site Property System.ComponentModel.ISite Site {g...
UsePropertyCache Property System.Boolean UsePropertyCache {ge...
Username Property System.String Username {get;set;}
A fenti, kicsit megvágott, de még így is hosszú listából látszik, hogy az objektumot valójában lehet például mozgatni, átnevezni, és néhány újabb tulajdonság is feltárul a szemünk előtt. De például még mindig nem látjuk a SetInfo és a Create metódust, mert ezek az ADSI COM interfészből jönnek, és a .NET nem kérdezi ezeket le, így nem is mutatja meg, viszont meghívni, használni lehet őket.
Vagy nézzük a következőket:
PS C:\> $d = [ADSI] ""
PS C:\> $d
distinguishedName
-----------------
{DC=iqjb,DC=w08}
A fenti módon például nagyon egyszerűen lehet az aktuális tartományunkhoz csatlakozni. Próbáljuk meg ennek megnézni a „rejtett” children tulajdonságát:
PS C:\> $adou = [ADSI] "LDAP://OU=Demó,DC=iqjb,DC=w08"
PS C:\> $adou.psbase.Children
distinguishedName
-----------------
{CN=Csilla Fájdalom,OU=Demó,DC=iqjb,DC=w08}
{CN=Csoport,OU=Demó,DC=iqjb,DC=w08}
{CN=group1,OU=Demó,DC=iqjb,DC=w08}
{CN=János Vegetári,OU=Demó,DC=iqjb,DC=w08}
{CN=Márton Beléd,OU=Demó,DC=iqjb,DC=w08}
Hiszen ez megadta az adott konténer objektumban található al-objektumokat!
Megjegyzés
A lokális gép esetében a PSBase egészen extrém információkat rejt el:
[40] PS C:\> $computer = [ADSI]"WinNT://."
[41] PS C:\> $v = $computer.psbase.children | ForEach-Object {$_}
[42] PS C:\> $v[0].name; $v[0].psbase.schemaclassname
Administrator
User
[43] PS C:\> $v[30].name; $v[30].psbase.schemaclassname
BITS
Service
A [40]-es sorban hozzákapcsolódom a helyi géphez egy $computer változón keresztül. Majd betöltöm egy $v változóba ennek a psbase által feltárt children tulajdonságának elemeit egy ciklussal. Ennek 0. elemének name tulajdonságát nézve kiderül, hogy ez egy helyi felhasználó, az Administrator. Ennek típusát is ki lehet olvasni egy újabb psbase feltárás után a schemaclassname tulajdonsággal.
A meglepetés akkor jön, ha egy jóval későbbi elemet nézünk meg, mondjuk a 90.-et. Nálam ez meg egy szolgáltatás volt, a BITS (a kedves olvasónál lehet, hogy ez valami más). Azaz a lokális gép esetében a children a gépnek nagyon sok jellemzőjét megadja. Az eligazodást segíti a Find metódus:
[47] PS C:\> $service = $computer.psbase.Children.Find("Alerter")
[48] PS C:\> $service.serviceaccountname
NT AUTHORITY\LocalService
Miután itt vegyes elemekkel dolgozunk, itt különösen jól jön a get-member cmdlet.
Mindebből az következik, hogy nem érdemes még kidobni korábbi ADSI ismereteinket, illetve ismernünk kell az AD objektumok tulajdonságainak neveit, hogy ezeket a tulajdonságokat módosíthassuk. Nézzünk ez utóbbira példát egy felhasználói fiókkal kapcsolatban. Van egy már létező felhasználóm, annak szeretném kiolvasni és beállítani a telefonszám tulajdonságát. Ehhez kell nekünk az, hogy tudjuk, hogy az AD-ben a telefonszám tulajdonságnak mi is a belső elnevezése. Ennek felderítésére több módszer van, nézzünk egy PowerShell-est:
PS C:\> $user = [ADSI] "LDAP://cn=János Vegetári,OU=Demó,DC=iqjb,DC=w08"
PS C:\> $user.psbase.properties
PropertyName Value Capacity Count
------------ ----- -------- -----
objectClass {top, person, o... 4 4
cn János Vegetári 4 1
sn Vegetári 4 1
telephoneNumber 2008 4 1
givenName János 4 1
distinguishedName CN=János Vegetá... 4 1
...
A fenti listában látjuk, hogy a telefonszám attribútum neve - meglepő módon – telephoneNumber.
Vagy a Windows Server 2008 esetében az Active Directory Users and Computers eszköz szerencsére már tartalmaz egy Attribute Editor fület, a korábbi Windows változatoknál az ADSIEdit eszközzel érhetjük el ugyanezt:
134 . ábra A Windows Server 2008 ADUC új Attribute Editor-a
Nézzük meg, hogyan lehet ezt a telefonszámot kiolvasni, majd átírni. Az első megoldás a „PowerShell-es”:
PS C:\> $user.telephoneNumber
1234
PS C:\> $user.telephoneNumber=2008
PS C:\> $user.setinfo()
PS C:\> $user.telephoneNumber
2008
Ebben az a furcsa, hogy a Get-Member-rel nem lehetett kiolvasni, hogy a $user-nek van telephoneNumber tulajdonsága, mégis lehet használni.
A második megoldás a hagyományos, ADSI-stílus:
PS C:\> $u.Get("telephoneNumber")
1234
PS C:\> $u.Put("telephoneNumber",9876)
PS C:\> $u.SetInfo()
PS C:\> $u.Get("telephoneNumber")
9876
A Get metódussal tudjuk az adott tulajdonságot kiolvasni, a Put-tal átírni. Egyik esetben sem szabad megfeledkezni a SetInfo-ról, ami az objektum memóriabeli reprezentációját írja be ténylegesen az AD-ba.
A Get és a Put is „rejtett” metódus, a paraméterezésük a példában látható. Az SD attribútumok LDAP nevére kell hivatkozni mindkettőnél.
Megjegyzés
Sajnos nem minden attribútum kezelhető a PowerShell-es módszerrel. Ilyen például a Company attribútum:
PS C:\> $u.company
PS C:\> $u.get("company")
Cég
Az első esetben nem kaptam semmilyen választ az attribútum kiolvasására, de get-tel mégis működött.
Mi van akkor, ha kiolvastuk egy felhasználó adatait egy változóba, majd ezután valaki egy másik gépről vagy egy másik alkalmazással módosítja a felhasználónk valamely attribútumát. Ilyenkor a getinfo metódussal lehet frissíttetni az adatokat a memóriában:
PS C:\> $u.get("company")
Egyik
PS C:\> $u.getinfo()
PS C:\> $u.get("company")
Másik
A fenti példában az első kiolvasás után az ADUC eszközzel az első get után átírtam a felhasználó Company attribútumát, és a getinfo-val ezt frissítettem a memóriában, így az új érték már a PowerShell-ből is látszik.
Az Active Directory egyik jellegzetes attribútuma az un. „multivalued property”. Ez olyan tulajdonság, ahova az értékek listáját, tömbjét tehetjük. Legtipikusabb ilyen attribútum az Exchange Server bevezetése után a felhasználók e-mail címeit tartalmazó ProxyAddresses, vagy a csoportok Member attribútuma, de erről majd külön értekezek. Marad mondjuk az other... kezdetű különböző telefonszámok tárolására szolgáló attribútumok, mint például az otherMobile vagy az otherTelephone.
Ezeket ki lehet olvasni az eddig megismert módszerekkel is, de nézzük, hogy milyen problémákkal szembesülhetünk. Ha a get metódust használom, és csak egy értéket tárol a „multivalued propery”, akkor nem egy elemű tömböt kapok, hanem sima skaláris értéket:
PS C:\> $user.get("otherMobile")
othermobil1234
PS C:\> $user.get("otherMobile").gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
Ezzel szemben, ha több értéket tárolunk, akkor már tömböt kapunk:
PS C:\> $user.getinfo()
PS C:\> $user.get("otherMobile")
othermobil2345
othermobil1234
PS C:\> $user.get("otherMobile").gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Ez nem biztos, hogy jó nekünk, mert így a szkriptünket kétfajta esetre kell felkészítenünk: külön arra az esetre, ha csak egy értéket tárolunk és külön arra az esetre is, ha többet. Ez bonyolítja a programjainkat.
Ha konzisztensen, mindig tömbként akarjuk kezelni az ilyen „multivalued property”-ket, akkor vagy használjuk a PowerShell-es stílust:
PS C:\> $user.otherMobile
othermobil1234
PS C:\> $user.otherMobile[0]
othermobil1234
PS C:\> $user.otherMobile.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PropertyValueCollection System.Collec...
Vagy használjuk a GetEx metódust:
PS C:\> $user.getex("otherMobile")
othermobil1234
PS C:\> $user.getex("otherMobile").gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Az ilyen multivalued property-k írása sem egyértelmű, hiszen több lehetőségem is van:
a meglevő értékekhez akarok egy újabbat hozzáfűzni,
a meglevő értékek helyett akarok egy újat betölteni.
Ezeket a lehetőségeket én magam is le tudom programozni a szkriptemben. Ha az első változatra van szükségem, akkor előbb kiolvasom az attribútum aktuális tartalmát egy változóba, hozzárakom az új értéket és így rakom vissza a put-tal, vagy egyszerű értékadással. Ha pedig a második változatra van szükségem, akkor egyszerűen felülírom az attribútumot az új értékkel.
Sokkal elegánsabb, ha ezt már maga az objektum tudná egy „okosabb” metódussal. Ilyen létezik, ez pedig a PutEx:
PS C:\> $user.getex("otherMobile")
othermobil1234
PS C:\> $user.putex(3,"otherMobile",@("othermobilPutEx2"));$user.setinfo()
PS C:\> $user.getex("otherMobile")
othermobilPutEx2
othermobil1234
PS C:\> $user.putex(2,"otherMobile",@("othermobilPutEx3"));$user.setinfo()
PS C:\> $user.getex("otherMobile")
othermobilPutEx3
A fenti példában a kiinduló állapotban egy mobilszámunk van. Ezután hozzáfűzök egy újabbat a putex használatával, a hozzáfűzést az első paraméterként szereplő 3-as jelzi. Fontos, hogy a hozzáfűzendő értéket tömbként kell kezelni, ezért van ott a kukac-zárójelpár! Ezután egy újabb putex-et hívok meg, immár 2-es paraméterrel, ez a felülírás művelete, ennek hatására már csak ez a legújabb mobilszám lesz az attribútumban.
Használhatom még az 1-es paramétert is, ez ekvivalens az attribútum értékeinek törlésével, vagy használhatom a 4-es paramétert, ez a paraméterként megadott elemet töröli az értékek közül:
PS C:\> $user.putex(3,"otherMobile",@("Append"));$user.setinfo()
PS C:\> $user.getex("otherMobile")
Append
othermobilPutEx3
PS C:\> $user.putex(4,"otherMobile",@("Append"));$user.setinfo()
PS C:\> $user.getex("otherMobile")
othermobilPutEx3
A fenti példában elsőként hozzáfűzök egy értéket, majd ugyanezt eltávolítom.
Van néhány attribútum, amelyek az eddig megismert módszerek egyikével sem kezelhetők:
PS C:\> $user.AccountDisabled
PS C:\> $user.get("AccountDisabled")
Exception calling "get" with "1" argument(s): "The directory property canno
t be found in the cache.
"
At line:1 char:10
+ $user.get( <<<< "AccountDisabled")
A PowerShelles szintaxis meg se nyikkan, a get meg még hibát is jelez. Ilyen esetekben használhatjuk a psbase nézeten keresztül az InvokeGet és InvokeSet metódusokat:
PS C:\> $user.psbase.invokeget("AccountDisabled")
False
PS C:\> $user.psbase.invokeset("AccountDisabled","TRUE")
PS C:\> $user.SetInfo()
PS C:\> $user.psbase.invokeget("AccountDisabled")
True