A Windows PowerShell rejtelmei - 5. rész
Egyelőre a PowerShell cikksorozat utolsó részéhez érkeztünk. Ebben a részben bepillantást adok a PowerShell két legfontosabb két bővítőmoduljának, az Active Directorynak és a Group Policynak a képességeibe.
Csoporttagságok rekurzív felderítése
Sok olyan feladat adódik a rendszergazdák munkája közben a felhasználókkal és a számítógép-környezetük beállításaival kapcsolatban, amelyre a grafikus rendszergazda eszközök nem adnak megoldást. Az egyik ilyen egyszerű feladat annak számbavétele, hogy egy felhasználó vajon mely csoportoknak a tagja? Nem csak egyszerűen a közvetlen tagság érdekel minket, hanem az áttételes is, azaz azok a csoportok is, amelyeknek azáltal tagja a felhasználó, hogy ezeknek valamely tagcsoportjának a tagja.
Ahhoz, hogy az Active Directory-t PowerShell cmdletekkel elérjük, importálni kell az ActiveDirectory modult. Ehhez telepíteni kell a Windows Server 2008 R2-es verzióhoz adott Remote Administration Tools csomag AD DS eszközei között található Active Directory module for Windows PowerShell képességet:
Ha esetleg maga a tartományvezérlőnk nem lenne Windows Server 2008 R2 verziójú, akkor sincs gond, a Management Gateway komponenst kell letölteni és telepíteni. Ezután a PowerShell ablakban importáljuk az Active Directory modult:
[1] PS C:\\> Import-Module activedirectory
Ezzel számos új cmdlethez jutunk. De nézzük a megoldandó feladatunkat, a csoporttagság rekurzív felderítésére a következő függvényt állítottam össze:
function get-ADMemberOfRecursive ( $adobject, $eddig = (new-object Collections.ArrayList)) { $ado = Get-ADObject $adobject -Properties memberof if($ado.memberof){$ado.memberof | foreach-object {$o = Get-ADObject $_ if($eddig -notcontains $o.distinguishedname){ [Void] $eddig.add(($o.distinguishedname)) get-ADMemberOfRecursive $o $eddig $o } } }}
A függvény igazából egy paramétert vár adobject néven, a másik csak a rekurzív meghívás miatti esetleges végtelen ciklusok elkerülését szolgálja. Hiszen lehet, hogy a felhasználó tagja A csoportnak, ami tagja B csoportnak, de B csoport meg tagja A csoportnak. A csoporttagság felderítése során tehát észre kell tudni venni, hogy az A csoportot már felderítettük, így azt még egyszer nem kell vizsgálni.
A függvény törzsében az AD objektumot újra lekérdezem a Get-ADObject cmdlettel, hiszen lehet, hogy a paraméterként átadott objektum nem tartalmazza a csoporttagság tulajdonságot, ennek a kifejezésnek a kimenete viszont igen. Ha ez az objektum tagja valamilyen csoportnak, akkor minden ilyen csoportra, ha még nem vizsgáltam, hozzáadom a DistinguishadName tulajdonságát az eddigi csoportok listájához és meghívom erre is a get-ADMemberOfGroup függvényt, de már az eddigi vizsgált csoportok listájával bővítve. Ezután a vizsgált csoport objektumát kiadom a kimenetre. Nézzük ennek futását:
[2] PS C:\\> get-ADMemberOfRecursive (Get-ADUser vj) DistinguishedName Name ObjectClass ObjectGUID - - - - CN=temp,OU=Demó,... temp group 7cdd95c2-3ee1-4 CN=Ön,OU=Demó,DC... Ön group ef75fe02-8168-4
A VJ nevű felhasználóm tagja az Ön nevű csoportnak, ami pedig a temp csoportnak a tagja, az Ön csoportnak pedig tagja a temp. Ennek ellenére a függvényem nem futott végtelen ciklusba, hanem ezt a két csoportot egyszer kiadta a kimenetére. A következő futtatásban a tipikus csoporttagság példája látható. A soosbence felhasználó tagja a UserGroupnak, ami viszont tagja a ResourceGroupnak:
[3] PS C:\\> get-ADMemberOfRecursive (Get-ADUser soosbence) DistinguishedName Name ObjectClass ObjectGUID - - - - CN=ResourceGroup... ResourceGroup group 3542d4c7-f045-4 CN=UserGroup,OU=... UserGroup group 52fe0789-5c76-4
Szervezeti diagram AD információk alapján
A következő, orgchart.ps1 szkriptfájl egy kiválasztott felhasználót a manager tulajdonsága, azaz az ő főnökét jelző AD információ alapján behelyezi a szervezeti hierarchiába:
param ($user) $user = Get-ADUser -Filter {samaccountname -eq $user} -Properties manager function get-topmanager ($u, $szint=0) { if($u.manager) { $főnök = Get-ADUser $u.manager -Properties manager if($u.distinguishedname -ne $főnök.distinguishedname){ get-topmanager $főnök ($szint-1)} } $u | Add-Member -Member NoteProperty -Name szint -Val $szint -Pass -force } function get-beosztrecursive ($u, $sz = 0){ Get-ADUser -Filter {manager -eq $u} -properties manager | ?{$_.distinguishedname -ne $u.distinguishedname} | %{if($_){ $_ | Add-Member -Member NoteProperty -Name szint -Val $sz -Pass -for get-beosztrecursive $_ ($sz+1)}} } $tops = @(Get-TopManager $user) $offset = -$tops[0].szint $tops | %{$_.szint += $offset} $beosztottak = get-beosztrecursive $user ($offset+1) $tops, $beosztottak | %{$_} | ?{$_} | %{ if($_.distinguishedname -eq $user.distinguishedname){$s = \"*\"} else{$s=\" \"} Write-Host ($s + (\"|`t\")*($_.szint) + \"+ \" + $_.name) }
A szkriptben két segédfüggvényt használok: az egyik a get-topmanager, ami a paramétereként megadott felhasználóból kiindulva végigszalad a ranglétrán, és megadja a feletteseket egészen addig, amíg már nincs további főnök, vagy esetleg valaki saját maga főnöke. Minden ilyen főnökhöz egy szint tulajdonságot is hozzárendelek, hogy tudjam, milyen mélységben kell majd ábrázolni a felhasználómat. A másik segédfüggvény rekurzívan kilistázza az adott felhasználó beosztottjait, azaz nem csak a közvetleneket, hanem a beosztottak beosztottjait is, és így tovább. Hogy szép formázott lehessen az eredmény, ehhez is hozzáveszem, hogy az adott beosztott hányadik szintű.
Maga a szkript ezek után nem bonyolult. Elsőként veszem a feletteseket a $tops változóba. Az első elem, azaz a top manager szintjének negáltja adja meg, hogy mennyivel eltolva kell majd az egész hierarchiát megjelenítenem, és gyorsan el is tolom a szinteket, hogy a top manager szintje legyen a kiinduló 0. Ezután veszem rekurzívan a beosztottakat, a szint itt eggyel nagyobbról indul, mint ahova a felettesek szintjeivel eljutottam. A végén az összes felettest és beosztottat kiíratom, az éppen vizsgált felhasználónál egy * karakterrel jelzem ezt, egyébként a szintnek megfelelő darabszámú függőleges vonal és tabulátor karaktert íratok ki, majd egy jelet, majd a felhasználó nevét. Nézzük a kimenetet a Nagy Főnök esetében:
[4] PS C:\\> C:\\munka\\orgchart.ps1 nf *+ Nagy Főnök | + Al1 Főnök | | + Kis1 Főnök | | + Kis2 Főnök | | | + Gál András | | | + Budai Edina | | | + Dolák Fanni | + Al2 Főnök | + Al3 Főnök | | + Bornai Viktor | | + Balogh Mária | | + Budai András
Látható, hogy a teljes hierarchia látható. Nézzük csak azt az ábrát, amiben a Kis2 Főnök nevű felhasználóra vagyok kíváncsi:
[5] PS C:\\> C:\\munka\\orgchart.ps1 kis2f + Nagy Főnök | + Al1 Főnök *| | + Kis2 Főnök | | | + Gál András | | | + Budai Edina | | | + Dolák Fanni
Látható, hogy itt már csak az ő al-ága látszik teljesen kibontva, a főnökei irányában csak egy út látszik.
Group Policy objektumok különbségeinek felderítése
A rendszergazdák másik gyakori fejfájása a Group Policy objektumok kezelése. Ha készítünk két GPO-t, akkor elég nehéz az ezekben található beállításokat egymás mellett áttekinteni. Mielőtt az ezen a területen segíteni képes PowerShell függvényt bemutatnám, nézzük először is, hogyan kezelhetjük PowerShell segítségével a GPO-kat. Elsőként importálni kell a GroupPolicy modult:
[6] PS C:\\> Import-Module grouppolicy
Ez a bővítmény – többek között – tartalmaz egy Get-GPOReport cmdletet, amellyel a GPO objektumok tartalmát tudjuk megnézni. Miután én nem csak nézegetni, hanem feldolgozni akarom ezeket az adatokat, ezért érdemes XML formátumú kimenetet kérni. Egy konkrét GPO beolvasása így történhet:
[7] PS C:\\> $fgpo = [xml] (Get-GPOReport -Name FőnökiGépek -ReportType XML)
Ezzel az előálló XML formátumú kimenetet tényleges XML adattípusként töltöm be a $fgpo változóba. Ez azért jó, mert ennek a változónak a tulajdonságain keresztül férek hozzá az adattartalomhoz:
[8] PS C:\\> $fgpo xml GPO - - version=\"1.0\" encoding=\"utf-16\" GPO
Ez még csak a \"fejléc\", de mehetünk mélyebbre a GPO tulajdonságon keresztül:
[9] PS C:\\> $fgpo.gpo xsi : http://www.w3.org/2001/XMLSchema-instance xsd : http://www.w3.org/2001/XMLSchema xmlns : http://www.microsoft.com/GroupPolicy/Settings Identifier : Identifier Name : FőnökiGépek IncludeComments : true CreatedTime : 2010-05-20T19:47:56 ModifiedTime : 2010-05-20T19:56:59 ReadTime : 2010-05-22T20:40:52.1631171Z SecurityDescriptor : SecurityDescriptor FilterDataAvailable : true Computer : Computer User : User
Mehetünk tovább a Computer ágban:
[10] PS C:\\> $fgpo.gpo.Computer VersionDirectory VersionSysvol Enabled ExtensionData - - - - 14 14 true {Security, Publ
És ennek ExtesionData ágán tovább:
[11] PS C:\\> $fgpo.gpo.Computer.ExtensionData Extension Name - - Extension Security Extension Public Key Extension Registry
Látható, hogy több elkülönülő részben tárolódnak a GPO beállításai. A legtöbb beállítást általában a Registry ágban, azaz a grafikus felületen az Administrative Templates ágban találjuk. Menjünk ebbe az irányba tovább és tovább:
[12] PS C:\\> $fgpo.gpo.Computer.ExtensionData[2] Extension Name - - Extension Registry [13] PS C:\\> $fgpo.gpo.Computer.ExtensionData[2].extension q3 type Policy RegistrySetting - - - - http://www.micro... q3:RegistrySettings {Restricts the U... {q3:RegistrySet [14] PS C:\\> $fgpo.gpo.Computer.ExtensionData[2].extension.policy Name : Restricts the UI language Windows uses for all logged users State : Enabled Explain : This is a setting for computers with more than one UI language installed. If you enable this setting the UI language of Windows menus and dialogs language for systems with more than one language is restricted to the specific language. If the specified language is not installed on the target computer or the policy is disabled, the language selection defaults to the language selected by the local administrator. Supported : At least Windows Vista Category : Control Panel/Regional and Language Options DropDownList : DropDownList
És itt jutottam el a tényleges beállításokhoz, ahol is a részletes leírástól kezdve minden hasznos információhoz hozzájuthatok. Ez olyan sok, hogy itt a cikkben jelentősen megvágtam. Ennek ismeretében nézzük, hogy hogyan lehet két GPO objektumot összehasonlítani a compare-gpos.ps1 szkriptem segítségével:
param ($gpo1, $gpo2, $hive = \"User\") function get-registrypolicy ($gpo, $h) { (([xml] (Get-GPOReport -name $gpo -ReportType XML -ErrorAction stop )).GPO.$h.ExtensionData | ?{$_.name -eq \"Registry\"}).exten sion.policy } $pol1 = get-registrypolicy $gpo1 $hive $pol2 = get-registrypolicy $gpo2 $hive if($pol1 -and $pol2) { Compare-Object $pol1 $pol2 -IncludeEqual -Property name, state } elseif($pol1){$pol1 | select-object name, state, @{n=\"SideIndicator\";e={\"<=\"}}} else{$pol2 | select-object name, state, @{n=\"SideIndicator\";e={\"=>\"}}}
A szkript két GPO nevet vár, illetve, hogy mely ágakat hasonlítsa össze (User vagy Computer). A belsejében definiálok egy segédfüggvényt, amely a GPO azon részeit szedi csak ki, amely a registry, azaz Administrative Templates ágon belül van. Ennek segítségével beolvasom mindkét GPO beállításait, majd a korábbi cikkekben már látott Compare-Object cmdlettel összehasonlítom ezeket, de csak akkor, ha mindkét GPO tartalmaz ezen az ágon beállításokat. Ha nem, akkor én emulálok hasonló kimenetet, mint amit a Compare-Object adna, mert a \"gyári\" cmdlet üres objektum összehasonlítását nem tudja megtenni. Hogy még áttekinthetőbb legyen a kimenet, nézzük rögtön a grafikus rácsban a TitkárnőiGépek és a FőnökiGépek GPO-k összehasonlítását:
compare-gpos.ps1 TitkárnőiGépek FőnökiGépek Computer | Out-GridView
A fenti rácsban láthatjuk, hogy az első két beállítás mindkét GPO-ban megtalálható, míg a 3-4. csak a FőnökiGépekben, és az 5-6. csak a TitkárnőiGépekben.
Végszó
Természetesen mindhárom kis példám továbbfejleszthető, a csoporttagság rekurzív kifejtésére alapozottan könnyen fel lehet deríteni, hogy vajon egy felhasználónknak a fájlkiszolgálón hol van írási joga. Természetesen nem csak a direkt jogmegadást keressük ilyenkor, hanem hogy valamely csoporttagsága által hol vannak jogai. A szervezeti diagram példát abba az irányba lehet továbbfejleszteni, hogy ne csak megjelenítésben lehessen felhasználni, hanem a kimenet maga felhasználói objektumok halmaza legyen. Így egyszerűen meg lehetne oldani például azt, hogy állítsuk át minden olyan felhasználó csoporttagságát, aki Kovács Béla által felügyelt szervezeti egységben dolgozik.
A Group Policy példát úgy lehetne fokozni, hogy egyrészt ne csak a registry-alapú beállítások között vizsgálódjon, hanem más részekben is. Továbbá hasznos lenne az is, ha egy \"etalon\" GPO-hoz képest valamely GPO-ban azzal ellentétes beállítások vannak, akkor azokat igazítsa az etalonhoz. Számtalan ilyen példát lehetne még hozni. Én párat összegyűjtöttem a http://www.iqjb.hu/PSkonzultacio.php oldalra, ha érdekli a tisztelt olvasót ilyen jellegű problémák megoldása PowerShell segítségével, keressen bizalommal!
Soós Tibor (PowerShell MVP, MCT), IQSOFT – John Bryce Oktatóközpont