Helyi csoporttagság lekérdezése PowerShellből

Szépen halad a félév, az Intelligens Rendszerfelügyelet tantárgyunkból most vágtunk bele a felhasználó kezelésbe egy kis modellezés és folyamatkezelés alapozás után. Már túl vagyunk az első házi feladaton (infrastruktúra elemek modellezése UML és XML segítségével), és most fejeztem be a felhasználó kezeléshez a feladatokat. Egy egyszerűbb PowerShell és egy bash scriptet kell majd megírniuk a hallgatóknak. Itt akadtam bele a következő problémába: hogyan lehet lekérdezni Windows alatt, hogy egy felhasználó milyen helyi csoportoknak a tagja.

Elsőre a net paranccsal próbálkoztam, de itt csak egy csoporthoz tartozó felhasználókat lehet lekérdezni, fordítva nem. Ezt se túl szép formában:

C:\>net localgroup Users
Alias name     Users
Comment        Users are prevented from making accidental or intentional system-
wide changes and can run most applications
 
Members
 
-------------------------------------------------------------------------------
NT AUTHORITY\Authenticated Users
NT AUTHORITY\INTERACTIVE
public
The command completed successfully.

A net localgroup segítségével ki lehet listázni az összes csoportot, aztán ki lehet hozzájuk bányászni az összes tagot, de nem túl kényelmes:)

Mivel a PowerShellben könnyű .NET-es osztályokat használni, elindultam abból az irányból. Sikerült is egy tömör megoldást összerakni:

# Get the groups of a user
# This solution is nice, but only works in domains:(
$user = new-object system.security.principal.windowsidentity $login
$groups = $user.groups | % { (new-object system.security.principal.securityidentifier $_.Value).Translate("system.security.principal.ntaccount") }

A WindowsIdentity-ből példányosítunk egy új objektumot a login megadásával, lekérdezzük a csoportjait a Groups tulajdonságából, és, mivel ez SID-eket ad vissza, le kell még kérdezni a SID-hez tartozó nevet. Tökéletesen működött a teszt környezetben, azonban a saját gépemen már nem futott le, a következő hibát dobta:

New-Object : Exception calling ".ctor" with "1" argument(s): "There are currently no logon servers available to servic
the logon request.

Hát, az MSDN szerint ez sajnos csak tartományi környezetben működik:( Pedig olyan szép lett volna:)

Sok helyen láttam még WMI-t használó megoldást, de az majd csak a következő előadásokon lesz, úgyhogy más módszert kellett keresni. Maradt az ADSI.

Ez a cikk jó kis áttekintés, hogy hogyan használjuk az ADSI-t PowerShellből: ADSI Connecting to Domains/Computers and Binding to Objects (sok más cikk is van, de ez kitér arra, hogy hogyan kell helyi gépet kezelni, és nem csak Active Directoryt).

Első körben le kell kérnünk a felhasználót reprezentáló ADSI objektumot:

$user = [ADSI]("WinNT://localhost/login")

A Get-Member segítségével meg lehet nézni, hogy mit kezdhetünk a felhasználóval:

PS C:\> $user | Get-Member


TypeName: System.DirectoryServices.DirectoryEntry

