Working with Classes

Now that you have a basic understanding of the schema, let's look at classes in Active Directory in more detail. As mentioned, the classSchema class is a template for creating objects in Active Directory. Instances of the classSchema class exist for each class. Table 9-4 lists the attributes of the classSchema class.

classSchema Attribute Mandatory or Optional Syntax Description

auxiliaryClass

Optional

OID Multivalued

List of auxiliary classes to beincluded with this class.

classDisplayName

Optional

DirectoryString Multivalued

Not used. It appears that this attribute is erroneously included in the classSchema class. This attribute is intended for the displaySpecifier class.

cn

Mandatory

DirectoryString

The common name for this class.

defaultHidingValue

Optional

Boolean

If True, new objects of this class will be hidden from view using the showInAdvanced- ViewOnly attribute.

defaultObjectCategory

Mandatory

DN

The DN of the most appropriate superclasses of this class.

defaultSecurity-Descriptor

Optional

DirectoryString

The default security descriptor for objects of this class.

governsID

Mandatory

OID

The OID for this class. Must be unique in the schema.

isDefunct

Optional

Boolean

If True, the class has been disabled.

lDAPDisplayName

Optional

DirectoryString

The LDAP display name for this class.

mayContain

Optional

OID Multivalued

List of attribute names that can be contained within this class.

mustContain

Optional

OID Multivalued

List of attribute names that must be contained within this class.

objectClassCategory

Mandatory

Enumeration

Either 1, 2, or 3, for structural, abstract, or auxiliary class type.

possSuperiors

Optional

OID Multivalued

List of classes that can contain this class.

rDNAttID

Optional

OID

The attribute is used to name objects of this class.

schemaFlagsEx

Optional

Integer

Internal use.

schemaIDGUID

Mandatory

OctetString

The GUID assigned by Active Directory to this class.

subClassOf

Mandatory

OID

The class name of the immediate superclass from which this class derives.

systemAuxiliaryClass

Optional

OID Multivalued

List of auxiliary classes that this class includes. Cannot be modified.

systemMayContain

Optional

OID Multivalued

List of attributes this class can contain. Cannot be modified.

systemMustContain

Optional

OID Multivalued

List of attributes this class must contain. Cannot be modified.

systemOnly

Optional

Boolean

If True, objects of this class cannot be modified.

systemPossSuperiors

Optional

OID Multivalued

List of classes that can contain this class. Cannot be modified.

Table 9-4 Attributes of the classSchema class.

Class Inheritance

Active Directory is designed to represent network resources and data for a company or an organization, and the resources or data often share common attributes. Instead of defining the same attributes in each class for a particular type, Active Directory allows one class to define the common characteristics and other classes to inherit those characteristics. By using inheritance, developers can make classes very specific and avoid redundant definitions.

When a new class is defined in the schema, a parent class must be specified. The parent class is also known as a superclass, and the inheriting, or child, class is known as the subclass. Through this inheritance model, attributes specified in the parent class are available to instances of the child class.

A common example of inheritance is the user class. In Active Directory, objects created from the user class represent a network account. The user class contains attributes such as the password, home directory location, and other pieces of information useful to operating on an Active Directory–enabled network. What's notable about the user class is that it does not include attributes for the user's name, e-mail address, and other personal information. However, the organizationalPerson class defines this information and includes many other attributes related to a person within a company or organization. Since the user class inherits from the organizationalPerson class, the organizationalPerson attributes are available to the user class. Figure 9-5 shows this inheritance.

Figure 9-5 Class inheritance in Active Directory.

The inheritance model shown in this example is known as single inheritance. While the user class contains attributes defined in several classes, it inherits only from its immediate parent, organizationalPerson. Single inheritance contrasts with multiple inheritance (a feature available in the C++ language but not supported in Active Directory), in which a new class inherits directly from more than one parent class to form a sort of concatenated class. Personally, I believe that multiple inheritance in C++ is pure evil and should be avoided because it can exponentially increase the complexity of your program. However, multiple inheritance has some benefits, and Active Directory provides similar functionally using auxiliary classes, which I'll discuss later in this chapter in the section "Class Categories."

Which class a new class inherits from is set by using the subClassOf attribute of the classSchema object that represents the new class. The subClassOf attribute is a single-valued attribute, and only the immediate parent class is specified.

