Fighting it out with ADSI

I have successfully installed Microsoft Exchange 2013 in my test environment. But I will describe that adventure later.

Rather what spiked my interest today is ADSI, Active Directory Service Interfaces.

Microsoft describes ADSI thus:

"Active Directory Service Interfaces (ADSI) is a set of COM interfaces used to access the features of directory services from different network providers. ADSI is used in a distributed computing environment to present a single set of directory service interfaces for managing network resources. Administrators and developers can use ADSI services to enumerate and manage the resources in a directory service, no matter which network environment contains the resource."

In other words, ADSI is Your Plastic Pal Who's Fun to Be With.

But what ADSI can do is help a system administrator access directory information from any language that has access to the .NET framework, including PowerShell, using a few useful classes like System.DirectoryServices.DirectorySearcher and System.DirectoryServices.DirectoryEntry.

Here are a few somewhat useful examples.

DirectorySearcher

$searcher = new-object system.directoryservices.directorysearcher
This creates a new object searcher of the type directorysearcher. Note that the type has several constructors which can make the next two lines unnecessary if the object is only needed once.

$searcher.searchroot = [ADSI]"LDAP://dc=ludwig,dc=example,dc=com"
The field searchroot is an ADSI object which can be created on-the-fly by casting a string containing an LDAP URL. In this case it's the LDAP URL for the domain ludwig.example.com. Note that this is somewhat case-sensitive. "ADSI" is upper case, as is "LDAP". "dc=" means "domain component=".
The searchroot field can also be initialised as the first or only parameter of the constructor. Note the second-last example.

$searcher.filter = "(objectclass=computer)"
This field defines what type of object is to be searched for. It's a string. The format appears to be "(objectlass=whatever type of object you want)". Apparently an empty string is automatically changed to "(objectclass=*)".
The filter field can also be initialised as the second or only parameter of the constructor. Note the last example.

$searcher.findall()
This method simply displays all matching objects.

$searcher = new-object system.directoryservices.directorysearcher([ADSI]"LDAP://foobar")
Creates a directorysearcher object with searchroot set to the ADSI object created on-the-fly.

$searcher = new-object system.directoryservices.directorysearcher("(foobar)")
Creates a directorysearcher object with filter set to the given string.

DirectoryEntry

$group = [ADSI]"WinNT://client1/administrators,group"
This creates a new object group of the type directoryentry based on a local directory entry on the computer client1, namely the group "administrators" on that computer. WinNT URLs look simpler than LDAP URLs. I don't know why casting to ADSI is enough to create a specific type of object. (The ",group" at the end of the URL is possibly unnecessary but who knows.)

$members = $group.psbase.invoke("Members")
I don't know what psbase is, but it contains a method invoke() which, in a coincidence that reminds me of Groucho Marx, invokes methods. Apparently there is a method Members which returns the members the directory entry has, presumably when it is a group. Note that the newly-created object members is now completely unusable and has to be cast into an array first.

$members = @($group.psbase.invoke("Members"))
This returns the same contents as the call above but casts the result into an array members.

$members[0].gettype().invokemember("Name", "GetProperty", $null, $members[0], $null)
Now it gets complicated. The class to which each individual object in the members array belongs has a static (class) method invokemember() which apparently takes four parameters. The first parameter is the name of the field (here "Name"), the second maybe a method (here "GetProperty"), the third and fifth are null and the fourth is the object on which to unleash this monster.

$members[0].gettype().invokemember("Name", "GetProperty", $null, $members[1], $null)
Note that since $members[0] is here only used to get the type of the objects in the members array it doesn't matter that the static method is called on the type of object number 0 while addressing object number 1. It's the same method on both objects but different data in the fields. Note that those are COM objects and rest assured that you don't want to deal with them more directly.

$user = [ADSI]"WinNT://ludwig/superman"
This creates another directoryentry, this time named user. The user in question is a domain user ludwig\superman.

$group.psbase.invoke("Add", $user.path)
This adds a user defined in the string user.path to the local group represented by the object group.

$group.psbase.invoke("Remove", $user.path)
And this removes that same user.

$group = [ADSI]"LDAP://cn=domain admins,cn=users,dc=ludwig,dc=example,dc=com"
And yes, this can be done with an LDAP (i.e. a domain-based) group as well. However, since the LDAP URLs to those groups are not as simple as the WinNT URLs to local groups, it is probably best to play with directorysearcher first, see above.

Anyway, this is how you can be finding it out with ADSI.

 © Andrew Brehm 2016