Name MemberType Definition
---- ---------- ----------
AutoUnlockInterval Property System.DirectoryServices.PropertyValueCollection AutoUnlockInterval {get;set;}
BadPasswordAttempts Property System.DirectoryServices.PropertyValueCollection BadPasswordAttempts {get;set;}
Description Property System.DirectoryServices.PropertyValueCollection Description {get;set;}
FullName Property System.DirectoryServices.PropertyValueCollection FullName {get;set;}
HomeDirDrive Property System.DirectoryServices.PropertyValueCollection HomeDirDrive {get;set;}
HomeDirectory Property System.DirectoryServices.PropertyValueCollection HomeDirectory {get;set;}
LastLogin Property System.DirectoryServices.PropertyValueCollection LastLogin {get;set;}
LockoutObservationInterval Property System.DirectoryServices.PropertyValueCollection LockoutObservationInterval {g...
LoginHours Property System.DirectoryServices.PropertyValueCollection LoginHours {get;set;}
LoginScript Property System.DirectoryServices.PropertyValueCollection LoginScript {get;set;}
MaxBadPasswordsAllowed Property System.DirectoryServices.PropertyValueCollection MaxBadPasswordsAllowed {get;s...
MaxPasswordAge Property System.DirectoryServices.PropertyValueCollection MaxPasswordAge {get;set;}
MaxStorage Property System.DirectoryServices.PropertyValueCollection MaxStorage {get;set;}
MinPasswordAge Property System.DirectoryServices.PropertyValueCollection MinPasswordAge {get;set;}
MinPasswordLength Property System.DirectoryServices.PropertyValueCollection MinPasswordLength {get;set;}
Name Property System.DirectoryServices.PropertyValueCollection Name {get;set;}
objectSid Property System.DirectoryServices.PropertyValueCollection objectSid {get;set;}
Parameters Property System.DirectoryServices.PropertyValueCollection Parameters {get;set;}
PasswordAge Property System.DirectoryServices.PropertyValueCollection PasswordAge {get;set;}
PasswordExpired Property System.DirectoryServices.PropertyValueCollection PasswordExpired {get;set;}
PasswordHistoryLength Property System.DirectoryServices.PropertyValueCollection PasswordHistoryLength {get;set;}
PrimaryGroupID Property System.DirectoryServices.PropertyValueCollection PrimaryGroupID {get;set;}
Profile Property System.DirectoryServices.PropertyValueCollection Profile {get;set;}
UserFlags Property System.DirectoryServices.PropertyValueCollection UserFlags {get;set;}

Sok minden van itt, de a csoporttagság pont nem szerepel közöttük. A PSBase tulajdonságon keresztül hozzá lehet férni a teljes objektumhoz, mielőtt még a PowerShell beburkolta volna azt, így a következőket látjuk:

PS C:\> $user.PSBase | Get-Member


TypeName: System.Management.Automation.PSMemberSet

Name MemberType Definition
---- ---------- ----------
add_Disposed Method System.Void add_Disposed(EventHandler value)
Close Method System.Void Close()
CommitChanges Method System.Void CommitChanges()
CopyTo Method System.DirectoryServices.DirectoryEntry CopyTo(DirectoryEntry newParent), Syste...
CreateObjRef Method System.Runtime.Remoting.ObjRef CreateObjRef(Type requestedType)
DeleteTree Method System.Void DeleteTree()
Dispose Method System.Void Dispose()
Equals Method System.Boolean Equals(Object obj)
GetHashCode Method System.Int32 GetHashCode()
GetLifetimeService Method System.Object GetLifetimeService()
GetType Method System.Type GetType()
get_AuthenticationType Method System.DirectoryServices.AuthenticationTypes get_AuthenticationType()
get_Children Method System.DirectoryServices.DirectoryEntries get_Children()
get_Container Method System.ComponentModel.IContainer get_Container()
get_Guid Method System.Guid get_Guid()
get_Name Method System.String get_Name()
get_NativeGuid Method System.String get_NativeGuid()
get_NativeObject Method System.Object get_NativeObject()
get_ObjectSecurity Method System.DirectoryServices.ActiveDirectorySecurity get_ObjectSecurity()
get_Options Method System.DirectoryServices.DirectoryEntryConfiguration get_Options()
get_Parent Method System.DirectoryServices.DirectoryEntry get_Parent()
get_Path Method System.String get_Path()
get_Properties Method System.DirectoryServices.PropertyCollection get_Properties()
get_SchemaClassName Method System.String get_SchemaClassName()
get_SchemaEntry Method System.DirectoryServices.DirectoryEntry get_SchemaEntry()
get_Site Method System.ComponentModel.ISite get_Site()
get_UsePropertyCache Method System.Boolean get_UsePropertyCache()
get_Username Method System.String get_Username()
InitializeLifetimeService Method System.Object InitializeLifetimeService()
Invoke Method System.Object Invoke(String methodName, Params Object[] args)
InvokeGet Method System.Object InvokeGet(String propertyName)
InvokeSet Method System.Void InvokeSet(String propertyName, Params Object[] args)
MoveTo Method System.Void MoveTo(DirectoryEntry newParent), System.Void MoveTo(DirectoryEntry...
RefreshCache Method System.Void RefreshCache(), System.Void RefreshCache(String[] propertyNames)
remove_Disposed Method System.Void remove_Disposed(EventHandler value)
Rename Method System.Void Rename(String newName)
set_AuthenticationType Method System.Void set_AuthenticationType(AuthenticationTypes value)
set_ObjectSecurity Method System.Void set_ObjectSecurity(ActiveDirectorySecurity value)
set_Password Method System.Void set_Password(String value)
set_Path Method System.Void set_Path(String value)
set_Site Method System.Void set_Site(ISite value)
set_UsePropertyCache Method System.Void set_UsePropertyCache(Boolean value)
set_Username Method System.Void set_Username(String value)
ToString Method System.String ToString()
AuthenticationType Property System.DirectoryServices.AuthenticationTypes AuthenticationType {get;set;}
Children Property System.DirectoryServices.DirectoryEntries Children {get;}
Container Property System.ComponentModel.IContainer Container {get;}
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.ActiveDirectorySecurity ObjectSecurity {get;set;}
Options Property System.DirectoryServices.DirectoryEntryConfiguration Options {get;}
Parent Property System.DirectoryServices.DirectoryEntry Parent {get;}
Password Property System.String Password {set;}
Path Property System.String Path {get;set;}
Properties Property System.DirectoryServices.PropertyCollection Properties {get;}
SchemaClassName Property System.String SchemaClassName {get;}
SchemaEntry Property System.DirectoryServices.DirectoryEntry SchemaEntry {get;}
Site Property System.ComponentModel.ISite Site {get;set;}
UsePropertyCache Property System.Boolean UsePropertyCache {get;set;}
Username Property System.String Username {get;set;}

Hát ez megint egy nagy rakás metódus és tulajdonság, de Groups még mindig nincs sehol:). Utánanézve több helyen is láttam olyan mintát (pl. itt, végre újra használni kellett a franciát:), ahol az Invoke segítségével hívnak meg egyéb metódusokat. Ehhez viszont tudni kell, hogy az adott ADSI objektum mit támogat, ez az MSDN-es dokumentációból derül ki: ADSI Service Providers. Helyi gép esetén az ADSI WinNT Providert kell választani.

Ha megnézzük az objektumok leírását, akkor látszik, hogy a User az IADS, IADsUser és IADsPropertyList interfészeket támogatja. Az IADsUser-nek pedig pont van egy Groups metódusa! Most már csak meg kell hívni:

PS C:\> $user.PSBase.Invoke("Groups")



PS C:\>

Ezt adta kimenetként, hát ez nem túl sok:) Get-Member segítségével megnézve kiderül, hogy a kimenete System.__ComObject, és az ezt produkálja a ToString() meghívására. A francia linken látottaknak megfelelően itt is Reflectionnel kell meghívni egy tulajdonságának, jelen esetben a Name, lekérdezését. A teljes script ennek megfelelően a következő két sor:

# Get the groups, in which a local user is a member 
$user = [ADSI]("WinNT://localhost/login")
($user.psbase).Invoke("Groups") | % { $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null ) }