What attributes does a child class inherit from its parent class? The child class inherits all the possible attributes of the parent class, both optional and mandatory. When the same attribute is specified in both the child class and one of the parent classes, only one instance exists in objects of the child class, although the Active Directory Schema snap-in will list each parent class that includes it. A slight wrinkle to this rule is when the child class specifies that a particular attribute is optional but one of the parent classes specifies that the same attribute is required. Since at least one of the parent classes require the attribute, it is a required attribute in all child classes. For example, the top class specifies the cn attribute as optional because not all objects that inherit from top need that attribute. However, the person class specifies that cn is required because it is used as the naming attribute (rDNAttID) for the person class. All child classes that inherit from the person class will be required to contain the cn attribute.

Additionally, the child class inherits the list of possible superior classes from the parent class. A possible superior class is the class of object that is allowed to contain instances of the child class. This list is stored in the possSuperiors and systemPossSuperiors attributes of the classSchema object. For example, contact objects are allowed to be created only within a container of the organizationalUnit or domainDNS classes.

Security

Active Directory supports the robust Windows 2000 security subsystem. Each object class can define a default security descriptor (SD) that is applied to each new instance of that class. A security descriptor is a structure containing a security identifier (SID) for the owner of the object and one or more access control lists (ACL) to specify which users or groups are allowed (or denied) access to the object.

When an object is created in the directory, it inherits the security descriptor of its parent container in addition to having the default security descriptor of the object's class applied. This allows Active Directory to delegate or restrict permissions on a container-by-container basis.

Active Directory and ADSI help developers work with security descriptors by providing the SecurityDescriptor object and the associated IADsSecurityDescriptor interface. However, when specifying the default security descriptor for an object class, the IADsSecurityDescriptor interface is of no use because the attribute that stores the default security descriptor, defaultSecurityDescriptor, expects a Unicode string formatted in the Security Descriptor Definition Language (SDDL). An example SDDL is shown here:

 O:AOG:DAD:(A;;RPWPCCDCLCSWRCWDWOGA;;;S-1-0-0) 

This string contains a lot of information. Table 9-5 describes its components.

SDDL Component Description

O:AO

The owner of the object is Account Operators group.

G:DA

The primary group for this object is the Domain Administrators group.

D:(...)

The discretionary access control list (DACL) with one access control entry (ACE).

A;;

Begins the list of rights that are allowed in this ACE.

RP

Read properties of the object.

WP

Write properties of the object.

CC

Create children of this object.

DC

Delete children of this object.

LC

List children of this object.

SW

Modify the group membership of a group object.

RC

Read Control.

WD

Modify DACL.

WO

Modify owner and assume ownership of the object.

GA

Generic all access rights.

;;;

This SD does not contain specific object GUIDs.

S-1-0-0

The SID string of the account for this DACL, in this case "Nobody".

Table 9-5 SDDL components of example string.

A security descriptor can be quite long as well, containing multiple access control entries and access control lists for auditing purposes. For example, the defaultSecurityDescriptor for the user class is shown below. I leave it as an exercise to the reader to figure out the permissions granted and denied.

 D:(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;DA)
(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)
(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;AO)
(A;;RPLCLORC;;;PS)
(OA;;CR;ab721a53-1e2f-11d0-9819-00aa0040529b;;PS)
(OA;;CR;ab721a54-1e2f-11d0-9819-00aa0040529b;;PS)
(OA;;CR;ab721a56-1e2f-11d0-9819-00aa0040529b;;PS)
(OA;;RPWP;77B5B886-944A-11d1-AEBD-0000F80367C1;;PS)
(OA;;RPWP;E45795B2-9455-11d1-AEBD-0000F80367C1;;PS)
(OA;;RPWP;E45795B3-9455-11d1-AEBD-0000F80367C1;;PS)
(OA;;RP;037088f8-0ae1-11d2-b422-00a0c968f939;;RS)
(OA;;RP;4c164200-20c0-11d0-a768-00aa006e0529;;RS)
(OA;;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;;RS)
(A;;RC;;;AU)
(OA;;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;;AU)
(OA;;RP;77B5B886-944A-11d1-AEBD-0000F80367C1;;AU)
(OA;;RP;E45795B3-9455-11d1-AEBD-0000F80367C1;;AU)
(OA;;RP;e48d0154-bcf8-11d1-8702-00c04fb96050;;AU)
(OA;;CR;ab721a53-1e2f-11d0-9819-00aa0040529b;;WD)
(OA;;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;;RS)
(OA;;RPWP;bf967a7f-0de6-11d0-a285-00aa003049e2;;CA)

