ISA Scripting Without “Scripting ISA”

Introduction

ISA Server 2006 SP1 includes a fix released in September 2007 that allowed ISA 2006 arrays to use multicast and IGMP-aware Integrated NLB. Implementing this change is a four-part process:

1. Install the update (or preferably; SP1)

2. Run the nlbclear utlity

3. Run the script with appropriate command-line options

4. Reconfigure Integrated NLB

In order to change the ISA 2006 Integrated NLB so that it can support multicast or IGMP, we need to change the storage schema. Since CSS is based on Active Directory Application Mode (ADAM), any time you change the schema you have to do so at the server which holds the Flexible Single Master of Operations (FSMO) Schema Master Role. The problem encountered by most folks was that although it’s a general truth that the first-installed CSS is the Schema Master, most folks can’t easily determine if this is actually true in their particular deployment. Server failures and replacements can often leave the CSS replication group without a Schema Master. This state won’t normally affect CSS operation, since ISA doesn’t make schema updates as part of normal ISA operations, but when you need to make the changes required by this particular update, or if another update or service pack requires a schema change, the installation will likely fail for lack of a responsive Schema Master.

Since the ISA admin needs a simple way to determine which CSS owns the Schema Master role, I went on a search for such a tool. One TechNet article offers a method using an optional ADAM Schema MMC snap-in, but because I’m a command-line and script geek (I often miss my seriously-modded Kaypro-4); I wanted something a bit less GUI-dependent. Unfortunately, none of the provided ADAM management tools allow you to simply query the FSMO roles without issuing a “transfer” or “seize” command. Unless a FSMO master is missing from the CSS set, why reassign a role just to find out which server currently owns it?

 

Some Background

To determine the name and port for the FSMO Schema Master server, we have to gather some data from ADAM directly, since ISA Storage mechanisms don’t provide this information. Because AD is effectively a relational database, the data we need is stored in multiple locations, each of which has a relationship to the other in at least one way. Lucky for us, the relationships between the AD objects are well-defined:

1. Each object in AD has a predefined set of data called “attributes” which are used to define that object’s configuration and its relationship to other AD objects.

2. AD includes multiple “contexts” (schema, configuration, etc.), or primary access points to the data it holds. In order to use the objects within each context, you must first determine its Distinguished Name (DN) of that context.

3. AD attributes which provide references to other objects frequently do so using the DN of the referenced object. This format not only provides the object’s relationship to other AD objects, but also the type of objects represented by the relationships. For instance, the DN for the fSMORoleOwner attribute would be provided as: CN=NTDS Settings,CN=CSS-01$ISASTGCTRL,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,CN={6EFCA912-D6BA-4D0B-836F-2AB6A5236064} . Although this is meaningful to AD, it’s gobbledygook to many humans. Luckily, we can use this information to gain access to the data we want.

4. Access to AD objects is gained through the use of the LDAP://ServerName:Port/DN syntax in a call to the Windows Scripting GetObject() function. Since each AD object is represented by a unique DN, and since each object we’re interested in is represented by the DN contained in another object’s attribute, we need only determine the relationship of one object to another.

5. The relationship we use in this script is:

· The “RootDSE” container represents the base-level AD object which is primarily used to provide structural information about the database. It includes an attribute named “schemaNamingContext”, which contains a DN representing the Schema definition table for this AD instance. By calling GetObject( “LDAP://ServerName:Port/RootDSE” ) , we can obtain a reference to this object and thus acquire the Schema object DN from the schemaNamingContext attribute.

· The “Schema” container is the AD object which describes the organizational requirements of the database. Among a great many other things, it includes an attribute named fSMOMaster which provides the DN for the computer charged with maintaining control over the schema structure. This DN is a reference to an object which is stored in the Configuration portion of AD. Specifically, this object represents an NT Directory Store (NTDS) object within the related Computer container.

· The “NTDS” container is an object referenced by the fSMOMaster DN. It contains an attribute named “msDS-PortLDAP”, which defines the port used by that server to provide CSS services for the ISA Enterprise. It also contains an attribute named “msDS-PortSSL”, which defines the SSL port used. Since this port is only used in or workgroup-deployed CSS and replication is not supported in workgroup -deployed CSS, “there can be only one” Schema master in workgroup scenarios.

· The “Computer” container is a parent of (contains) the NTDS container which we used to obtain the listening ports. The Computer container includes an attribute named “DnsHostName” which provides the fully-qualified name of this computer.

Now that we have an understanding of the data locations and relationships, we can describe how to obtain them in code. You may notice that we don’t make use of this information until the GetFsmoSchemaMaster function, but we use all of it there.  MSKB 235617 provides a VBScript to query the various Schema Master roles in a domain-based AD, but aside from querying data which doesn’t exist in ADAM such as RIDManager, Primary Domain Controller (PDC), etc., it lacks the ability to tell you what port the ADAM instance is using.

 

The Script

Scripting tends to be a very personal exercise, so I wrote two versions of the tool; one for VBScript and one for Jscript. I prefer Jscript for its C-based syntax and behavior, but many folks prefer VBScript for its English-like syntax. What you should notice about these scripts is that I don’t use any ISA COM objects, collections, methods or properties. Instead, I use well-defined Active Directory Services Interface (ADSI) mechanisms that ADAM inherits from Windows domain-based AD. Where the languages allow, I’ve also tried to keep the statement formatting as similar as possible so that the syntactical differences are less confusing.

Since it’s a programming best practice to avoid global variables whenever possible, I generally like to start things off in a “main” function and use it as the only statement in the script body. You may also note that I’m a proponent of “CamelBack notation”, or mixing upper- and lowercase letters in variable and function names to improve readability. I also like to prefix my variables so that their intended usage is clearer. For instance, “sz” refers to a “string” variable; “o” is an object and so on. I’ve intentionally omitted error-handling in these script. While I’m usually a stickler for resilient code regardless of the language or environment, explanatory code tends to benefit from brevity. You’re welcome (and encouraged) to add your own error-handling code to these scripts.

 

The Script Body

The body of the script has one task– it starts the script by calling the main function.

Jscript

main();

VBScript:

main()

The Main Function

main has three jobs:

· Determine the connection information for the FSMO Schema Master

· Report those findings to the user

main performs four actions:

1. Define a variable named szServer and populate it with the value returned from a call to the CheckArguments function

2. Define a variable named szPath and populate it by combining three pieces of data:

a. A string constant “LDAP://”

b. The szServer, variable

c. A string constant “:2171”

3. Define a variable named szFsmoMaster and populate it with the value returned from a call to the GetFsmoSchemaMaster function with the szPath variable

4. Combine szFsmoMaster with a static string message and send it to the console using the WScript.Echo function provided by the scripting engine

Jscript

function main()

{

    var szServer = CheckArguments();

    var szPath = "LDAP://" + szServer + ":2171/";

    var szFsmoMaster = GetFsmoSchemaMaster( szPath );

    WScript.Echo( "The FSMO Schema Master connection is \r\n\t" + szFsmoMaster );

}

VBScript

Function main()

    Dim szServer: szServer = CheckArguments()

    Dim szPath: szPath = "LDAP://" & szServer & ":2171/"

    Dim szFsmoMaster: szFsmoMaster = GetFsmoSchemaMaster( szPath )

    WScript.Echo( "The FSMO Schema Master connection is " & vbCrLf & vbTab & szFsmoMaster )

     

End Function

 

The CheckArguments Function

CheckArguments has two jobs:

· Verify the command-line option provide by the user

· Provide a usable server name to the main function

CheckArguments performing seven actions:

1. Define a variable named oNamed and populate it with the WScript.Arguments.Namedcollection provided by the scripting engine

2. Determine if the user passed the /server option on the command-line

3. Return a static string of “localhost” if the user provided no command-line options

4. If the user did provide a /server option, define a variable named szServer and populate it from the oNamed.Item object named server

5. Test the szServer variablefor valid content

6. If szServer is empty, assign it a value of “localhost”

7. Return the szServer variable contents to the main function

Jscript

function CheckArguments()

{

    /*

       we need one argument to function: /server;

       can be the simple or qualified name of the CSS

    */

    var oNamed = WScript.Arguments.Named;

    if( 0 == oNamed.length ||

        !oNamed.Exists( "server" ) )

    {

        return "localhost";

    }

    var szServer = oNamed.Item( "server" );

    if( "undefined" == typeof( szServer ) )

    {

        szServer = "localhost";

    }

   

    return szServer;

}

VBScript

Function CheckArguments()

   'we need one argument to function: /server

   'can be the simple or qualified name of the CSS

    Dim oNamed: Set oNamed = WScript.Arguments.Named

    If 0 = oNamed.length or _

        Not( oNamed.Exists( "server" ) ) Then

      CheckArguments = "localhost"

      Exit Function

    End If

    Dim szServer: szServer = oNamed.Item( "server" )

    If "" = szServer Then

        szServer = "localhost"

    End If

    CheckArguments = szServer

End Function

 

The GetFsmoSchemaMaster Function

Here’s the real meat of the script.

GetFsmoSchemaMaster has four jobs:

· Determine the fully-qualified name of the FSMO Schema Master computer

· Determine the listening port used by the FSMO Schema Master computer

· Combine these into a usable connection string

· Return the connection string to the main function

GetFsmoSchemaMaster performs eight actions:

1. Define a variable named oRootDSE and populate it with an ADSI object obtained from a call to GetObject() using the szPath variable from main

2. Define a variable named oSchema and populate it with an ADSI object obtained from a call to GetObject() using a combination of the szPath from main and the oRoot object’s SchemaNamingContext attribute.

3. Define a variable named oNTDS and populate it with an ADSI object obtained from a call to GetObject() using a combination of the szPath variable from main and the oSchema object’s fSMORoleOwner attribute

4. Define a variable named oComputer and populate it with an ADSI object obtained from a call to GetObject() using the oNTDS.Parent property

5. Define a variable named szServer and populate it with the oComputer.DnshostName attribute

6. Define a variable named szPort and populate it with the oNTDS object’s msDS-PortLDAP attribute

Note: because the msDS-PortLDAP is actually stored in ADAM as a number, it should be converted to a string for syntactical consistency

7. Define a variable named szFsmoOwnerConnection and populate it with the combination of szServer, “:” and szPort

8. Return the contents of the szFsmoOwnerConnection variable to the main function

Jscript

function GetFsmoSchemaMaster( szPath )

{

    var oRootDSE = GetObject( szPath + "RootDSE" );

    var oSchema = GetObject( szPath + oRootDSE.Get( "schemaNamingContext" ) );

    var oNTDS = GetObject( szPath + oSchema.Get( "fSMORoleOwner" ) );

    var oComputer = GetObject( oNTDS.Parent );

    var szServer = oComputer.DnsHostName;

    var szPort = oNTDS.Get( "msDS-PortLDAP" ).toString();

    var szFsmoOwnerConnection = szServer + ":" + szPort;

    return szFsmoOwnerConnection;

}

VBScript

Function GetFsmoSchemaMaster( szPath )

   

    Dim oRootDSE: Set oRootDSE = GetObject( szPath & "RootDSE" )

    Dim oSchema: Set oSchema = GetObject( szPath & oRootDSE.Get( "schemaNamingContext" ) )

    Dim oNTDS: Set oNTDS = GetObject( szPath + oSchema.Get( "fSMORoleOwner" ) )

    Dim oComputer: Set oComputer = GetObject( oNTDS.Parent )

    Dim szServer: szServer = oComputer.DnsHostName

    Dim szPort: szPort = CSTR( oNTDS.Get( "msDS-PortLDAP" ) )

    Dim szFsmoOwnerConnection: szFsmoOwnerConnection = szServer & ":" & szPort

    GetFsmoSchemaMaster = szFsmoOwnerConnection

End Function

 

Wrap-Up

To create a functioning script from these individual functions, you would:

1. Use notepad (or your favorite script editor) to create FindCssFsmo.js or FindCssFsmo.vbs (or both)

2. Copy the relevant code from each section grey box to the new file (order of the functions is not important)

3. Save the file

..or you could just download them from http://isatools.org

You would then run it on any CSS in your ISA Enterprise as

(Jscript) cscript findcssfsmo.js [/server:NameOfCss]

(VBscript) cscript findcssfsmo.vbs [/server:NameOfCss]

Note: the brackets in the examples indicate that the /server option is; well, optional. The brackets must not be included when you run the script. If you supply the /server option, the script will attempt to connect to “NameOfCss:2171”. If you omit it, it will try to connect to “localhost:2171”. If you use the /server option without any data, it will connect to “localhost:2171”. If this behavior is not what you expect, you might want to review the CheckArguments function description again.

If all is well, you should receive output similar to:

The FSMO Schema Master connection is

        cssname.domain.tld:2171

..As a side note – while the motivation for this script was ISA 2006-based, it will also function equally well on an ISA 2004 CSS because it’s strictly ADSI-based; there are no dependencies on ISA COM at all.

That’s it – all this discussion for less than 50 lines of script and all it does is tell you which CSS server holds the Schema Master role. Aren’t you glad you stayed for the credits?

Jim Harrison, Program Manager, ISA SE

 

Reviewers

Yuri Diogenes, Support Engineer, CSS Security

Nathan Bigman, Content Publishing Manager