Lekérjük a csoportokat, majd az InvokeMember segítségével lekérdezzük a Name tulajdonságukat. Az InvokeMember paraméterezése itt látható, az első paraméter adja meg, hogy mit kell meghívni, a negyedik pedig, hogy milyen objektumon hajtsa végre a hívást.

Két sor kód csak, de azért pár órám elment ma vele:-)

A dolognak egyetlen szépséghibája, hogy ugyan helyi gépen nem lehet csoportot csoportba ágyazni, azonban úgynevezett Built-in security principalokat igen. Ezek közé tartozik viszont az Authenticated Users vagy az Everyone. Ha egy csoportnak tagja az Everyone, de nem tagja explicite az adott felhasználó, akkor ez a kód azt a csoportot nem találja meg. (A WindowsIdentity-s megoldás megtalálja.)

Megjegyzés: még korábban találtam egy másik módszert, a NetUserGetLocalGroups Windows API hívás is elvileg ezt csinálja. Az itt leírtaknak megfelelően ezt is meg lehetne hívni: PINVOKE or accessing WIN32 APIs, azonban ez azt csinálná, hogy futás közben egy stringként átadott C# kódot fordít le, és úgy hívja meg a natív API függvényt. Nem semmi:)

Megjegyzés2: Természetesen végül az eredeti feladatot nem adtam fel háziként, hisz ez még csak az első lépése lett volna:-)

Kategória: Tech | Közvetlen link a könyvjelzőhöz.

Hozzászólás