The Windows security system is a complex and often confusing subject. I've found Microsoft Windows 2000 Security Technical Reference (Microsoft Press, 2000) invaluable for solving security-related issues. Chapter 7, "Access Control Model," provides excellent coverage of SIDs, ACEs, and ACLs. Additional information about security descriptors, SDDL, ACLs, and ACEs is available in the "Security" topic of the Microsoft Platform SDK.

Class Categories

Active Directory has three distinct categories of classes: structural, abstract, and auxiliary. I'll describe the use of each of these categories in the following sections.

The category for a class is specified in the objectClassCategory attribute of the classSchema object. This attribute is an integer that has one of four possible values. These are listed in Table 9-6.

Value Description

0

88 Class

1

Structural

2

Abstract

3

Auxiliary

Table 9-6 Values for the objectClassCategory attribute.

I won't describe the 88 class, or type 88 class, category, as it is reserved for legacy classes that were defined before the 1993 X.500 recommendation. The name comes from the 1988 X.500 recommendation. (Reminds me of the time when languages were known by the year of their specification. COBOL 77 anyone?) Active Directory does not have any default classes that are part of the 88 class category. A class with an objectClassCategory of 0 does not receive as much consistency checking as do classes of other categories. As such, you should not create new schema objects within this category. It exists solely for potential backward compatibility.

Structural Classes

The structural class category is the most common. Structural classes define the objects that can actually be created within Active Directory. Structural classes must inherit from either another structural class or from an abstract class.

Abstract Classes

The abstract class category is used for high-level, generic definitions. An instance of an abstract class cannot be created within the directory itself. The purpose of an abstract class is to define a broad set of attributes that are then inherited by other classes that derive from it.

For example, the top class is at the top of the Active Directory class hierarchy. All other classes in the schema must derive from it. The top class is an abstract class that defines the common attributes that all classes within Active Directory must or may contain. The top class specifies that all child classes must contain the instanceType, nTSecurityDescriptor, objectCategory, and objectClass attributes. The set of optional attributes includes cn, description, and createTimeStamp.

Because the top class is an abstract class, you cannot create an instance of it in Active Directory. If you think about it, you would not need to because the top class, like all abstract classes, is too general in nature.

Abstract classes can inherit only from other abstract classes, with the exception of the special top class. The following is a list of abstract classes available in Active Directory:

  • applicationSettings
  • applicationSiteSettings
  • connectionPoint
  • country
  • device
  • domain
  • groupOfNames
  • ipsecBase
  • leaf
  • organizationalPerson
  • person
  • rpcEntry
  • securityObject
  • top

Auxiliary Classes

I mentioned earlier that Active Directory does not support C++ style multiple inheritance. However, the auxiliary class category provides a similar capability. Auxiliary classes are like abstract classes in that they define a set of attributes that become part of a child class. However, the attributes from auxiliary classes are included in the definition of a child class, whereas attributes from abstract classes are inherited.

This makes sense when two very different types of objects require a similar set of attributes. For example, the group and user classes. When used with Microsoft Exchange 2000 Server, group objects can be e-mail enabled and act as distribution lists. That means both users and groups require a common set of attributes related to e-mail, although that is about all they have in common. To provide a set of common attributes, Active Directory uses the mailRecipient auxiliary class, which is included in both the user and group objects.

An auxiliary class can inherit from another auxiliary class or an abstract class. The definition of an auxiliary class can also specify other auxiliary classes to be included.

The following is a list of auxiliary classes included with Active Directory. Note that server applications such as Exchange 2000 Server extend existing and add new auxiliary classes.

  • mailRecipient
  • samDomain
  • samDomainBase
  • securityPrincipal

Object Classes and Object Categories

Now that you know about inheritance and class categories, let's see how these pieces fit together to produce an instance of a class in the directory. Figure 9-6 shows an inheritance map for an instance of the user class. The user object represents a new user named John Doe. Note that not all the possible attributes (mandatory or optional) are shown, just a representative portion.

