Executive Summary: You can extend PowerShell's capabilities by taking advantage of the Microsoft .NET Framework. If you work with non-Microsoft LDAP directories, one particularly useful .NET tool is the System.DirectoryServices.Protocols (S.DS.P) namespace. Here's an example of how to use the System.DirectoryServices.Protocols namespace in a PowerShell script. The PowerShell script produces a comma-delimited list of groups in a directory container, including all nested group relationships. It works with Active Directory (AD) and non-Microsoft LDAP directories. |
At face value, the request was simple. A colleague asked if we could provide him with a tool that would produce a comma-delimited list of groups in a directory container, including all nested group relationships. For example, if GroupA contains GroupB as a member and GroupB contains GroupC as a member, then show the group hierarchy as
GroupA, GroupB, GroupC
Then, for GroupB, the hierarchy would be
GroupB, GroupC
To get a better idea of what he wanted, see Figure 1. To complicate matters just a bit more, he wanted to use the script with Active Directory (AD) and a non-Microsoft LDAP directory.
Figure 1: Nested group output |
 |
Because our colleague was interested in only groups (and not user account members) and wanted to include nested group relationships, the tool had to perform both filtering and recursion operations. And because he wanted to use the tool with a non-Microsoft LDAP directory, it had to use an LDAP-compliant directory services namespace.
We decided to use the System.DirectoryServices.Protocols (S.DS.P) namespace, which was introduced in Microsoft .NET Framework 2.0. S.DS.P is one of the coolest namespaces released by the Microsoft Directory Services team. It's faster and significantly more compatible with heterogeneous LDAP directories than other directory services namespaces. Its high performance and compatibility are achieved by removing Active Directory Service Interfaces (ADSI) from the picture. The ADSI layer is a suite of COM interfaces upon which all the other directory services .NET namespaces (System.DirectoryServices, System.DirectoryServices.ActiveDirectory, and System.DirectoryServices.AccountManagement) rely.
Because System.DirectoryServices.Protocols is free of ADSI, its classes make calls directly to the Win32 classes in Wldap32.dll. To see an architectural diagram of this namespace, see MSDN's "Introduction to System.DirectoryServices.Protocols (S.DS.P)" web page.
You might be wondering why anyone would use the other directory services namespaces when S.DS.P is faster and much more compatible with heterogeneous LDAP directories. Although the ADSI layer reduces performance and compatibility, it simplifies directory services code creation from managed code and provides access to COM-based scripting languages such as VBScript.
While it's true that using the classes in S.DS.P is more difficult than using the classes in the other directory services namespaces, you'll notice code patterns that emerge once you're familiar with S.DS.P. Being able to use the code with any LDAP-compliant directory more than offsets the steeper learning curve.
Although you can't use the S.DS.P namespace with VBScript, you can use it with PowerShell. So, we decided to use PowerShell rather than managed code to create our colleague's tool. GetGroupRelationships.ps1 is the result. After we show you how to use this script, we'll tell you how it works.
How to Use the Script
To obtain GetGroupRelationships.ps1, click the Download the Code Here button at the top of the page. Place the script on the machine you intend to run it from. The script accesses a domain controller (DC) in the current domain, so make sure that the machine is joined to a domain. You don't need to modify the script before you use it.
When you run the script, you need to specify two command-line parameters: the Fully Qualified Domain Name (FQDN) of the AD or LDAP server you want to connect to and the distinguished name (DN) of the organizational unit (OU) or container you want to evaluate. For example, the command might look like
GetGroupRelationships amer.corp.fabrikam.com ou=Groups,dc=amer,dc=corp,dc=fabrikam,dc=com
After the script completes, you'll receive a list of groups. Figure 1 shows an example of what the output might look.
How the Script Works
The first thing that GetGroupRelationships.ps1 does is load the S.DS.P assembly. This .NET assembly contains all the properties and methods needed to write code for LDAP. PowerShell relies on the .NET System.Reflection.Assembly class to load this and other .NET assemblies.
There are a number of ways to use the Assembly class to load a .NET assembly. The two you'll see most often in PowerShell are using the Assembly class's Load method and using the Assembly class's LoadWithPartialName method.
Even though it works, you should steer clear of the LoadWithPartialName method. We found posts as far back as 2003 (e.g., Suzanne Cook's blog post "Avoid Partial Binds") that suggest you use Load method because the LoadWithPartialName method was deprecated with the release of the .NET Framework 2.0. Using the Load method ensures the correct version of the assembly loads, preventing any backward and forward incompatibilities.
As Listing 1 shows, we follow that advice and use the Load method to load the S.DS.P assembly.
Listing 1: Code That Loads the S.DS.P Assembly |
 |
Declaring [Void] when the code calls the Load method suppresses the output associated with loading it.