Hey, Scripting Guy!Browsing Active Directory

The Microsoft Scripting Guys

Code download available at: HeyScriptingGuy2009_02.exe(151 KB)

Contents

Giving Help
The Nitty-Gritty
Helper Functions

As you may know, the Scripting Editor and I have only recently become the new full-time Microsoft Scripting Guys, and we have spent much of our time lately sifting through the thousands of e-mails that come to the scripter@microsoft.com e-mail account. Without having actually commissioned a study, we'd venture to guess that the number one e-mail question we've seen is "Where can I get the Active Directory Browser." But guess what? We have never seen the thing. We looked and looked but could not find it.

Finally, after dropping a number of secret messages on an Internet message board, we got a coded response that, when deciphered, indicated to us that the ADSI Scriptomatic is in fact the Active Directory Browser. Well, it's not called the Active Directory Browser, and its purpose is not actually browsing Active Directory (it's helping you create ADSI scripts), so I decided to write a Windows Power­Shell script that would allow you to browse the Active Directory schema. It is called, oddly enough, BrowseActiveDirectory­Schema.ps1, and it is shown in Figure 1.

Figure 1 BrowseActiveDirectorySchema.ps1

Param($action,$class, [switch]$help)

Function GetHelp()
{
  $helpText= '
@"
 DESCRIPTION:
 NAME: BrowseActiveDirectorySchema.ps1
 Browse Active Directory Schema. Lists Classes, and properties. 

 PARAMETERS: 
 -Action <L(ist all classes), M(andatory), O(ptional), F(ind)>
 -Class class to search: user, computer, person, contact etc
 -Help displays this help topic

 SYNTAX:
 BrowseActiveDirectorySchema.ps1 -A L
 Lists the name of each class defined in the AD schema

 BrowseActiveDirectorySchema.ps1 -A M -c user
 Lists the mandatory properties of the user class

 BrowseActiveDirectorySchema.ps1 -A O -c computer
 Lists the optional properties of the computer class

 BrowseActiveDirectorySchema.ps1 -A F -c user
 Lists all Active Directory Classes that contain the word user 
 in the actual class name

 BrowseActiveDirectorySchema.ps1 -Action Find -c user
 Lists all Active Directory Classes that contain the word user 
 in the actual class name

 BrowseActiveDirectorySchema.ps1 -help
 Prints the help topic for the script
"@ #end helpText
  $helpText
} #end GetHelp
Function GetADSchema($Action, $class)
{
 $schema = [DirectoryServices.ActiveDirectory.ActiveDirectorySchema]::GetCurrentSchema()
 Switch ($Action)
  {
   "classes" { 
              $schema.FindAllClasses() | 
              Select-Object -Property Name 
             }
   "Mandatory" 
             {
              "Mandatory Properties of $class object"
              ($schema.FindClass("$class")).MandatoryProperties | 
               Format-Table -Property Name, Syntax, IsSingleValued -AutoSize 
             }
   "Optional" 
             {
              "Optional Properties of $class object"


              "This might take a few seconds ..."
              ($schema.FindClass("$class")).OptionalProperties | 
              Format-Table -Property Name, Syntax, IsSingleValued -AutoSize 
             }
   "Find" 
             {
              $schema.FindAllClasses() | 
              Where-Object { $_.name -match "$class" } |
              Select-Object -property name
             }
   DEFAULT {"$action is not a valid action." ; GetHelp ; Exit}
  }
} #end GetADSchema

Function GetAllClasses()
{
 GetAdSchema("classes")
} #end GetAllClasses

Function GetMandatory($class)
{
 GetAdSchema -action "Mandatory"  -class $class 
} #end GetMandatory

Function GetOptional($class)
{
 GetAdSchema -action "Optional"  -class $class 
} #end GetOptional

Function FindClasses($class)
{
 GetAdSchema -action "Find" -class $class
} #end FindClasses
# *** Entry Point to Script ***
if($help) { GetHelp ; Exit }

Switch ($action)
{
 "L" {GetAllClasses ; Exit}
 "M" {GetMandatory($class) ; Exit}
 "O" {GetOptional($class) ; Exit}
 "F" {FindClasses($class) ; Exit}
 DEFAULT { "$action is not a valid action." ; GetHelp ; Exit} 
}

As you can see, the script begins with the Param statement. This is used to create three command-line parameters that allow you to modify the script by running it, instead of having to edit the script to see different behavior, which is pretty helpful:

Param($action,$class, [switch]$help)

If, for example, you would like to see the mandatory properties of the user class in Active Directory Domain Services (AD DS), you specify the –action M and the –class user, as shown in Figure 2.