Figure 9-6 Inheritance map of the user class in Active Directory.

In Figure 9-6, you'll notice that the user object inherits a mandatory attribute named objectClass from the top class. The objectClass attribute is a read-only, multivalued attribute that contains a value for each class that the object inherits from. For example, the objectClass for the user object is: top, person, organizationalPerson, and user. By examining the objectClass attribute of any object, you can readily tell which classes the object inherited from to form the object. Note, however, that the values in the objectClass attribute reflect only inherited classes, not included auxiliary classes.

Since objectClass is a mandatory attribute of the top class, it is a required attribute for all classes. This means every object in Active Directory has this attribute. You can use this knowledge to build an LDAP query that searches every object. Use the "any" character (*) in the search:

 (objectClass=*) 

This query will always return every object within the search scope.

In addition to objectClass, the John Doe user object contains another mandatory attribute, objectCategory. What's interesting to note about the objectCategory attribute is that instead of being an integer value representing the class category, it's a distinguished name string. For example:

 CN=Person,CN=Schema,CN=Configuration,DC=coppersoftware,DC=com 

This distinguished name is pointing to the object in the schema that defines the person class. Huh? Didn't I just explain how a class category is either structural, abstract, or auxiliary?

This is a case of confusing terminology. Classes have categories that define the type of class (structural, abstract, or auxiliary). However, objects also have categories that define the type of object. Active Directory uses the objectClassCategory attribute of the class object to know how to treat the class. The objectCategory attribute is available for applications to deal with related objects.

It's easy to confuse the objectCategory attribute with the objectClassCategory attribute. I tend to think of the class category as the "class type," which contains a value from a limited range (1, 2, or 3). Since the objectCategory is a distinguished name string, the range of possibilities is wider and it's easier for me to think of it as a category.

Having an attribute for the category of an object (as opposed to its class) is beneficial for two reasons. First, it allows related objects to be grouped in searches. Imagine that you want to find all the employees in your company with the last name Smith. You could use the following search query:

 (&(objectClass=user)(sn=Smith)) 

This query would work, but the objects returned would only be the people named Smith who are network users. What about people who are not network users but are entered in the directory as contacts? You could write the following query to accommodate that case:

 (&(|(objectClass=user)(objectClass=contact)))(sn=Smith)) 

However, this query is inefficient and won't accommodate extensions to the schema, for example, a class like retiredEmployee.

What you want to do is search for all the "people" in the directory. OK, so then this should work:

 (&(objectClass=person)(sn=Smith)) 

This does work because the person value appears in the objectClass attribute of all objects of the user and contact classes. However, using the objectCategory attribute is more efficient because it's a single-valued attribute, as opposed to objectClass, which is multivalued. Such a query reduces the workload on the server and results in quicker searches.

The second major benefit of using objectCategory is that it's an indexed attribute, meaning that Active Directory can optimize itself to quickly search for objects based on that attribute. Contrast this with the objectClass attribute, which is not unique: every instance includes the top class. Searching multivalued attributes makes for inefficient indexing.

Normally, the objectCategory value is the distinguished name of the class object; however, it can point to any classSchema object in the schema. As we saw with the user object, the objectCategory value points to the person abstract class. This makes the common query for all objects representing people easy, for example:

 (&(objectCategory=person)(sn=Smith)) 

The default value for an object's category is set in the defaultObjectCategory attribute of the class object. Applications cannot change the objectCategory attribute of an object, nor can the schema be modified to reflect a new value for defaultObjectCategory.

Object Naming

Developers new to Active Directory often ask me about object naming. Their question goes like this: "Why does the Name property of an object return something like CN=John Doe? Why can't it just return John Doe?"

The answer, which might not be satisfying, returns to the roots of LDAP and X.500. There is no fixed "name" attribute for any object. The string CN=John Doe is actually the RDN of the object. The RDN is made up of the naming attribute of the object, an equal sign, and the value of the naming attribute.

Generally, the naming attribute is the cn (Common-Name) attribute. Sometimes, however, a different attribute is chosen to be the naming attribute. The object named DC=coppersoftware is using the dc (Domain-Component) attribute as the naming attribute. Table 9-7 lists the classes that use a naming attribute other than cn.

Class Naming Attribute

organization

o
Organization-Name

organizationalUnit

ou
Organizational-Unit-Name

country

c
Country-Name

dnsNode

dc
Domain-Component

dnsZone

dc
Domain-Component

domain

dc
Domain-Component

domainDNS

dc
Domain-Component

locality

l
Locality-Name

Table 9-7 Classes that use a naming attribute other than cn.

The naming attribute is set by the class using the rDNAttID attribute of the classSchema object. It contains the OID of the attribute to be used for the name of the object.


Creating Objects with the Same Name

I've heard from several network administrators who think it's impossible to have two people with the same name in the same container or organizational unit. This perceived problem reflects the design of the creation wizard for the user class more than the overall architecture of Active Directory.

When adding a new user to Active Directory, the New User Wizard automatically creates the Full Name field from the concatenation of the first name, middle initial, and last name of the person. The wizard uses the Full Name field as the value for the cn attribute. This attribute is the naming attribute for most classes, including the user class. So, if another user with the same cn value already exists in the container, an error will be presented to the user, as shown here:

The solution is to create the user using a unique Full Name field by adding a relatively unique string. This illustration shows the name with the date appended.

After the user is created, you can edit the displayName attribute to reflect the full name of the user. Modifying the displayName attribute will not affect the cn attribute, which is now unique in the container.


IADsClass

The IADsClass interface allows you to easily retrieve information about an object class. The interface is straightforward. Its properties are shown in Table 9-8. This ADSI interface is not specific to Active Directory, so not all its properties apply here. I've noted the relevant properties below.

IADsClass Property Data Type Description

Abstract

Boolean

True if the class is an abstract class.

AuxDerivedFrom

Variant array

Array of strings identifying the auxiliary classes that this class is derived from.

Auxiliary

Boolean

True if the class is an auxiliary class.

CLSID

String

The class ID GUID of the COM object that implements this class.

Container

Boolean

True if the class is a container.

Containment

Variant array

Array of strings identifying the classes this class can contain.

DerivedFrom

Variant array

Array of strings identifying the classes this class derives from. Under Active Directory, only the first derived class is listed.

HelpFileContext

Long

Context ID of the help file topic where information about this class can be found.

HelpFileName

String

Name of the help file that contains information about this class.

MandatoryProperties

Variant array

Array of strings, each with the name of the attributes that are required for this class.

NamingProperties

Variant array

Array of strings with the names of attributes that are used to name objects of this class. Under Active Directory, this property contains only one attribute.

OID

String

The object identifier for this class.

OptionalProperties

Variant array

Array of strings, each with the name of the attributes that are optional for this class.

PossibleSuperiors

Variant array

Array of strings identifying the classes that can contain instances of this class.

PrimaryInterface

String

The interface ID (IID) GUID of the primary interface for this class. For example, the user class would have the IID of the IADsUser interface.

Table 9-8 Properties of the IADsClass interface.

The IADsClass interface also defines a Qualifiers method that is not implemented with Active Directory.

Listing 9-4, from the SchemaBrowser sample, uses the IADsClass properties to display a dialog box with information about a particular class.

 ` ADsClassInfo.frm - Displays class information
` Shows the IADsClass interface
`
Private Sub Form_Load()
    ` Bind to the class object in the schema
    Dim adsClass As IADsClass
    Set adsClass = GetObject("LDAP://schema/" & frmClassList.Tag)
    ` Not all classes have these properties
    On Error Resume Next
    ` Fill in text fields with information from IADsClass
    txtDisplayName.Text = adsClass.Name
    txtOID.Text = adsClass.OID
    txtCLSID.Text = adsClass.CLSID
    txtPrimaryInterface.Text = adsClass.PrimaryInterface
    ` Set the type
    If adsClass.Abstract Then
        optAbstract.Value = True
        optAbstract.Enabled = True
    ElseIf adsClass.Auxiliary Then
        optAuxiliary.Value = True
        optAuxiliary.Enabled = True
    Else
        optStructural.Value = True
        optStructural.Enabled = True
    End If
    ` List Possible Superiors
    Call ListPossibleSuperiors(adsClass, lstPossSuperiors)
    ` Is this class a container?
    If adsClass.Container Then
        ` Set the container checkbox and enable
        chkIsContainer.Value = 1
        chkIsContainer.Enabled = True         ` List the objects that can be contained
        Call ListContainment(adsClass, lstContainment)
    Else
        ` Turn off container checkbox and disable
        chkIsContainer.Value = 0
        chkIsContainer.Enabled = False
    End If
    ` List derived classes
    Call ListDerivedClasses(adsClass, lstDerivedFrom)
    ` List derived auxiliary classes
    Call ListDerivedAuxClasses(adsClass, lstAuxDerivedFrom)
    ` List the attributes for this class
    Call ListAttributes(adsClass, lstAttributes)
    ` Set the naming attribute
    ` (just one under Active Directory)
    txtNamingAttribute.Text = adsClass.NamingProperties
    ` Restore error handling
    On Error GoTo 0