fig02.gif

Figure 2 Viewing the mandatory properties of the user class

You could just as easily choose the optional properties of the group class. This is the advantage of using command-line parameters: you can modify the behavior of a script at run time instead of having to go back to the drawing board. This is a best practice when writing scripts you intend to use as utilities, instead of those you will use only once.

Giving Help

The next section of the script in Figure 1 shows the GetHelp function. When you write a script that exposes command-line parameters, it is a best practice to include a function that will display help for using the script. You don't want the users of your script to have to open it and read through all of the text to figure out what it does. True, a guiding principle of script design is that a script should be readable, but if you include a help function, you make life easier for users.

The GetHelp function displays information in much the same way as the Get-Help cmdlet. Three sections of text are displayed: description, parameters, and syntax. GetHelp does two things. It creates a here-string that contains the text to display, and then it displays the contents of the variable that holds the here-string (in this case, it's $helpText).

A here-string is a Windows PowerShell construct that allows you to type information and format your output without worrying about quoting rules. Everything that you type in the here-string is treated as text. GetHelp is called when the script is run with the –help switch or if someone types an incorrect parameter. Figure 3 shows the result of calling the script with the –help switch.

fig03.gif

Figure 3 Calling the script with the–help switch

The Nitty-Gritty

Now we come to the GetAD­Schema function—the one that does most of the real work for the script in Figure 1. The here key is using the DirectoryServices.ActiveDir­ectory.ActiveDirectorySchema Microsoft .NET Framework class. By putting the class in square brackets and following that with double colons, you can access the static methods of the class (in this case, GetSchema and Get­CurrentSchema).

The GetCurrent­Schema static method returns an instance of an Active­DirectorySchema class that represents the schema to which you are currently connected. Figure 4 shows the ActiveDirectorySchema class members.

fig04.gif

After creating an instance of the DirectoryServices.ActiveDirectory.Ac­tive­Schema .NET Framework class and store the resulting schema object in the $schema variable, you must decide which action to perform. To do this, a Switch statement evaluates the value that is passed to it from the $action variable.

Based upon the condition that is met, the function will find all of the classes in AD DS, display mandatory or optional properties of a specific class, or search for a class that meets a given criteria. When you are using Switch, it is a best practice to always include a default condition.

Helper Functions

To break up some of the code and make it easier to expand the script, I've included a number of helper functions that are called based on the parameters passed to the script when it is run. Each of the helper functions calls GetADSchema and passes a different set of parameters, depending upon the value supplied for the –action parameter from the command line.

The first helper function is GetAllClasses, which calls GetADSchema and passes the word "classes". When the Switch statement in the GetAD­Schema function matches the string "classes", it calls the FindAllClasses method from the ActiveDirectorySchema class. Here is the code called by the Switch statement:

"classes" { 
              $schema.FindAllClasses() | 
              Select-Object -Property Name'

And here's the GetAllClasses function:

Function GetAllClasses()
{
GetADSchema("classes")
} #end GetAllClasses

The next helper function is GetMandatory, whose purpose is to return mandatory properties of the object specified in the –class parameter when the script is run. The GetMandatory function receives the value for $class from the command line via the –class parameter. When GetMandatory calls the GetADSchema function, it passes two parameters. Because two parameters are being passed, I consider it a best practice to specify the full parameter name for both, making the code easier to read and understand. In contrast, in the GetAllClasses function, we did not use the –action parameter name when calling the GetADSchema function. In that case, the value "classes" was passed in a positional fashion.

When the GetADSchema function is called, it uses the FindClass method from the ActiveDirectorySchema class to retrieve the class that is specified in the $class variable. It returns an instance of an ActiveDirectorySchemaClass class. The members of this class are shown in Figure 5.

fig05.gif

The GetADSchema function next queries the MandatoryProperties property of the object and pipelines the results to the Format-Table cmdlet, where it chooses the Name, Syntax, and IsSingleValued properties. The –Autosize switched parameter of the Format-Table cmdlet automatically sizes the columns to avoid cutting off property values, if possible. The code that runs when the string "Mandatory" is matched is shown here:

"Mandatory" 
       {
       "Mandatory Properties of $class object"
       ($schema.FindClass("$class")).MandatoryProperties | 
       Format-Table -Property Name, Syntax, IsSingleValued -AutoSize 
       }

And here's the GetMandatory function:

Function GetMandatory($class)
{
 GetAdSchema -action "Mandatory" -class $class 
} #end GetMandatory

Now let's look at how the Get­Optional function can display the optional properties of an AD DS class. GetOptional accepts the $class value that was received from the command line via the –class parameter, then it passes the $class value to the GetADSchema function along with the action named "Optional".

When the string "Optional" is matched in the Switch statement, a message is printed to the screen in order to explain that displaying optional properties might take a few seconds. Then, the FindClass method from the ActiveDirectorySchema class is called. The FindClass method returns an instance of an ActiveDirectory­SchemaClass class. If you were to query the Optional­Properties property of the ActiveDirectorySchema­Class class, it'd return the optional properties defined for the chosen class in AD DS.

The results are pipelined to the Format-Table cmdlet, which displays the information in the same fashion as it did for the mandatory properties. Here's that section of the code:

"Optional" 
      {
      "Optional Properties of $class object"
      "This might take a few seconds ..."
      ($schema.FindClass("$class")).OptionalProperties | 
      Format-Table -Property Name, Syntax, IsSingleValued -AutoSize 
      }

The following code is the complete GetOptional function:

Function GetOptional($class)
{
 GetAdSchema -action "Optional"  -class $class 
} #end GetOptional

The final helper function is FindClasses, which calls the GetADSchema function and passes two values. The first is the action Find, and the second is the class to locate. The purpose of this function is to help the user identify classes in the AD DS schema that might merit further exploration.

Suppose you were interested in working with e-mail. In that case, you would want to see which classes relate to e-mail, then which properties exist for an applicable class. As Figure 6 shows, first you run the script with the –action f and –class mail parameters. This returns all classes that have the string "mail" contained somewhere in the name. After you locate a class you are interested in, you explore the properties of the class by choosing the action m (for mandatory) and specifying the –c (for class) parameter, followed by the exact name of the class.

fig06.gif

Figure 6 Finding a class in the AD DS schema

When the FindClasses function calls the GetADSchema, the FindAllClasses method is called and the resulting collection of ActiveDirectorySchemaClass classes is sent over the pipeline. The Where-Object uses a regular expression pattern match to look for classes that match the value stored in the $class variable. Here's the code:

"Find" 
    {
    $schema.FindAllClasses() | 
    Where-Object { $_.name -match "$class" } |
    Select-Object -property name

And this is the FindClasses function:

Function FindClasses($class)
{
 GetAdSchema -action "Find" -class $class
} #end FindClasses

After all the helper functions are defined, you arrive at the entry point to the script. The entry point does only one thing—it examines the command line and determines which function to call. The first thing that is checked is the presence of the –help parameter. If the script is run with –h or –help, the script will call the GetHelp function to display help and then exit, as follows:

if($help) { GetHelp ; Exit }

Because the –help parameter is looked for first, its presence on the command line trumps everything else. If the –help parameter was not used, the script must evaluate the value supplied for the –action parameter—which is the default parameter. Anything typed on the command line will be interpreted as a value for –action if nothing else is used. This gives the added advantage of allowing the capture of bogus input to the script.

The Switch statement is a natural tool for evaluating the values supplied for –action. Four actions are defined, each of which call the appropriate function. The fifth condition that is defined is the default action, and it displays a message that the action is not permitted, whereupon it calls the GetHelp function.

One thing to keep in mind about the Switch statement is that it could find multiple matches. For this reason, the Exit statement is used after each call to the functions. The Switch statement code is shown here:

Switch ($action)
{
 "L" {GetAllClasses ; Exit}
 "M" {GetMandatory($class) ; Exit}
 "O" {GetOptional($class) ; Exit}
 "F" {FindClasses($class) ; Exit}
 DEFAULT { "$action is not a valid action." ; GetHelp ; Exit} 
}

Well, that's about it folks. There is no Active Directory Browser, other than the ADSI Scriptomatic. But the BrowseActive­DirectorySchema.ps1 script should serve you well for the time being. For more Active Directory scripting fun, visit the Active Directory scripting hub. And you know we're open every moment of the year on the Script Center.

Ed Wilson is a Senior Consultant at Microsoft and a well-known scripting expert. He is a Microsoft Certified Trainer who delivers a popular Windows PowerShell workshop to Microsoft Premier customers worldwide. He has written eight books including several on Windows scripting, and he has contributed to almost a dozen other books. Ed holds more than 20 industry certifications.

Craig Liebendorfer is a wordsmith and longtime Microsoft Web editor. Craig still can’t believe there’s a job that pays him to work with words every day. One of his favorite things is irreverent humor, so he should fit right in here. He considers his greatest accomplishment in life to be his magnificent daughter.