End Sub
Public Sub ListAttributes(adsClass As IADsClass, _
    lstListBox As ListBox)
` List mandatory attributes
With lstListBox
    Dim varProperty As Variant
    If IsArray(adsClass.MandatoryProperties) Then
        ` Enumerate each item
        For Each varProperty In adsClass.MandatoryProperties
            ` Add to list
            .AddItem varProperty
            .Selected(.NewIndex) = True
        Next
    Else
        .AddItem adsClass.MandatoryProperties
        .Selected(.NewIndex) = True
    End If     ` List optional attributes
    If IsArray(adsClass.OptionalProperties) Then
        ` Enumerate each item
        For Each varProperty In adsClass.OptionalProperties
            ` Add to list
            .AddItem varProperty
            .Selected(.NewIndex) = False
        Next
    Else
        .AddItem adsClass.OptionalProperties
        .Selected(.NewIndex) = False
    End If
    .ListIndex = 0
End With
End Sub
Public Sub ListDerivedClasses(adsClass As IADsClass, _
    lstListBox As ListBox)
    ` List each derived class
    If IsArray(adsClass.DerivedFrom) Then
        ` Enumerate each item
        Dim varDerivedClass As Variant
        For Each varDerivedClass In adsClass.DerivedFrom
            ` Add to list
            lstListBox.AddItem varDerivedClass
        Next
    Else
        lstListBox.AddItem adsClass.DerivedFrom
    End If
End Sub
Public Sub ListDerivedAuxClasses(adsClass As IADsClass, _
    lstListBox As ListBox)
    
    ` List derived aux classes
    If IsArray(adsClass.AuxDerivedFrom) Then
        
        ` Enumerate each item
        Dim varDerivedClass As Variant
        For Each varDerivedClass In adsClass.AuxDerivedFrom
            ` Add to list
            lstListBox.AddItem varDerivedClass
        Next
    Else
        lstListBox.AddItem adsClass.AuxDerivedFrom
    End If
    
End Sub
Public Sub ListContainment(adsClass As IADsClass, _
    lstListBox As ListBox)
    ` List each item that can be contained
    If IsArray(adsClass.Container) Then
        ` Enumerate each item
        Dim varContainer As Variant
        For Each varContainer In adsClass.Containment
            ` Add to list
            lstListBox.AddItem varContainer
        Next
    Else
        lstListBox.AddItem adsClass.Containment
    End If
End Sub
Public Sub ListPossibleSuperiors(adsClass As IADsClass, _
    lstListBox As ListBox)
    If IsArray(adsClass.PossibleSuperiors) Then
        ` Enumerate each item
        Dim varContainer As Variant
        For Each varContainer In adsClass.PossibleSuperiors
            ` Add to list
            lstListBox.AddItem varContainer
        Next
    Else
        lstListBox.AddItem adsClass.PossibleSuperiors
    End If
End Sub

Listing 9-4 Code from the SchemaBrowser sample showing how to use the IADsClass interface to obtain information about a class.

When you run the SchemaBrowser sample, select a class, and click the Properties button, a dialog box similar to Figure 9-7 is displayed.

Figure 9-7 The Class Properties dialog box showing information obtained using the IADsClass interface.



MicrosoftR WindowsR 2000 Active DirectoryT Programming
MicrosoftR WindowsR 2000 Active DirectoryT Programming
ISBN: N/A
EAN: N/A
Year: 2001
Pages: 108

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net