O'Reilly logo

Active Directory Cookbook, 3rd Edition by Robbie Allen, Laura E. Hunter

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Searching and Manipulating Objects

4.0. Introduction

Active Directory is based on the Lightweight Directory Access Protocol (LDAP) and supports the LDAP version 3 specification defined in RFC 2251. And while many of the AD tools and interfaces, such as ADSI, abstract and streamline LDAP operations to make things easier, any good AD administrator or developer must have a thorough understanding of LDAP to fully utilize Active Directory. This chapter will cover some of the LDAP-related tasks you may need to perform when working with Active Directory, along with other tasks related to searching and manipulating objects within the directory.

The Anatomy of an Object

The Active Directory schema is composed of a hierarchy of classes that define the types of objects that can be created within Active Directory, as well as the different attributes that they can possess. These classes support inheritance, which enables developers to reuse existing class definitions for more than one type of object; for example, the description attribute is available with every type of AD object, but the attribute itself is only defined once within the schema. At the top of the inheritance tree is the top class, from which every class in the schema is derived. Table 4-1 contains a list of some of the attributes that are available from the top class, and subsequently are defined on every object that is created in Active Directory.

Table 4-1. Common attributes of objects

Attribute

Description

cn

RDN attribute for most object classes, also referred to as the common name.

whenCreated

Timestamp when the object was created. See Recipe 4.26 for more information.

description

Multivalued attribute that can be used as a generic field for storing a description of the object. Although this attribute is multivalued, objects such as users and groups can only have one value populated due to legacy support requirements.

displayName

Name of the object displayed in administrative interfaces.

distinguishedName

Distinguished name of the object.

whenChanged

Timestamp when the object was last changed by the local server. See Recipe 4.26 for more information.

name

RDN of the object. The value of this attribute will mirror the naming attribute (e.g., cn, ou, dc).

nTSecurityDescriptor

Security descriptor assigned to the object.

objectCategory

Used as a grouping mechanism for objects with a similar purpose (e.g., Person).

objectClass

List of classes from which the object’s class was derived.

objectGUID

Globally unique identifier for the object.

uSNChanged

Update sequence number (USN) assigned by the local server after the last change to the object (can include creation).

uSNCreated

USN assigned by the local server when the object was created.

4.1. Viewing the RootDSE

Problem

You want to view attributes of the RootDSE, which can be useful for discovering basic information about a forest, domain, or domain controller without hardcoding the name of a particular naming context into a query.

Solution

Using a graphical user interface

  1. Open LDP from the Windows Support Tools. (LDP is installed by default on a Windows Server 2008 domain controller.)

  2. From the menu, select Connection→Connect.

  3. For Server, enter a domain controller, domain name, or leave blank to do a serverless bind.

  4. For Port, enter 389.

  5. Click OK.

  6. The contents of the RootDSE will be shown in the right pane.

Using a command-line interface

To display the RootDSE of a domain controller using AdFind, use the following syntax:

> adfind -rootdse

You’ll see results similar to the following (truncated for readability):

>currentTime: 20051130204431.0Z
>subschemaSubentry: CN=Aggregate,CN=Schema,CN=Configuration,DC=contoso,DC=com
>dsServiceName: CN=NTDS Settings,CN=2K3-SP1-R2,CN=Servers,CN=Default-First-Site-
Name,CN=Sites,CN=Configuration,DC=contoso,DC=com
>namingContexts: DC=contoso,DC=com
>namingContexts: CN=Configuration,DC=contoso,DC=com
>namingContexts: CN=Schema,CN=Configuration,DC=contoso,DC=com
>namingContexts: DC=DomainDnsZones,DC=contoso,DC=com
>namingContexts: DC=ForestDnsZones,DC=contoso,DC=com
>defaultNamingContext: DC=contoso,DC=com
>schemaNamingContext: CN=Schema,CN=Configuration,DC=contoso,DC=com
>configurationNamingContext: CN=Configuration,DC=contoso,DC=com
>rootDomainNamingContext: DC=contoso,DC=com
>serverName: CN=2K3-SP1-R2,CN=Servers,CN=Default-First-Site-Name,CN=Sites,
CN=Configuration,DC=contoso,DC=com
>supportedCapabilities: 1.2.840.113556.1.4.800
>supportedCapabilities: 1.2.840.113556.1.4.1670
>supportedCapabilities: 1.2.840.113556.1.4.1791
>isSynchronized: TRUE
>isGlobalCatalogReady: TRUE
>domainFunctionality: 0
>forestFunctionality: 0
>domainControllerFunctionality: 2

Using VBScript

' This code prints the
' attributes of the RootDSE
set objRootDSE = GetObject("LDAP://RootDSE")
objRootDSE.GetInfo
for i = 0 to objRootDSE.PropertyCount - 1
    set strProp = objRootDSE.Item(i)
    WScript.Echo strProp.Name & " "
    for each strPropval in strProp.Values
       WScript.Echo " " & strPropval.CaseIgnoreString
    next
 next

Using PowerShell

 $root = [ADSI] "LDAP://RootDSE"
 $colRootProps = $root | get-member
 foreach ($prop in $colRootProps) { write-host $prop.Name -nonewline; 
>> write-host ": " -nonewline;
>> write-host $root.($prop.Name) 
>> } 
>>

Discussion

The RootDSE was originally defined in RFC 2251 as part of the LDAPv3 specification. It is not part of the Active Directory namespace per se. It is a synthetic object that is maintained separately by each domain controller.

The RootDSE can be accessed anonymously using LDP; the command-line and VBScript solutions use the credentials of the currently logged-on user unless you specify an alternate username and password. In the CLI and VBScript solutions, serverless binds were used against the RootDSE. In that case, the DC Locator process is used to find a domain controller in the domain you authenticate against. This can also be accomplished with LDP by not entering a server name from the Connect dialog box.

The RootDSE is key to writing portable AD-enabled applications. It provides a mechanism to programmatically determine the distinguished names of the various naming contexts (among other things), which means that you do not need to hardcode that information in scripts and programs. Here is an example from LDP when run against a Windows Server 2003–based domain controller:

ld = ldap_open("dc01", 389);
Established connection to dc01.
Retrieving base DSA information …
Result <0>: (null)
Matched DNs:
Getting 1 entries:
>> Dn:
1> currentTime: 07/26/2008 15:29:42 Pacific Standard Time Pacific Daylight Time;

1> subschemaSubentry:CN=Aggregate,CN=Schema,CN=Configuration,DC=adatum,DC=com;

1> dsServiceName: CN=NTDS Settings,CN=DC01,CN=Servers,CN=Default-First-Site-
Name,CN=Sites,CN=Configuration,DC=adatum,DC=com;

5> namingContexts: DC=adatum,DC=com; CN=Configuration,DC=adatum,DC=com;
CN=Schema,CN=Configuration,DC=adatum,DC=com;
DC=DomainDnsZones,DC=adatum,DC=com; DC=ForestDnsZones,DC=adatum,DC=com;

1> defaultNamingContext: DC=adatum,DC=com;

1> schemaNamingContext: CN=Schema,CN=Configuration,DC=adatum,DC=com;

1> configurationNamingContext: CN=Configuration,DC=adatum,DC=com;

1> rootDomainNamingContext: DC=adatum,DC=com;

21> supportedControl: 1.2.840.113556.1.4.319; 1.2.840.113556.1.4.801;
1.2.840.113556.
1.4.473; 1.2.840.113556.1.4.528; 1.2.840.113556.1.4.417; 1.2.840.113556.1.4.619;
1.2.
840.113556.1.4.841; 1.2.840.113556.1.4.529; 1.2.840.113556.1.4.805;
1.2.840.113556.1.
4.521; 1.2.840.113556.1.4.970; 1.2.840.113556.1.4.1338; 1.2.840.113556.1.4.474;
1.2.
840.113556.1.4.1339; 1.2.840.113556.1.4.1340; 1.2.840.113556.1.4.1413; 2.16.840.1.
113730.3.4.9; 2.16.840.1.113730.3.4.10; 1.2.840.113556.1.4.1504;
1.2.840.113556.1.4.
1852; 1.2.840.113556.1.4.802;

2> supportedLDAPVersion: 3; 2;

12> supportedLDAPPolicies: MaxPoolThreads; MaxDatagramRecv; MaxReceiveBuffer;
InitRecvTimeout; MaxConnections; MaxConnIdleTime; MaxPageSize; MaxQueryDuration;
MaxTempTableSize; MaxResultSetSize; MaxNotificationPerConn; MaxValRange;

1> highestCommittedUSN: 53242;

4> supportedSASLMechanisms: GSSAPI; GSS-SPNEGO; EXTERNAL; DIGEST-MD5;

1> dnsHostName: dc01.adatum.com;

1> ldapServiceName: adatum.com:dc01$@ADATUM.COM;

1> serverName: CN=DC01,CN=Servers,CN=Default-First-Site-
Name,CN=Sites,CN=Configuration,DC=adatum,DC=com;

3> supportedCapabilities: 1.2.840.113556.1.4.800; 1.2.840.113556.1.4.1670; 1.2.840.
113556.1.4.1791;

1> isSynchronized: TRUE;

1> isGlobalCatalogReady: TRUE;

1> domainFunctionality: 0 = ( DS_BEHAVIOR_WIN2000 );

1> forestFunctionality: 0 = ( DS_BEHAVIOR_WIN2000 );

1> domainControllerFunctionality: 2 = ( DS_BEHAVIOR_WIN2003 );

Using VBScript

All attributes of the RootDSE were retrieved and displayed. Typically, you will need only a few of the attributes, in which case you’ll want to use Get or GetEx as in the following example:

strDefaultNC = objRootDSE.Get("defaultNamingContext")

Or if want to get an object based on the DN of one of the naming contexts, you can call GetObject using an ADsPath:

set objUser = GetObject("LDAP://cn=administrator,cn=users," & _
                        objRootDSE.Get("defaultNamingContext") )

Using PowerShell

The PowerShell code in this example makes use of the foreach command, which allows you to take a collection of objects (in this case, the properties of RootDSE), and perform the same action on each one.

See Also

RFC 2251, MS KB 219005 (Windows 2000: LDAPv3 RootDSE), MSDN: IADsPropertyEntry, MSDN: IADsProperty Value, MSDN: IADs::Get, and MSDN: IADs::GetEx

4.2. Viewing the Attributes of an Object

Problem

You want to view one or more attributes of an object.

Solution

Using a graphical user interface

  1. Open LDP from the Windows Support Tools or from the Windows Server 2008 command prompt.

  2. From the menu, select Connection→Connect.

  3. For Server, enter the name or IP address of a domain controller or domain that contains the object.

  4. For Port, enter 389.

  5. Click OK.

  6. From the menu, select Connection→Bind.

  7. Enter credentials of a user who can view the object (if necessary).

  8. Click OK.

  9. From the menu, select View→Tree.

  10. For BaseDN, type the DN of the object you want to view.

  11. For Scope, select Base.

  12. Click OK.

Using a command-line interface

To obtain a list of attributes for a particular object using DSQuery, use the following syntax:

> dsquery * "<ObjectDN>" -scope base -attr *

For Windows 2000, use this command:

> enumprop "LDAP://<ObjectDN>"

To query for an object using AdFind, use the following syntax:

> adfind -b <Parent Container DN> -f cn=<Object CN> -tdcgt

For example, querying for the administrator user object produces the following output:

C:\>adfind -b dc=contoso,dc=com -f cn=administrator -tdc -tdcgt

AdFind V01.27.00cpp Joe Richards (joe@joeware.net) November 2005

Using server: 2k3-sp1-r2.contoso.com:389
Directory: Windows Server 2003

dn:CN=Administrator,CN=Users,DC=contoso,DC=com
>objectClass: top
>objectClass: person
>objectClass: organizationalPerson
>objectClass: user
>cn: Administrator
>description: Built-in account for administering the computer/domain
>distinguishedName: CN=Administrator,CN=Users,DC=contoso,DC=com
>instanceType: 4
>whenCreated: 05/26/2008-12:13:15 Eastern Daylight Time
>whenChanged: 
05/26/2008-12:13:15 Eastern Daylight Time

>uSNCreated: 8194
>memberOf: CN=Group Policy Creator Owners,CN=Users,DC=contoso,DC=com
>memberOf: CN=Domain Admins,CN=Users,DC=contoso,DC=com
>memberOf: CN=Enterprise Admins,CN=Users,DC=contoso,DC=com
>memberOf: CN=Schema Admins,CN=Users,DC=contoso,DC=com
>memberOf: CN=Administrators,CN=Builtin,DC=contoso,DC=com
>uSNChanged: 13905
>name: Administrator
>objectGUID: {A5C30B01-535C-4BCF-83C1-ABA5D445B9F6}
>userAccountControl: 66048
>badPwdCount: 0
>codePage: 0
>countryCode: 0
>badPasswordTime: 0
>lastLogoff: 0
>lastLogon: 05/26/2008-23:09:03 Eastern Daylight Time
>pwdLastSet: 5/17/2008-18:09:27 Eastern Daylight Time
>primaryGroupID: 513
>objectSid: S-1-5-21-751427308-4037830757-4109730475-500
>adminCount: 1
>accountExpires: 00/00/0000-00:00:00
>logonCount: 7
>sAMAccountName: Administrator
>sAMAccountType: 805306368
>objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=contoso,DC=com
>isCriticalSystemObject: TRUE

1 Objects returned

Using VBScript

' This code prints all default attributes for the specified object.
' ------ SCRIPT CONFIGURATION ------
strObjectDN = "<ObjectDN>" ' e.g. cn=jsmith,cn=users,dc=adatum,dc=com
' ------ END CONFIGURATION ---------

DisplayAttributes("LDAP://" & strObjectDN)

Function DisplayAttributes( strObjectADsPath )

   set objObject = GetObject(strObjectADsPath)
   objObject.GetInfo

   'Declare the hash (dictionary), constants and variables
   'Values taken from ADSTYPEENUM
   set dicADsType = CreateObject("Scripting.Dictionary")
   dicADsType.Add 0, "INVALID"
   dicADsType.Add 1, "DN_STRING"
   dicADsType.Add 2, "CASE_EXACT_STRING"
   dicADsType.Add 3, "CASE_IGNORE_STRING"
   dicADsType.Add 4, "PRINTABLE_STRING"
   dicADsType.Add 5, "NUMERIC_STRING"
   dicADsType.Add 6, "BOOLEAN"
   dicADsType.Add 7, "INTEGER"
   dicADsType.Add 8, "OCTET_STRING"
   dicADsType.Add 9, "UTC_TIME"
   dicADsType.Add 10, "LARGE_INTEGER"
   dicADsType.Add 11, "PROV_SPECIFIC"
   dicADsType.Add 12, "OBJECT_CLASS"
   dicADsType.Add 13, "CASEIGNORE_LIST"
   dicADsType.Add 14, "OCTET_LIST"
   dicADsType.Add 15, "PATH"
   dicADsType.Add 16, "POSTALADDRESS"
   dicADsType.Add 17, "TIMESTAMP"
   dicADsType.Add 18, "BACKLINK"
   dicADsType.Add 19, "TYPEDNAME"
   dicADsType.Add 20, "HOLD"
   dicADsType.Add 21, "NETADDRESS"
   dicADsType.Add 22, "REPLICAPOINTER"
   dicADsType.Add 23, "FAXNUMBER"
   dicADsType.Add 24, "EMAIL"
   dicADsType.Add 25, "NT_SECURITY_DESCRIPTOR"
   dicADsType.Add 26, "UNKNOWN"
   dicADsType.Add 27, "DN_WITH_BINARY"
   dicADsType.Add 28, "DN_WITH_STRING"

   for intIndex = 0 To (objObject.PropertyCount - 1)
      set objPropEntry = objObject.Item(intIndex)
      for Each objPropValue In objPropEntry.Values
         value = ""

         if (dicADsType(objPropValue.ADsType) = "DN_STRING") then
            value = objPropValue.DNString

         elseIf (dicADsType(objPropValue.ADsType) = "CASE_EXACT_STRING") then
            value = objPropValue.CaseExactString

         elseIf (dicADsType(objPropValue.ADsType) = "CASE_IGNORE_STRING") then
            value = objPropValue.CaseIgnoreString

         elseIf (dicADsType(objPropValue.ADsType) = "PRINTABLE_STRING") then
            value = objPropValue.PrintableString

         elseIf (dicADsType(objPropValue.ADsType) = "NUMERIC_STRING") then
            value = objPropValue.NumericString

         elseIf (dicADsType(objPropValue.ADsType) = "BOOLEAN") then
            value = CStr(objPropValue.Boolean)

         elseIf (dicADsType(objPropValue.ADsType) = "INTEGER") then
            value = objPropValue.Integer

         elseIf (dicADsType(objPropValue.ADsType) = "LARGE_INTEGER") then
            set objLargeInt = objPropValue.LargeInteger
            value = objLargeInt.HighPart * 2^32 + objLargeInt.LowPart

         elseIf (dicADsType(objPropValue.ADsType) = "UTC_TIME") then
            value = objPropValue.UTCTime

         else
            value = "<" & dicADsType.Item(objPropEntry.ADsType) & ">" 


         end if
         WScript.Echo objPropEntry.Name & " : " & value
      next
   next
End Function

Using PowerShell

$obj = [ADSI]("LDAP://" + "<Object DN>")
$colObjProps = $obj | get-member
foreach ($prop in $colObjProps) { 
  write-host $prop.Name -nonewline
  write-host ": " -nonewline
  write-host $obj.($prop.Name) 
}

Discussion

Objects in Active Directory are made up of a collection of attributes. Attributes can be single- or multivalued. Each attribute also has an associated syntax that is defined in the schema. See Recipe 10.7 for a complete list of syntaxes.

Using a graphical user interface

You can customize the list of attributes returned from a search with LDP by modifying the Attributes: field under Options→Search. To include all attributes, enter an asterisk (*). To modify the default subset of attributes that are returned, enter a semicolon-separated list of attributes. You can also use the numeric attribute ID instead of the attribute name, such as using 1.1 in place of distinguishedName.

Using a command-line interface

The -attr option for the dsquery command accepts a whitespace-separated list of attributes to display. Using an asterisk (*) will return all default attributes.

For the enumprop command, you can use the /ATTR option and a comma-separated list of attributes to return. In the following example, only the name and whenCreated attributes are returned:

> enumprop /ATTR:name,whenCreated "LDAP://<ObjectDN>"

When using AdFind, you have several shortcut switches to reduce the amount of typing you need to do. If you are searching for an object in the default container, you can use the –default switch rather than something like –b dc=contoso,dc=com. Likewise, if you are querying the Configuration NC, you can use the –config switch, -root for the root partition, or –schema for the Schema partition. If you want to query a subcontainer of one of these partitions, you can add the –rb switch, which stands for Relative Base.

Using VBScript

The DisplayAttributes function prints the attributes that contain values for the object passed in. After using GetObject to bind to the object, the IADs::GetInfo method was used to populate the local property cache with all of the object’s attributes from AD. To print each value of a property, you have to know its type or syntax. The ADsType method returns an integer from the ADSTYPEENUM enumeration that corresponds with a particular syntax (e.g., Boolean). Based on the syntax, you call a specific method (e.g., Boolean) that can properly print the value. If you didn’t incorporate this logic and tried to print all values, using the CaseIgnoreString method for example, an error would get generated when the script encountered an octet string because octet strings (i.e., binary data) do not have a CaseIgnoreString representation.

The values from the ADSTYPEENUM enumeration are stored in key/value pairs in a dictionary object (i.e., Scripting.Dictionary). In the dictionary object, the key for the dictionary is the ADSTYPEENUM integer and the value is a textual version of the syntax. The dictionary object was used to print the textual syntax of each attribute. You iterated over all the properties in the property cache using IADsPropertyList and IADsPropertyEntry objects, which are instantiated with the IADsPropertyList::Item method.

Note

The DisplayAttributes function is used throughout the book in examples where the attributes for a given type of object are displayed.

Using PowerShell

The PowerShell example in this recipe can also make use of the free Quest Active Directory cmdlets, specifically the Get-QADObject cmdlet.

See Also

Chapter 1 for more information about the Quest Active Directory cmdlets, Recipe 10.7, MSDN: IADsPropertyEntry, MSDN: IADsPropertyList, MSDN: ADSTYPEENUM, MSDN: IADs::GetInfo, and Chapter 20 of Active Directory, Fourth Edition, Brian Desmond et al. (O’Reilly).

4.3. Counting Objects in Active Directory

Problem

You want to retrieve the number of directory objects that meet the result of an LDAP query.

Solution

Using a graphical user interface

  1. Open LDP from the Windows Support Tools.

  2. From the menu, select Connection→Connect.

  3. For Server, enter the name or IP address of a domain controller or domain that contains the object.

  4. For Port, enter 389.

  5. Click OK.

  6. From the menu, select Connection→Bind.

  7. Enter credentials of a user who can view the object (if necessary).

  8. Click OK.

  9. From the menu, select Browse→Search.

  10. Enter the base DN, scope, and the LDAP filter of the objects that you’re looking for.

  11. Click on Options and remove the checkmark next to Display Results. This will display the number of objects returned by the query without displaying the details of the items that are returned.

  12. Click OK and then click Run to perform the query.

Using a command-line interface

To retrieve a count of objects that match a particular query, use the following syntax:

> adfind -b <Search Base> -s <Scope> -f <Search Filter> -c

For example, retrieving the number of user objects in the adatum.com domain would use the following syntax:

> adfind -default -f "(&(objectclass=user)(objectcategory=person))" -c
>
> AdFind V01.27.00cpp Joe Richards (joe@joeware.net) November 2005
>
> Using server: 2k3-sp1-r2.adatum.com:389
> Directory: Windows Server 2003
> Base DN: DC=adatum,DC=com
>
> 5 Objects returned

Using VBScript

' This code lists the number of objects
' returned based on the specified criteria.
' ------ SCRIPT CONFIGURATION ------
strBase    =  "<LDAP://<BaseDN>>;" ' BaseDN should be the search base
strFilter  = "<Filter>;"           ' Valid LDAP search filter
strAttrs   = "<AttrList>;"         ' Comma-separated list
strScope   = "<Scope>"             ' Should be one of Subtree, Onelevel, or Base
' ------ END CONFIGURATION ---------

set objConn = CreateObject("ADODB.Connection")
objConn.Provider = "ADsDSOObject"
objConn.Open "Active Directory Provider"
set objRS = objConn.Execute(strBase & strFilter & strAttrs & strScope)
Wscript.Echo(objRS.RecordCount & " objects returned.")

Using PowerShell

The following example will query Active Directory for a list of user objects, and return the count:

$col = Get-QADObject -ldapfilter '(&(objectcategory=person)(objectclass=user))'
$col.Count

Discussion

Using VBScript

The VBScript solution uses the RecordCount property of an ADO Recordset, which contains the number of records that were returned by a particular query. The script listed here does not enable paging, so it will not work if more than 1,000 records will be returned by a query unless you specify the "Page size" property of the connection object, similar to the following:

objConn.Properties("Page size") = <PageSize>

Using PowerShell

The PowerShell code here can be shortened to a single line through the use of parentheses, as follows:

(Get-QADObject -ldapfilter '(&(objectcategory=person)(objectclass=user))').Count

4.4. Using LDAP Controls

Problem

You want to use an LDAP control as part of an LDAP operation.

Solution

Using a graphical user interface

  1. Open LDP from the Windows Support Tools.

  2. From the menu, select Options→Controls.

  3. For the Windows Server 2003 version of LDP, select the control you want to use under Load Predefined. The control should automatically be added to the list of Active Controls.

    For the Windows 2000 version of LDP, you’ll need to type the object identifier (OID) of the control under Object Identifier.

  4. Enter the value for the control under Value.

  5. Select whether the control is server- or client-side under Control Type.

  6. Check the box beside Critical if the control is critical.

  7. Click the Check-in button.

  8. Click OK.

  9. At this point, you will need to invoke the LDAP operation (e.g., Search) that will use the control. In the dialog box for any operation, be sure that the “Extended” option is checked before initiating the operation.

Using a command-line interface

The AdFind and AdMod utilities will enable a number of LDAP controls, either by default or through the use of various command-line switches. For example, the –showdel switch will invoke the Show Deleted Objects LDAP control, and -stats will invoke the Show Stats control.

Using VBScript

None of the ADSI automation interfaces directly expose LDAP controls. That means they cannot be utilized from VBScript. On the other hand, many of the controls, such as paged searching or deleting a subtree, are wrapped within their own ADSI methods that can be used within VBScript.

Any LDAP-based API, such as the Perl Net::LDAP modules, can be used to set controls as part of LDAP operations.

Using PowerShell

You can leverage LDAP controls within the current version of PowerShell by setting various properties on a DirectorySearcher object, such as the Tombstone property to return deleted objects, the ReferralChasing property, etc. For example, the following code will search for deleted objects that have an objectClass of computer:

$strCategory = "computer"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.Filter = ("(objectCategory=$strCategory)")
$objSearch.tombstone = $true
$colResults = $objSearcher.FindAll()

Discussion

LDAP controls were defined in the LDAPv3 specification as a way to extend LDAP and its operations without breaking the protocol. Many controls have been implemented, some of which are used when searching the directory (e.g., paged searching, Virtual List View [VLV], finding deleted objects, and attribute scoped query), and some are needed to do certain modifications to the directory (e.g., cross-domain object moves, tree delete, and permissive modify). Controls can be marked as critical, which means they must be processed with the request or an error is returned. If an unsupported control is not flagged as critical, the server can continue to process the request and ignore the control.

The complete list of controls supported by Active Directory is included in Table 4-2.

Table 4-2. LDAP controls supported by Active Directory

Name

OID

Description

Permit No-Opt Modify

1.2.840.113556.1.4.1413

Allows duplicate adds of the same value for an attribute or deletion of an attribute that has no values to succeed (normally, it would fail in that situation).

Return Deleted Objects

1.2.840.113556.1.4.417

Used to inform the server to return any deleted objects that matched the search criteria.

Cross Domain Move

1.2.840.113556.1.4.521

Used to move objects between domains.

Set change notifications

1.2.840.113556.1.4.528

Used by clients to register for notification of when changes occur in the directory.

Delayed Write

1.2.840.113556.1.4.619

Used to inform the server to return after directory modifications have been written to memory, but before they have been written to disk. This can speed up processing of a lot of modifications.

Security Descriptor Flags

1.2.840.113556.1.4.801

Used to pass flags to the server to control certain security descriptor options.

Subtree Delete

1.2.840.113556.1.4.805

Used to delete portions of the directory tree, including any child objects.

Verify Name Existence

1.2.840.113556.1.4.1338

Used to target a specific GC server that is used to verify DN-valued attributes that are processed during addition or modification operations.

No referrals generated

1.2.840.113556.1.4.1339

Informs the server not to generate any referrals in a search response.

Domain or phantom scope

1.2.840.113556.1.4.1340

Used to pass flags to the server to control search options.

Search Stats

1.2.840.113556.1.4.970

Used to return statistics about an LDAP query. See Recipe 15.10 for an example.

Attribute Scoped Query

1.2.840.113556.1.4.1504

Used to force a query to be based on a specific DN-valued attribute. This control is new to Windows Server 2003. See Recipe 4.8 for an example.

Extended DN

1.2.840.113556.1.4.529

Used to return an object’s GUID and SID (for security principals) as part of its distinguished name.

Quota SID

1.2.840.113566.1.4.1852

Used to pass the SID of a security principal in order to query constructed attributes such as ms-DS-Quota-Effective and ms-DS-Quota-Used.

Paged Results

1.2.840.113556.1.4.319

Instructs the server to return search results in “pages.”

DIRSYNC

1.2.840.113556.1.4.841

Used to find objects that have changed over a period of time.

Server-side Sort Request

1.2.840.113556.1.4.473

Used to inform the server to sort the results of a search.

Server-side Sort Response

1.2.840.113556.1.4.474

Returned by the server in response to a sort request.

VLV Request

2.16.840.1.113730.3.4.9

Used to request a virtual list view of results from a search. This control is new to Windows Server 2003.

VLV Response

2.16.840.1.113730.3.4.10

Response from a server returning a virtual list view of results from a search. This control is new to Windows Server 2003.

See Also

Recipe 4.8, Recipe 15.10, RFC 2251 (Lightweight Directory Access Protocol [v3]) for a description of LDAP controls, MSDN: Extended Controls, and MSDN: Using Controls

4.5. Using a Fast or Concurrent Bind

Problem

You want to perform an LDAP bind using a concurrent bind, also known as a fast bind. Concurrent binds are typically used in situations where you need to authenticate a lot of users, and those users either do not need to directly access the directory or else the directory access is done with another account.

Solution

Note

This capability was added in Windows Server 2003.

Using a graphical user interface

  1. Open LDP from the Windows Support Tools.

  2. From the menu, select Connection→Connect.

  3. For Server, enter the name of a DC.

  4. For Port, enter 389.

  5. Click OK.

  6. From the menu, select Options→Connection Options.

  7. Under Option Name: select LDAP_OPT_F*_CONCURRENT_BIND.

  8. Click the Set button.

  9. From the menu, select Connection→Bind.

  10. Enter credentials of a user.

  11. Click OK.

Discussion

Unlike simple binding, concurrent binding does not generate a security token or determine a user’s group memberships during the authentication process. It only determines if the authenticating user has a valid enabled account and password, which makes it much faster than a typical bind. This is usually used pro grammatically for AD-enabled applications to improve the speed of AD authentication; it’s not something that you’ll typically do on the fly. Concurrent binding is implemented as a session option that is set after you establish a connection to a domain controller, but before any bind attempts are made. After the option has been set, any bind attempt made with the connection will be a concurrent bind.

There are a couple of caveats when using concurrent binds. First, you cannot enable signing or encryption, which means that all data for concurrent binds will be sent over the network in clear text. Secondly, because the user’s security token is not generated, access to the directory is done anonymously and access restrictions are based on the ANONYMOUS LOGON principal.

It is worth mentioning that there is another type of bind—a fast bind—which has been available since Windows 2000, but it is completely different from the procedure just described. This fast bind is implemented within ADSI, and simply means that when you fast bind to an object, the objectClass attribute for the object is not retrieved; therefore, the object-specific IADs class interfaces are not available. For example, if you bound to a user object using an ADSI fast bind, then only the basic IADs interfaces would be available, not the IADsUser interfaces.

This is the complete list of interfaces that are available for objects retrieved with fast binds:

  • IADs

  • IADsContainer

  • IDirectoryObject

  • IDirectorySearch

  • IADsPropertyList

  • IADsObjectOptions

  • ISupportErrorInfo

  • IADsDeleteOps

You must use the IADsOpenDSObject::OpenDSObject interface to enable fast binds. If you call IADsContainer::GetObject on a child object of a parent you used a fast bind with, the same fast bind behavior applies. Unlike concurrent binds, ADSI fast binds do not impose any restrictions on the authenticating user. This means that the object-specific IADs interfaces will not be available. Also, no check is done to verify the object exists when you call OpenDSObject.

ADSI fast binds are useful when you need to make a lot of updates to objects that you know exist (perhaps from an ADO query that returned a list of DNs) and you do not need any IADs-specific interfaces. Instead of two trips over the network per object binding, there would only be one.

See Also

MSDN: Using Concurrent Binding and MSDN: ADS_AUTHENTICATION_ENUM

4.6. Connecting to an Object GUID

Problem

You want to bind to a container using its Globally Unique Identifier (GUID).

Solution

Using a graphical user interface

  1. Open LDP from the Windows Support Tools.

  2. From the menu, select Connection→Connect.

  3. For Server, enter the name of a domain controller (or leave blank to do a serverless bind).

  4. For Port, enter 389.

  5. Click OK.

  6. From the menu, select Connection→Bind.

  7. Enter credentials of a user.

  8. Click OK.

  9. From the menu, select Browse→Search.

  10. For BaseDN, enter the GUID of the object that you’re searching for in the following format:

    <GUID=758A39F4A44A0C48A16016457C1AE9E9>
  11. For Scope, select the appropriate scope.

  12. For Filter, enter an LDAP filter.

  13. Click Run.

Using a command-line interface

> adfind -b "<GUID=ObjectGUID>"

Using VBScript

' This code illustrates how to bind to an object GUID.
' ------ SCRIPT CONFIGURATION ------
strDomain = "<DomainDNSName>" ' e.g. apac.adatum.com
strGUID = "<GUID>" ' e.g. "aa312825768811d1aded00c04fd8d5cd"
                          ' for the default Computers container
' ------ END CONFIGURATION --------

set objRootDSE = GetObject("LDAP://" & strDomain & "/RootDSE")
set objContainer = GetObject("LDAP://<GUID=" & _
                             strGUID & "," & _
                             objRootDSE.Get("defaultNamingContext") & ">" )
WScript.Echo objContainer.Get("distinguishedName")

Using PowerShell

$obj = [System.DirectoryServices.DirectoryEntry] "LDAP://<GUID=<ObjectGUID>>"

Discussion

Each object in Active Directory has a GUID associated with it, stored in the objectGUID attribute. The GUID is for most purposes a unique identifier that retains its value even if an object is updated, renamed, or moved. This makes the GUID the preferable means of binding to an object, rather than hardcoding a reference to an object name that might change or by using a potentially complex LDAP query.

See Also

For a more in-depth discussion of the objectGUID attribute, see “GUIDs, or Having Unique in the Name Doesn’t Make It So” (http://blog.joeware.net/2005/06/19/42/), MSDN: IADs.GUID, MSDN: Using objectGUID to Bind to an Object, and Recipe 4.7

4.7. Connecting to a Well-Known GUID

Problem

You want to connect to LDAP using one of the well-known GUIDs in Active Directory.

Solution

Using a graphical user interface

  1. Open LDP.

  2. From the menu, select Connection→Connect.

  3. For Server, enter the name of a domain controller (or leave blank to do a serverless bind).

  4. For Port, enter 389.

  5. Click OK.

  6. From the menu, select Connection→Bind.

  7. Enter credentials of a domain user.

  8. Click OK.

  9. From the menu, select View→Tree.

  10. For the DN, enter:

    <WKGUID=<WKGUID>,<DomainDN>>

    where <WKGUID> is the well-known GUID that you want to connect to, and <DomainDN> is the distinguished name of a domain.

  11. Click OK. In the lefthand menu, you can now browse the container corresponding to the well-known GUID that you specified.

Using a command-line interface

To enumerate the well-known GUIDs in the Domain NC, use the following syntax:

> adfind -default -s base wellknownObjects

To display the WKGUIDs in the Configuration NC, replace –default with –config in the previous syntax.

To connect to a well-known GUID in the Domain NC using AdFind, use the following syntax:

> adfind -b "<WKGUID=<WKGUID>,<DomainDN>>" -s base -dn

Note

Because of additional security settings attached to the Deleted Objects container, if you specify this GUID you must also use the –showdel switch in adfind.

Using VBScript

' This code illustrates how to bind to the default computer's container.
' ------ SCRIPT CONFIGURATION ------
strDomain = "<DomainDNSName>" ' e.g. apac.adatum.com
strWKGUID = "<WKGUID>" ' e.g. "aa312825768811d1aded00c04fd8d5cd"
                    ' for the default Computer's container
' ------ END CONFIGURATION --------

set objRootDSE = GetObject("LDAP://" & strDomain & "/RootDSE")
set objCompContainer = GetObject("LDAP://<WKGUID=" & _
                             strWKGUID & "," & _
                             objRootDSE.Get("defaultNamingContext") & ">" )
WScript.Echo objCompContainer.Get("distinguishedName")

Using PowerShell

$obj = [ADSI] "LDAP://<WKGUID=<Well-Known GUID>,<Domain DN>>"

Discussion

The domain NC in Active Directory contains a number of well-known GUIDs that correspond to containers that exist in every AD implementation. These GUIDs are stored as wellKnownObjects attributes within the <DomainDN> object, and allow administrators and developers to consistently connect to critical containers even if they are moved or renamed. The <DomainDN> container possesses the following objects that correspond to well-known GUIDs:

  • CN=NTDS Quotas,<DomainDN>

  • CN=Microsoft,CN=Program Data,<DomainDN>

  • CN=Program Data,<DomainDN>

  • CN=ForeignSecurityPrincipals,<DomainDN>

  • CN=Deleted Objects,<DomainDN>

  • CN=Infrastructure,<DomainDN>

  • CN=LostAndFound,<DomainDN>

  • CN=System,<DomainDN>

  • OU=Domain Controllers,<DomainDN>

  • CN=Computers,<DomainDN>

  • CN=Users,<DomainDN>

The Configuration NC adds these additional WKGUIDs:

  • CN=NTDS Quotas,CN=Confguration,<ForestRootDN>

  • CN=LostAndFoundConfig,CN=Configuration,<ForestRootDN>

  • CN=Deleted Objects,CN=Configuration,<ForestRootDN>

See Also

MSDN: Binding to Well-Known Objects Using WKGUID

4.8. Searching for Objects in a Domain

Problem

You want to find objects in a domain that match certain criteria.

Solution

Using a graphical user interface

  1. Open LDP from the Windows Support Tools.

  2. From the menu, select Connection→Connect.

  3. For Server, enter the name of a domain controller (or leave blank to do a serverless bind).

  4. For Port, enter 389.

  5. Click OK.

  6. From the menu, select Connection→Bind.

  7. Enter credentials of a user.

  8. Click OK.

  9. From the menu, select Browse→Search.

  10. For BaseDN, type the base distinguished name where the search will start. (You can leave this blank if you wish to connect to the domain NC as the base DN.)

  11. For Scope, select the appropriate scope.

  12. For Filter, enter an LDAP filter.

  13. Click Run.

Using a command-line interface

To run a query using the built-in DSQuery tool, use the following syntax:

> dsquery * <BaseDN> -scope <Scope> -filter "<Filter>" -attr "<AttrList>"

To retrieve the SAM account name for all user objects within the adatum.com domain, for example, use the following syntax:

> dsquery * dc=adatum,dc=com -filter
"(&(objectclass=user)(objectcategory=person))" -attr sAMAccountName

To run a query using adfind, use the following syntax:

> adfind -b <BaseDN> -s <Scope> -f <Filter> <Attributes>

Querying for SAM account names of user objects with adfind takes the following syntax:

> adfind -b dc=adatum,dc=com -f "(&(objectclass=user)(objectcategory=person))"
sAMAccountName

Note

Both DSQuery and AdFind assume a default search scope of subtrees; you only need to specify the search scope if you want to use a different one.

Using VBScript

' This code searches
' for objects based on the specified criteria.
' ------ SCRIPT CONFIGURATION ------
strBase   = "<LDAP://<BaseDN>>;" ' BaseDN should be the search base
strFilter = "<Filter>;"                ' Valid LDAP search filter
strAttrs  = "<AttrList>;"              ' Comma-separated list
strScope  = "<Scope>"                  ' Should be one of Subtree, Onelevel, or Base
' ------ END CONFIGURATION --------

set objConn = CreateObject("ADODB.Connection")
objConn.Provider = "ADsDSOObject"
objConn.Open "Active Directory Provider"
set objRS = objConn.Execute(strBase & strFilter & strAttrs & strScope)
objRS.MoveFirst
While Not objRS.EOF
    Wscript.Echo objRS.Fields(0).Value
    objRS.MoveNext
Wend
WScript.Echo("Search complete!")

Using PowerShell

The following example will search for user objects within an Active Directory domain using the Quest get-QADObject cmdlet:

get-QADObject -ldapfilter '(&(objectcategory=person)(objectclass=user))'

Another option is to use the DirectorySearcher class from the .NET Framework, as follows:

$strCategory = "computer"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.Filter = ("(objectCategory=$strCategory)")
$colResults = $objSearcher.FindAll()

Discussion

Most tools that can be used to search Active Directory require a basic understanding of how to perform LDAP searches using a base DN, search scope, and search filter, as described in RFC 2251 and 2254. The base DN is where the search begins in the directory tree. The search scope defines how far down in the tree to search from the base DN. The search filter is a prefix notation string that contains equality comparisons of attribute and value pairs.

The scope can be base, onelevel (or one), or subtree (or sub). A base scope will only match the base DN, onelevel will only match objects that are contained directly under the base DN, and subtree will match everything from the base DN and any objects beneath it.

Note

There are no LDAP query scopes that will walk backward “up” the tree.

The search filter syntax is a powerful way to represent simple and complex queries. For example, a filter that matches all of the user objects would be (&(objectclass=user)(objectcategory=Person)). For more information on filters, see RFC 2254.

Using a graphical user interface

To customize the list of attributes returned for each matching object, look at the GUI discussion in Recipe 4.2.

Using a command-line interface

<AttrList> should be a space-separated list of attributes to return. To return all attributes that have been populated with a value, leave this field blank or use an asterisk (*).

Using VBScript

The VBScript solution uses ADO to perform the search. When using ADO, you must first create a connection object with the following three lines:

set objConn = CreateObject("ADODB.Connection")
objConn.Provider = "ADsDSOObject"
objConn.Open "Active Directory Provider"

At this point you can pass parameters to the Execute method, which will return a ResultSet object. You can iterate over the ResultSet by using the MoveFirst and MoveNext methods.

See Recipe 4.9 for more information on specifying advanced options in ADO like the page size.

Using PowerShell

A DirectorySearcher can be further customized by modifying additional properties related to the directory search, such as $objSearcher.SearchScope = [System.DirectoryServices.SearchScope]::OneLevel to specify a One Level LDAP search.

See Also

Recipe 4.2 for viewing attributes of objects, Recipe 4.9 for setting advanced ADO options, RFC 2251 (Lightweight Directory Access Protocol [v3]), RFC 2254 (Lightweight Directory Access Protocol [v3]), MSDN: Searching with ActiveX Data Objects (ADO), and for a good white paper on performing queries with LDAP, see http://www.microsoft.com/windows2000/techinfo/howitworks/activedirectory/ldap.asp

4.9. Searching the Global Catalog

Problem

You want to perform a forest-wide search using the global catalog.

Solution

Using a graphical user interface

  1. Open LDP from the Windows Support Tools.

  2. From the menu, select Connection→Connect.

  3. For Server, enter the name of a global catalog server.

  4. For Port, enter 3268.

  5. Click OK.

  6. From the menu, select Connection→Bind.

  7. Enter the credentials of a user.

  8. Click OK.

  9. From the menu, select Browse→Search.

  10. For BaseDN, type the base distinguished name of where to start the search.

  11. For Scope, select the appropriate scope.

  12. For Filter, enter an LDAP filter.

  13. Click Run.

Using a command-line interface

To query the global catalog using DSQuery, use the following syntax:

> dsquery * <BaseDN> -gc -scope <Scope> -filter "<Filter>" -attr "<AttrList>"

To run a query using AdFind, use the following syntax:

> adfind -gc -b <BaseDN> -s <Scope> -f <Filter> <Attributes>

Using VBScript

' This code searches the global catalog
' ------ SCRIPT CONFIGURATION ------
strBase    = "<GC://<BaseDN>>;"
strFilter  = "<Filter>;"
strAttrs   = "<AttrList>;"
strScope   = "<Scope>"
' ------ END CONFIGURATION ---------

set objConn = CreateObject("ADODB.Connection")
objConn.Provider = "ADsDSOObject"
objConn.Open "Active Directory Provider"
set objRS = objConn.Execute(strBase & strFilter & strAttrs & strScope)
objRS.MoveFirst
while Not objRS.EOF
    Wscript.Echo objRS.Fields(0).Value
    objRS.MoveNext
wend

Using PowerShell

To query the global catalog using the Quest AD cmdlets, use the following syntax to create the global catalog connection, and then use get-QADObject as described in previous recipes:

get-QADService -UseGlobalCatalog

To query the global catalog using the DirectorySearcher class, use the following syntax:

$strCategory = "computer"
$objGC = [System.DirectoryServices.DirectoryEntry] "GC://<DomainDN>"
$objGCSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objGCSearcher.SearchRoot = $objGC
$objSearcher.Filter = ("(objectCategory=$strCategory)")
$colResults = $objSearcher.FindAll()

Discussion

The global catalog facilitates forest-wide searches. When you perform a normal LDAP search over port 389, you are searching against a particular partition within Active Directory, whether that is the Domain naming context, Configuration naming context, Schema naming context, or an application partition. If you have multiple domains in your forest, this type of search will not search against all domains but only the domain that you specify.

The global catalog, by contrast, contains a subset of the attributes for all objects in the forest (excluding objects in application partitions). Think of it as a subset of all the naming contexts combined. Every object in the directory will be contained in the global catalog (except for objects contained within application partitions), but only some of the attributes of those objects will be available. For that reason, if you perform a global catalog search and do not get values for attributes you were expecting to, make sure those attributes are included in the global catalog, also known as the partial attribute set (PAS). See Recipe 10.15 for more information on adding information to the PAS. As an alternative, you can query a DC within the domain containing the object to return a list of all attributes configured for that object.

Using a graphical user interface

The only difference between this solution and Recipe 4.8 is that the port has changed to 3268, which is the standard GC port.

Using a command-line interface

The only difference between this solution and Recipe 4.8, both for DSQuery and AdFind, is the addition of the -gc flag.

Using VBScript

The only difference between this solution and Recipe 4.8 is that the strBase variable changed to use the GC: progID:

strBase = "<GC://<BaseDN>>;"

See Also

Recipe 4.8 for searching for objects, Recipe 10.15, and MSDN: Searching with ActiveX Data Objects (ADO)

4.10. Searching for a Large Number of Objects

Problem

Your search is returning exactly 1,000 objects, which is only a subset of the objects you expected, and you want it to return all matching objects.

Solution

You might notice that searches with large numbers of matches stop displaying after 1,000. By default, domain controllers return a maximum of 1,000 entries from a search unless paging is enabled. This is done to prevent queries from consuming excessive resources on domain controllers by retrieving the results all at once instead of in pages or batches. The following examples are variations of Recipe 4.8, which will show how to enable paging and return all matching entries.

Using a graphical user interface

  1. Open LDP from the Windows Support Tools.

  2. From the menu, select Connection→Connect.

  3. For Server, enter the name of a domain controller (or leave blank to do a serverless bind).

  4. For Port, enter 389.

  5. Click OK.

  6. From the menu, select Connection→Bind.

  7. Enter the credentials of a user.

  8. Click OK.

  9. From the menu, select Browse→Search.

  10. For BaseDN, type the base distinguished name of where the search will start. (You can leave this blank if you wish to connect to the domain NC as the base DN.)

  11. For Scope, select the appropriate scope.

  12. For Filter, enter an LDAP filter.

  13. Click Options to customize the options for this query.

  14. For Timeout(s), enter a value such as 10.

  15. For Page size, enter the number of objects to be returned with each page (e.g., 1,000).

  16. Under Search Call Type, select Paged.

  17. Click OK and then Run to perform the query. A page of results (i.e., 1,000 entries) will be displayed each time you click Run until all results have been returned.

Using a command-line interface

> dsquery * <BaseDN> -limit 0 -scope <Scope> -filter "<Filter>" -attr "<AttrList>"

Using VBScript

' This code enables paged searching
' ------ SCRIPT CONFIGURATION ------
strBase   = "<LDAP://<BaseDN>>;"
strFilter = "<Filter>;"
strAttrs  = "<AttrList>;"
strScope  = "<Scope>"
' ------ END CONFIGURATION ---------
set objConn = CreateObject("ADODB.Connection")
objConn.Provider = "ADsDSOObject"
objConn.Open "Active Directory Provider"
set objComm = CreateObject("ADODB.Command")
objComm.ActiveConnection = objConn
objComm.Properties("Page Size") = 1000
objComm.CommandText = strBase & strFilter & strAttrs & strScope
set objRS = objComm.Execute
objRS.MoveFirst
while Not objRS.EOF
    Wscript.Echo objRS.Fields(0).Value
    objRS.MoveNext
wend

Using PowerShell

$strCategory = "computer"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 1000
$objSearcher.Filter = ("(objectCategory=$strCategory)")
$colResults = $objSearcher.FindAll()

Discussion

Paged searching support is implemented via an LDAP control. LDAP controls were defined in RFC 2251 and the Paged control in RFC 2696. Controls are extensions to LDAP that were not built into the protocol, so not all directory vendors support the same ones.

Note

In Active Directory, you can change the default maximum page size of 1,000 by modifying the LDAP query policy. See Recipe 4.27 for more information.

If you need searches to return hundreds of thousands of entries, Active Directory will return a maximum of only 262,144 entries even when paged searching is enabled. This value is defined in the LDAP query policy and can be modified like the maximum page size (see Recipe 4.27).

Using a graphical user interface

A word of caution when using LDAP to display a large number of entries—by default, only 2,048 lines will be displayed in the right pane. To change that value, go to Options→General and change the Line Value under Buffer Size to a larger number.

Using a command-line interface

The only difference between this solution and Recipe 4.8 is the addition of the -limit 0 flag. With -limit set to 0, paging will be enabled according to the default LDAP query policy; matching objects will be returned within those parameters. If -limit is not specified, a maximum of 100 entries will be returned.

Note

AdFind enables paged searches by default; it will return any number of objects from a query without any modification.

Using VBScript

To enable paged searching in ADO, you must instantiate an ADO Command object. A Command object allows for various properties of a query to be set, such as size limit, time limit, and page size. See MSDN for the complete list.

Using PowerShell

To enable paged searches in PowerShell, you will need to modify the PageSize property of the DirectorySearcher object.

The get-QADObject cmdlet also includes a –PageSize switch that will indicate the maximum results that should be returned, with a default value of 50. Similar to the –limit switch in dsquery, invoking this switch will cause paging to be enabled according to the default LDAP query policy.

See Also

Recipe 4.8 for searching for objects, Recipe 4.27 for viewing the default LDAP policy, RFC 2251 (Lightweight Directory Access Protocol [v3]), RFC 2696 (LDAP Control Extension for Simple Paged Results Manipulation), and MSDN: Searching with ActiveX Data Objects (ADO)

4.11. Searching with an Attribute-Scoped Query

Note

This recipe requires the Windows Server 2003 forest functional level or better.

Problem

You want to perform a search using an individual value within a multivalued attribute as part of the search criteria. An attribute-scoped query can do this in a single query, instead of the previous method, which required multiple queries.

Solution

Using a graphical user interface

  1. Follow the steps in Recipe 4.4 to enable an LDAP control.

  2. Select the Attribute Scoped Query control (you can select controls by name with the Windows Server 2003 and Windows Server 2008 version of LDP). For the Windows 2000 version of LDP, add a control with an OID of 1.2.840.113556.1.4.1504.

  3. For Value, enter the multivalued attribute name (e.g., member).

  4. Click the “Check in” button.

  5. Click OK.

  6. From the menu, select Browse→Search.

  7. For BaseDN, type the DN of the object that contains the multivalued attributes.

  8. For Scope, select Base.

  9. For Filter, enter an LDAP filter to match against the objects that are part of the multivalued DN attribute.

  10. Click Run.

Warning

Attribute-scoped queries can only be performed using a Base scope.

Using a command-line interface

AdFind allows attribute-scoped queries by using the -asq switch; for example:

adfind -b cn=somegroup,cn=users,dc=domain,dc=group -asq member -f objectclass=user
samaccountname

Using VBScript

You cannot use attribute-scoped queries with ADSI, ADO, and VBScript. In an ADO search, you can use the ADSI Flags property as part of a Connection object to set the search preference, but there is no way to set the attribute that should be matched, which must be included as part of the LDAP control.

Using PowerShell

The Quest AD cmdlets will allow you to perform an attribute-scoped query as follows:

Get-QADObject -SearchRoot <ObjectDN> -AttributeScopeQuery '<Attribute>'

You can also use the native ADSI methods in PowerShell:

$group  = New-Object System.DirectoryServices.DirectoryEntry(
    "LDAP://CN=Domain Admins,CN=Users,<DomainDN>")
$source = New-Object System.DirectoryServices.DirectorySearcher

$source.SearchRoot  = $group
$source.SearchScope = [System.DirectoryServices.SearchScope]::Base
$source.Filter      = "(objectClass=*)"

$source.PropertiesToLoad.Add("member")
$source.PropertiesToLoad.Add("sAMAccountName")

$source.AttributeScopeQuery = "member"

$results = $source.FindAll()

Discussion

When dealing with group objects, you may have encountered the problem where you wanted to search against the members of a group to find a subset or to retrieve certain attributes about each member. This normally involved performing a query to retrieve all of the members, and additional queries to retrieve whatever attributes you needed for each member. This was less than ideal, so an alternative was developed for Windows Server 2003.

With an attribute-scoped query, you can perform a single query against the group object and return whatever properties you need from the member’s object, or return only a subset of the members based on certain criteria. Let’s look at the LDAP search parameters for an attribute-scoped query:

Attribute-scoped query control value

The value to set for this control should be the DN attribute that you want to iterate over (e.g., member).

Base DN

This must be the DN of the object that contains the DN attribute (e.g., cn=Domain Admins,cn=users,dc=adatum,dc=com).

Scope

This must be set to Base to query only the group object itself.

Filter

The filter will match against objects defined in the Control Value. For example, a filter of (objectClass=user) would match user objects only. You can also use any other attributes that are available with those objects. The following filter would match all user objects that have a department attribute equal to “Sales”:

(&(objectclass=user)(department=Sales))
Attributes

This should contain the list of attributes to return for the objects matched in the DN attribute.

When performing an attribute-scoped query against a member attribute, it’s important to remember that primary group membership is handled as a special case; as such you may experience unpredictable results in this situation.

See Also

Recipe 4.4, MSDN: Performing an Attribute Scoped Query, and MSDN: Searching with ActiveX Data Objects (ADO)

4.12. Searching with a Bitwise Filter

Problem

You want to search against an attribute that contains a bit flag, which requires you to use a bitwise filter to perform the search.

Solution

Using a graphical user interface

  1. Open LDP from the Windows Support Tools.

  2. From the menu, select Connection→Connect.

  3. For Server, enter the name of a domain controller (or leave blank to do a serverless bind).

  4. For Port, enter 389.

  5. Click OK.

  6. From the menu, select Connection→Bind.

  7. Enter credentials of a user.

  8. Click OK.

  9. From the menu, select Browse→Search.

  10. For BaseDN, type the base distinguished name of where the search will start. (You can leave this blank if you wish to connect to the domain NC as the base DN.)

  11. For Scope, select the appropriate scope.

  12. For the Filter, enter the bitwise expression, such as the following, which will find all universal groups:

    (&(objectCategory=group)(groupType:1.2.840.113556.1.4.804:=8))
  1. Click Run.

Using a command-line interface

The following query finds universal groups in the adatum.com domain by using a bitwise AND filter:

> dsquery * dc=adatum,dc=com -scope subtree -attr "name" -filter
"(&(objectclass=group)(objectCategory=group)
(groupType:1.2.840.113556.1.4.804:=8) )"

The following query finds disabled user accounts in the adatum.com domain by using a bitwise AND filter:

> dsquery * dc=adatum,dc=com -attr name -scope subtree -filter
"(&(objectclass=user)(objectcategory=person)(useraccountcontrol:1.2.840.113556.1.4.
803:=2))"

You can also perform queries that use bitwise filters using AdFind. The following will find all disabled user accounts in the adatum.com domain:

> adfind -default -bit -f useraccountcontrol:AND:=2

Similarly, the following will return all universal groups in the adatum.com domain using a bitwise filter:

> adfind -default -bit -f groupType:AND:=8

Using VBScript

' The following query finds all disabled user accounts in the
' adatum.com domain
strBase   = "<LDAP://dc=adatum,dc=com>;"
strFilter = "(&(objectclass=user)(objectcategory=person)" & _
            "(useraccountcontrol:1.2.840.113556.1.4.803:=2));"
strAttrs  = "name;"
strScope  = "subtree"

set objConn = CreateObject("ADODB.Connection")
objConn.Provider = "ADsDSOObject"
objConn.Open "Active Directory Provider"
set objRS = objConn.Execute(strBase & strFilter & strAttrs & strScope)
objRS.MoveFirst
while Not objRS.EOF
    Wscript.Echo objRS.Fields(0).Value
    objRS.MoveNext
wend

Using PowerShell

$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.Filter =
("(&(objectclass=user)(objectcategory=person)(useraccountcontrol:1.2.840.113556.1.4
.803:=2))")
$colResults = $objSearcher.FindAll()

Discussion

Many attributes in Active Directory are composed of bit flags. A bit flag is often used to encode properties about an object into a single attribute. For example, the groupType attribute on group objects is a bit flag that is used to determine the group scope and type.

The userAccountControl attribute on user and computer objects is used to describe a whole series of properties, including account status (i.e., enabled or disabled), account lockout, password not required, smartcard authentication required, etc.

The searchFlags and systemFlags attributes on attributeSchema objects define, among other things, whether an attribute is constructed, indexed, and included as part of Ambiguous Name Resolution (ANR).

To search against these types of attributes, you need to use bitwise search filters. There are two types of bitwise search filters you can use, one that represents a logical OR and one that represents a logical AND. This is implemented within a search filter as a matching rule. A matching rule is simply a way to inform the LDAP server (in this case, a domain controller) to treat part of the filter differently. Here is an example of what a matching rule looks like:

(userAccountControl:1.2.840.113556.1.4.803:=514)

The format is (attributename:MatchingRuleOID:=value), though AdFind allows you to use an easier syntax for bitwise queries. As mentioned, there are two bitwise matching rules, which are defined by OIDs. The logical AND matching rule OID is 1.2.840.113556.1.4.803 and the logical OR matching rule OID is 1.2.840.113556.1. 4.804. These OIDs instruct the server to perform special processing on the filter. A logical OR filter will return success if any bit specified by value is stored in attributename. Alternatively, the logical AND filter will return success if all bits specified by value match the value of attributename. Perhaps an example will help clarify this.

To create a normal user account, you have to set userAccountControl to 514. The number 514 was calculated by adding the normal user account flag of 512 together with the disabled account flag of 2 (512 + 2 = 514). If you use the following logical OR matching rule against the 514 value, as shown here:

(useraccountcontrol:1.2.840.113556.1.4.804:=514)

then all normal user accounts (flag 512) OR disabled accounts (flag 2) would be returned. This would include enabled user accounts (from flag 512), disabled computer accounts (from flag 2), and disabled user accounts (from flag 2). In the case of userAccountControl, flag 2 can apply to both user and computer accounts, which is why both would be included in the returned entries.

One of the benefits of bitwise matching rules is that they allow you to combine a bunch of comparisons into a single filter. In fact, it may help to think that the OR filter could also be written using two expressions:

(|(useraccountcontrol:1.2.840.113556.1.4.804:=2)
(useraccountcontrol:1.2.840.113556.
1.4.804:=512))

Just as before, this will match userAccountControl attributes that contain either the 2 or 512 flags; we’re performing two OR operations against the same value, first ORing the value against 2, then against 512.

For the logical AND operator, similar principles apply. Instead of any of the bits in the flag being a possible match, all of the bits in the flag must match for it to return a success. If the userAccountControl example was changed to use logical AND, it would look like this:

(useraccountcontrol:1.2.840.113556.1.4.803:=514)

In this case, only normal user accounts that are also disabled would be returned. The same filter could be rewritten using the & operator instead of | as in the following:

(&(useraccountcontrol:1.2.840.113556.1.4.803:=2)
(useraccountcontrol:1.2.840.113556.1.4.803:=512))

An important subtlety to note is that when you are comparing only a single bit flag value, the logical OR and logical AND matching rule would return the same result. So if you wanted to find any normal user accounts you could search on the single bit flag of 512 using either of the following:

(useraccountcontrol:1.2.840.113556.1.4.803:=512)

(useraccountcontrol:1.2.840.113556.1.4.804:=512)

Using PowerShell

Searching on a bitwise operator in PowerShell is done using a DirectorySearcher object with the appropriate LDAP filter, as you can see. In future chapters we will look at individual AD cmdlets that “mask” the bitwise search into a more human-readable operation, such as the Enable-QADUser and Disable-QADUser Quest cmdlets.

See Also

MSDN: Enumerating Groups by Scope or Type in a Domain, MSDN: Determining Which Properties Are Non-Replicated, Constructed, Global Catalog, and Indexed, and MS KB 305144 (How to Use the UserAccountControl Flags to Manipulate User Account Properties)

4.13. Creating an Object

Problem

You want to create an object.

Solution

In each solution below, an example of adding a user object is shown. Modify the examples as needed to include whatever class and attributes you need to create.

Using a graphical user interface

  1. Open ADSI Edit from the Windows Support Tools in Windows 2000 and Windows Server 2003. (ADSI Edit is a native MMC snap-in in Windows Server 2008.)

  2. If an entry for the naming context you want to browse is not already displayed, do the following:

    1. Right-click on ADSI Edit in the right pane and click “Connect to…”.

    2. Fill in the information for the naming context, container, or OU you want to add an object to. Click on the Advanced button if you need to enter alternate credentials.

  1. In the left pane, browse to the container or OU you want to add the object to. Once you’ve found the parent container, right-click on it and select New→Object.

  2. Under Select a Class, select user.

  3. For the cn, enter jsmith and click Next.

  4. For sAMAccountName, enter jsmith and click Next.

  5. Click the More Attributes button to enter additional attributes.

  6. Click Finish.

You can also create an object using LDP as follows:

  1. Open LDP from the Windows Support Tools.

  2. From the menu, select Connection→Connect.

  3. For Server, enter the name of a domain controller (or leave blank to do a serverless bind).

  4. For Port, enter 389.

  5. Click OK.

  6. From the menu, select Connection→Bind.

  7. Enter credentials of a user.

  8. Click OK.

  9. Click Browse→Add Child. For DN, enter the Distinguished Name of the object that you want to create.

  10. Under Attribute and Values, enter the name of any attribute that you want to populate along with its associated value, and then click Enter. Repeat this until you’ve added all of the required attributes for the type of object you are creating, as well as any optional attributes that you want to populate.

  11. Click Run to create the object.

Using a command-line interface

Create an LDIF file called create_object.ldf with the following contents:

dn: cn=jsmith,cn=users,dc=adatum,dc=com
changetype: add
objectClass: user
samaccountname: jsmith

Then run the following command:

> ldifde -v -i -f create_object.ldf

It is also worth noting that you can add a limited number of object types with the dsadd command. Run dsadd /? from a command line for more details.

You can also create objects using AdMod; to create a new user object in the adatum.com domain use the following syntax:

> admod -b "cn=Joe Smith,cn=users,dc=adatum,dc=com"

objectclass::user samaccountname::jsmith -add

Using VBScript

set objUsersCont = GetObject("LDAP://cn=users,dc=adatum,dc=com")
set objUser = objUsersCont.Create("user", "CN=jsmith")
objUser.Put "sAMAccountName", "jsmith" ' mandatory in Windows 2000
objUser.SetInfo

Using PowerShell

To create an object using the Quest AD cmdlets, use the following syntax:

new-QADObject -ParentContainer "ou=Workstations,dc=adatum,dc=com" -type 'computer'
-name 'comp1' -ObjectAttributes @{samAccountName='comp1';description='New Vista
Workstation'}

To create an object using System.DirectoryServices, use the following syntax:

 $parentOU = [System.DirectoryServices.DirectoryEntry]
"LDAP://ou=Workstations,dc=adatum,dc=com"
 $newWS = $parentOU.Create("computer","test1")
 $newWS.put("samaccountName,"test1")
 $newWS.put("description","New Vista Workstation")
 $newWS.SetInfo()

Discussion

To create an object in Active Directory, you have to specify the objectClass, RDN value, and any other mandatory attributes that are not automatically set by Active Directory. Some of the automatically generated attributes include objectGUID, instanceType, and objectCategory.

In the jsmith example, the object class was user, the RDN value was jsmith, and the only other attribute set was sAMAccountName: this attribute is only mandatory in Windows 2000; it is optional in Windows Server 2003, and cannot, by default, be set in ADAM. Admittedly, this user object is unusable in its current state because it will be disabled by default and no password was set, but it should give you an idea of how to create an object. In the case of a user object, you’ll need to configure a password that meets any existing password complexity requirements before enabling the user.

Using a graphical user interface

Other tools, such as AD Users and Computers, could be used to do the same thing, but ADSI Edit is useful as a generic object editor.

One attribute that you will not be able to set via ADSI Edit is the password (unicodePwd attribute). It is stored in binary form and needs to be edited using a secure connection. If you want to set the password for a user through a GUI, you can do it with the AD Users and Computers snap-in.

Using a command-line interface

For more on ldifde, see Recipe 4.28.

With DSAdd, you can set numerous attributes when creating an object. The downside is that you can create only the following object types: computer, contact, group, OU, quota, and user.

Using VBScript

The first step in creating an object is to call GetObject on the parent container. Then call the Create method on that object and specify the objectClass and RDN for the new object. The sAMAccountName attribute is then set by using the Put method. Finally, SetInfo commits the change. If SetInfo is not called, the creation will not get committed to the domain controller.

See Also

Recipe 4.28, Recipe 4.29 for importing objects with LDIF, MSDN: IADsContainer::GetObject, MSDN: IADsContainer::Create, MSDN: IADs::Put, and MSDN: IADs::SetInfo

4.14. Modifying an Object

Problem

You want to modify one or more attributes of an object.

Solution

The following examples set the last name (sn) attribute for the jsmith user object.

Using a graphical user interface

  1. Open ADSI Edit.

  2. If an entry for the naming context you want to browse is not already displayed, do the following:

    1. Right-click on ADSI Edit in the right pane and click “Connect to…”.

    2. Fill in the information for the naming context, container, or OU you want to add an object to. Click on the Advanced button if you need to enter alternate credentials.

  1. In the left pane, browse to the container or OU that contains the object you want to modify. Once you’ve found the object, right-click on it and select Properties.

  2. Right-click the sn attribute and select Edit.

  3. Enter Smith and click OK.

  4. Click Apply, followed by OK.

You can also modify an object using LDP as follows:

  1. Open LDP from the Windows Support Tools.

  2. From the menu, select Connection→Connect.

  3. For Server, enter the name of a domain controller (or leave blank to do a serverless bind).

  4. For Port, enter 389.

  5. Click OK.

  6. From the menu, select Connection→Bind.

  7. Enter credentials of a user.

  8. Click OK.

  9. Click Browse→Modify. For DN, enter the Distinguished Name of the object that you want to modify.

  10. Under Attribute and Values, enter the name of any attribute that you want to modify along with its associated value, and then click Enter. Repeat this until you’ve added all of the attributes that you want to modify.

  11. Click Run to modify the object.

Using a command-line interface

Create an LDIF file called modify_object.ldf with the following contents:

dn: cn=jsmith,cn=users,dc=adatum,dc=com
changetype: modify
replace: givenName
givenName: Jim
-

Then run the following command:

> ldifde -v -i -f modify_object.ldf

To modify an object using AdMod, you’ll use the following general syntax:

> admod -b <ObjectDN> <attribute>:<operation>:<value>

For example, you can add a description to a user object using the following syntax:

> admod -b cn="Joe Smith,cn=Users,dc=adatum,dc=com"
      description::Consultant

You can modify a limited number of object types with the dsmod command. Run dsmod /? from a command line for more details.

Using VBScript

strObjectDN = "cn=jsmith,cn=users,dc=adatum,dc=com"
set objUser = GetObject("LDAP://" & strObjectDN)
objUser.Put "sn", "Smith"
objUser.SetInfo

Using PowerShell

To modify an object using the Quest AD cmdlets, use the following syntax:

set-QADObject -Identity <ObjectDN> @{attribute1=<Value>;attribute2=<Value>}

To modify an object using ADSI, use the following:

$objWS = [System.DirectoryServices.DirectoryEntry] "LDAP://<ObjectDN>"
$objWS.put("description","New Workstation Description")
$objWS.SetInfo()

Discussion

Using a graphical user interface

If the parent container of the object you want to modify has a lot of objects in it, you may want to add a new connection entry for the DN of the target object. This will be easier than trying to hunt through a container full of objects. You can do this by right-clicking ADSI Edit and selecting “Connect to…”. Under Connection Point, select Distinguished Name and enter the DN of the object.

Using a command-line interface

For more on ldifde, see Recipe 4.28.

As of the publication of this book, the only types of objects you can modify with DSMod are the following: computer, contact, group, OU, server, quota, and user.

As you saw in the recipe, the basic format of the AdMod command when used to modify an attribute is as follows:

> admod -b <ObjectDN> <attribute>:<operation>:<value>

The value used for <operation> can be any one of the following:

<blank>

Updates the attribute with the new value. (In practical terms, this leads to a syntax of <attribute>::<value>, with nothing included between the two colons.)

+

Adds a value to an attribute.

-

Clears an attribute.

++

Adds multiple values to an attribute.

--

Removes multiple values from an attribute.

Note

To change a user’s password using AdMod, encrypt the connection by using the -kerbenc switch, and then modify the unicodepwd attribute.

Using VBScript

If you need to do anything more than simple assignment or replacement of a value for an attribute, you’ll need to use the PutEx method instead of Put. PutEx allows for greater control of assigning multiple values, deleting specific values, and appending values.

PutEx requires three parameters: update flag, attribute name, and an array of values to set or unset. The update flags are defined by the ADS_PROPERTY_OPERATION_ENUM collection and listed in Table 4-3. Finally, SetInfo commits the change. If SetInfo is not called, the creation will not get committed to the domain controller.

Table 4-3. ADS_PROPERTY_OPERATION_ENUM

Name

Value

Description

ADS_PROPERTY_CLEAR

1

Remove all value(s) of the attribute.

ADS_PROPERTY_UPDATE

2

Replace the current values of the attribute with the ones passed in. This will clear any previously set values.

ADS_PROPERTY_APPEND

3

Add the values passed into the set of existing values of the attribute.

ADS_PROPERTY_DELETE

4

Delete the values passed in.

In the sample below, each update flag is used while setting the otherTelephoneNumber attribute:

strObjectDN = "cn=jsmith,cn=users,dc=adatum,dc=com"

const ADS_PROPERTY_CLEAR  = 1
const ADS_PROPERTY_UPDATE = 2
const ADS_PROPERTY_APPEND = 3
const ADS_PROPERTY_DELETE = 4

set objUser = GetObject("LDAP://" & strObjectDN)

' Add/Append two values
objUser.PutEx ADS_PROPERTY_APPEND, "otherTelephoneNumber", _
              Array("555-1212", "555-1213")
objUser.SetInfo
' Now otherTelephoneNumber = 555-1212, 555-1213

' Delete one of the values
objUser.PutEx ADS_PROPERTY_DELETE, "otherTelephoneNumber", Array("555-1213")
objUser.SetInfo
' Now otherTelephoneNumber = 555-1212

' Change values
objUser.PutEx ADS_PROPERTY_UPDATE, "otherTelephoneNumber", Array("555-1214")
objUser.SetInfo
' Now otherTelephoneNumber = 555-1214

' Clear all values
objUser.PutEx ADS_PROPERTY_CLEAR, "otherTelephoneNumber", vbNullString
objUser.SetInfo
' Now otherTelephoneNumber = <empty>

Using PowerShell

As of this writing, there is a bug in PowerShell relating to the use of the PutEx method to clear an attribute value. If you use the following code to clear an object’s description, for example, you will still be able to retrieve the last value set for this value even after issuing the SetInfo() command:

$obj = [System.DirectoryServices.DirectoryEntry] "LDAP://<ObjectDN>"
$obj.PutEx(1, "description", 0)
$obj.SetInfo()

Despite this bug in PowerShell, if you view the object within AD Users and Computers or another interface, you will see that the attribute value has actually been cleared.

When working with the Quest cmdlets, if you are attempting to modify a multivalued attribute, the –ObjectAttributes switch requires you to format the values to be added to attribute as an array, using the @(…) syntax. As written, this syntax will overwrite any existing information within the multivalued attribute. In order to append values using the Quest tools, you will need to create and use a Dictionary object as follows:

[Collections.DictionaryEntry] $de = New-Object Collections.DictionaryEntry 
-argumentList Append, @('<Value1>',<Value2>')
set-QADOject -Identity <Object DN> -ObjectAttributes @{siteLink=$de}

To delete values from a multivalued attribute, change –argumentList Append to –argumentList Delete.

See Also

MSDN: IADs::Put, MSDN: IADs::PutEx, MSDN: IADs::SetInfo, and MSDN: ADS_ PROPERTY_OPERATION_ENUM

4.15. Modifying a Bit Flag Attribute

Problem

You want to safely modify an attribute that contains a bit flag, without blindly overwriting its existing contents.

Solution

Using VBScript

' This code safely modifies a bit flag attribute
' ------ SCRIPT CONFIGURATION ------
strObject = "<ObjectDN>" ' e.g. cn=jsmith,cn=users,dc=adatum,dc=com
strAttr = "<AttrName>" ' e.g. adatum-UserProperties
boolEnableBit = <TRUEorFALSE> ' e.g. FALSE
intBit = <BitValue> ' e.g. 16
' ------ END CONFIGURATION --------

set objObject = GetObject("LDAP://" & strObject)
intBitsOrig = objObject.Get(strAttr)
intBitsCalc = CalcBit(intBitsOrig, intBit, boolEnableBit)

if intBitsOrig <> intBitsCalc then
   objObject.Put strAttr, intBitsCalc
   objObject.SetInfo
   WScript.Echo "Changed " & strAttr & " from " & intBitsOrig & " to " & intBitsCalc
else
   WScript.Echo "Did not need to change " & strAttr & " (" & intBitsOrig & ")"
end if

Function CalcBit(intValue, intBit, boolEnable)

   CalcBit = intValue

   if boolEnable = TRUE then
      CalcBit = intValue Or intBit
   else
      if intValue And intBit then
         CalcBit = intValue Xor intBit
      end if
   end if

End Function

Using PowerShell

To set the userAccountControl bit value using a logical OR operation, use the following syntax:

$objUser = [ADSI] "LDAP://cn=testuser,cn=users,dc=adatum,dc=com"
$newUAC = ($objUser.userAccountControl.ToString()) -bor 2
$objUser.Put("userAccountControl", $newValue)
$objUser.SetInfo()

Discussion

Recipe 4.12 described how to search against attributes that contain a bit flag, which is used to encode various settings about an object in a single attribute. As a quick recap, you need to use a logical OR operation to match any bits being searched against, and a logical AND to match a specific set of bits. If you want to set an attribute that is a bit flag, you need to take special precautions to ensure you don’t overwrite an existing bit. Let’s consider an example. Adatum wants to secretly store some politically incorrect information about their users, such as whether the user is really old or has big feet. They don’t want to create attributes such as adatum-UserHasBigFeet, so they decide to encode the properties in a single bit flag attribute. They decide to call the attribute adatum-UserProperties with the possible bit values shown in Table 4-4.

Table 4-4. Sample bit flag attribute values

Value

Description

1

User is overweight

2

User is very tall

4

User has big feet

8

User is very old

After they extend the schema to include the new attribute, Adatum needs to initially populate the attribute for all their users. To do so they can simply logically OR the values together that apply to each user. So if settings 4 and 8 apply to the jsmith user, his adatum-UserProperties would be set to 12 (4 OR 8). No big deal so far. The issue comes in when they need to modify the attribute in the future.

Note

You will, however, find that searching for information based on a bit flag attribute is not terribly efficient. This is because bit flags cannot be indexed; you need to calculate the value for every object populated with the bit flag attribute in question.

They later find out that jsmith was a former basketball player and is 6’8”. They need to set the 2 bit (for being tall) in his adatum-UserProperties attribute. To set the 2 bit they need to first determine if it has already been set. If it has already been set, then there is nothing to do. If the 2 bit hasn’t been set, they need to logical OR 2 with the existing value of jsmith’s adatum-UserProperties attribute. If they simply set the attribute to 2, it would overwrite the 4 and 8 bits that had been set previously. In the VBScript solution, they could use the CalcBit function to determine the new value:

intBitsCalc = CalcBit(intBitsOrig, 2, TRUE)

The result would be 14 (12 OR 2).

The same logic applies if they want to remove a bit, except the XOR logical operator is used.

Warning

Active Directory contains numerous bit flag attributes, most notably options (which is used on several different object classes) and userAccountControl (which is used on user objects). We do not recommend blindly setting those attributes unless you know what you are doing. It is preferable to use a script from this recipe so that it calculates the new value based on the existing value.

You should note that it’s certainly possible to modify bitwise attributes using a GUI tool like ADSI Edit or a command-line tool like DsMod. However, it will require a certain amount of manual effort as you’ll first need to make note of the existing attribute value and then calculate the new value using a calculator or some other method. The VBScript solution presented here simply automates that process by performing the lookup and calculations for you.

Using PowerShell

The PowerShell solution makes use of the built-in –bor operator in PowerShell, which performs a Boolean OR. You can also use –bxor to perform a Boolean XOR to clear the value set for a particular bit.

See Also

Recipe 4.12 for searching with a bitwise filter

4.16. Dynamically Linking an Auxiliary Class

Note

This recipe requires Windows Server 2003 or better forest functional level.

Problem

You want to dynamically link an auxiliary class to an existing object instance.

Solution

In each solution below, an example of adding the custom adatum-SalesUser auxiliary class to the jsmith user object will be described.

Using a graphical user interface

  1. Open ADSI Edit.

  2. If an entry for the naming context you want to browse is not already displayed, do the following:

    1. Right-click on ADSI Edit in the right pane and click “Connect to…”.

    2. Fill in the information for the naming context, container, or OU you want to add an object to. Click on the Advanced button if you need to enter alternate credentials.

  1. In the left pane, browse to the container or OU that contains the object you want to modify. Once you’ve found the object, right-click on it and select Properties.

  2. Right-click the sn attribute and select Edit.

  3. Click the More Attributes button to enter additional attributes.

  4. Edit the values for the objectClass attribute.

  5. For “Value to add,” enter adatum-SalesUser.

  6. Click Add.

  7. Click OK twice.

Using a command-line interface

Create an LDIF file called dynamically_link_class.ldf with the following contents:

dn: cn=jsmith,cn=users,dc=adatum,dc=com
changetype: modify
add: objectClass
objectClass: adatum-SalesUser
-

Then run the following command:

> ldifde -v -i -f dynamically_link_class.ldf

Alternatively, you can use AdMod as follows:

> admod -b <ObjectDN> objectClass:+:<Dynamic Object Class>

Using VBScript

const ADS_PROPERTY_APPEND = 3
set objUser = GetObject("LDAP://cn=jsmith,cn=users,dc=adatum,dc=com")
objUser.PutEx ADS_PROPERTY_APPEND,"objectClass",Array("adatum-SalesUser")
objUser.SetInfo

Using PowerShell

set-variable -name ADS_PROPERTY_APPEND -value 3 -option constant
$objUser = [ADSI] "LDAP://cn=testuser,cn=users,dc=adatum=,dc=com"
$objUser.PutEx(ADS_PROPERTY_APPEND, "objectClass", @("adatum-SalesUser"))
$objUser.SetInfo()

Discussion

Dynamically linking an auxiliary class to an object is an easy way to use new attributes without modifying the existing object class definition in the schema. In Windows 2000, auxiliary classes could only be statically linked in the schema. Beginning with Windows Server 2003, you can dynamically link them by appending the auxiliary class name to the objectClass attribute of an object.

A situation in which it makes more sense to dynamically link auxiliary classes rather than link them statically is when several organizations or divisions within a company maintain their own user objects and want to add new attributes to the user class. Under Windows 2000, each organization would need to create their new attributes and auxiliary class in the schema, and then modify the user class to include the new auxiliary class. If you have 10 organizations that want to do the same thing, user objects in the forest could end up with a lot of attributes that would go unused. In Windows Server 2003, each division can instead create the new attributes and auxiliary class, and then dynamically link the auxiliary class with the specific objects that they want to have the new attributes. This eliminates the step of modifying the user class in the schema to contain the new auxiliary classes.

It is also worth mentioning that extensive use of dynamically linked auxiliary classes can lead to problems. If several groups are using different auxiliary classes, it might become hard to determine what attributes you can expect on your user objects. Essentially, you could end up with many variations of a user class that each group has implemented through the use of dynamic auxiliary classes. For this reason, use of dynamic auxiliary classes should be closely monitored. In addition, some tools that access Active Directory may not work properly with auxiliary classes.

See Also

Recipe 4.14 for modifying an object

4.17. Creating a Dynamic Object

Note

This recipe requires the Windows Server 2003 or better forest functional level.

Problem

You want to create an object that is automatically deleted after a period of time unless it is refreshed.

Solution

Using a graphical user interface

  1. Open LDP. Click Connection→Connect and click OK.

  2. Click Connection→Bind. Enter the appropriate user credentials, or just click OK.

  3. Click View→Tree. Enter the DN of the parent container of the object you want to create, then click OK.

  4. Click Browse→Add Child. The Add window appears.

  5. In the DN text box, enter the DN of the new object.

  6. In the Attribute text box, enter objectClass. In the Values: text box, enter the object class of the object you are creating, such as 'user'. Click Enter. In the Values: text box, enter dynamicObject and click Enter.

  7. In the Attribute text box, enter entryTTL. In the Values: text box, enter the TTL of the object you are creating, such as '3600'. Click Enter.

  8. Enter any other attributes and values that you wish to populate in the Attribute and Values: text boxes.

  9. Click Run.

Using a command-line interface

Create an LDIF file called create_dynamic_object.ldf with the following contents:

dn: cn=jsmith,cn=users,dc=adatum,dc=com
changetype: add
objectClass: user
objectClass: dynamicObject
entryTTL: 1800
sAMAccountName: jsmith

Then run the following command:

> ldifde -v -i -f create_dynamic_object.ldf

Using VBScript

' This code creates a
' dynamic user object with a TTL of 30 minutes (1800 secs)
set objUsersCont = GetObject("LDAP://cn=users,dc=adatum,dc=com")
set objUser = objUsersCont.Create("user", "CN=jsmith")
objUser.Put "objectClass", "dynamicObject"
objUser.Put "entryTTL", 1800
objUser.Put "sAMAccountName", "jsmith" ' mandatory attribute
objUser.SetInfo

Using PowerShell

set-variable -name ADS_PROPERTY_APPEND -value 3 -option constant
$parentOU = [System.DirectoryServices.DirectoryEntry]
"LDAP://ou=adatumUsers,dc=adatum,dc=com"
$objUser = $parentOU.Create("user","TestUser1")
$objUser.put("samaccountName,"testuser1")
$objUser.put("description","New Vista Workstation")
$objUser.Put("objectClass", "dynamicObject")
$objUser.Put("entryTTL", "1800)
$objUser.Put("sAMAccountName', "jsmith")
$objUser.SetInfo()

Discussion

The ability to create dynamic objects was introduced in Windows Server 2003. This gives you the ability to create objects that have a limited lifespan before they are automatically removed from the directory. To create a dynamic object, you simply need to specify the objectClass to have a value of dynamicObject in addition to its structural objectClass (e.g., user) value when instantiating the object. The entryTTL attribute can also be set to the number of seconds before the object is automatically deleted. If entryTTL is not set, the object will use the dynamicObjectDefaultTTL attribute specified in the domain. The entryTTL cannot be lower than the dynamicObjectMinTTL for the domain. See Recipe 4.19 for more information on how to view and modify these default values.

Dynamic objects have a few special properties worth noting:

  • A static object cannot be turned into a dynamic object. The object must be marked as dynamic when it is created.

  • Dynamic objects cannot be created in the Configuration NC and Schema NC.

  • Dynamic objects do not leave behind tombstone objects.

  • Dynamic objects that are containers cannot have static child objects.

  • A dynamic container will not expire prior to any child objects contained within it. If the dynamic container has a lower TTL value than any of the children, once the container’s TTL expires, it will be reset to the highest TTL value of the children plus one second.

See Also

Recipe 4.18 for refreshing a dynamic object and Recipe 4.19 for modifying the default dynamic object properties

4.18. Refreshing a Dynamic Object

Note

This recipe requires the Windows Server 2003 or Windows Server 2008 forest functional level.

Problem

You want to refresh a dynamic object to keep it from expiring and getting deleted from Active Directory.

Solution

In each solution below, an example of adding a user object is used. Modify the examples as needed to refresh whatever object is needed.

Using a graphical user interface

  1. Open LDP.

  2. From the menu, select Connection→Connect.

  3. For Server, enter the name of a domain controller (or leave it blank to do a serverless bind).

  4. For Port, enter 389.

  5. Click OK.

  6. From the menu, select Connection→Bind.

  7. Enter credentials of a user who can modify the object.

  8. Click OK.

  9. Select Browse→Modify.

  10. For DN, enter the DN of the dynamic object you want to refresh.

  11. For Attribute, enter entryTTL.

  12. For Values, enter the new time to live (TTL) for the object in seconds.

  13. Under Operation, select Replace.

  14. Click Enter.

  15. Click Run.

Using a command-line interface

Create an LDIF file called refresh_dynamic_object.ldf with the following contents:

dn: cn=jsmith,cn=users,dc=adatum,dc=com
changetype: modify
replace: entryTTL
entryTTL: 1800
-

Then run the following command:

> ldifde -v -i -f refresh_dynamic_object.ldf

You can also use AdMod with the following syntax:

> admod -b <ObjectDN> entryTTL::<TTL in Seconds>

Using VBScript

set objUser = GetObject("LDAP://cn=jsmith,cn=users,dc=adatum,dc=com")
objUser.Put "entryTTL", "1800"
objUser.SetInfo

Using PowerShell

To refresh a dynamic object using the Quest AD cmdlets, use the following syntax:

set-QADObject -Identity <ObjectDN> @{entryTTL=1800}

To modify an object using ADSI, use the following:

$objDyn = [System.DirectoryServices.DirectoryEntry] "LDAP://<ObjectDN>"
$objDyn.put("entryTTL","1800")
$objDyn.SetInfo()

Discussion

Dynamic objects expire after their TTL becomes 0. You can determine when a dynamic object will expire by looking at the current value of an object’s entryTTL attribute or by querying msDS-Entry-Time-To-Die, which contains the seconds remaining until expiration. If you’ve created a dynamic object and need to refresh it so that it will not get deleted, you must reset the entryTTL attribute to a new value. There is no limit to the number of times you can refresh a dynamic object. As long as the entryTTL value does not reach 0, the object will remain in Active Directory.

See Also

Recipe 4.14 for modifying an object and Recipe 4.17 for creating a dynamic object

4.19. Modifying the Default TTL Settings for Dynamic Objects

Problem

You want to modify the minimum and default TTLs for dynamic objects.

Solution

In each solution below, we’ll show how to set the DynamicObjectDefaultTTL setting to 172800. Modifying the DynamicObjectMinTTL can be done in the same manner.

Using a graphical user interface

  1. Open ADSI Edit.

  2. If an entry for the Configuration naming context is not already displayed, do the following:

    1. Right-click on ADSI Edit in the right pane and click “Connect to…”.

    2. Fill in the information for the naming context for your forest. Click on the Advanced button if you need to enter alternate credentials.

  3. In the left pane, browse to the following path under the Configuration naming context: Services→Windows NT→Directory Service.

  4. Right-click cn=Directory Service and select Properties.

  5. Edit the msDS-Other-Settings attribute.

  6. Click on DynamicObjectDefaultTTL= <xxxxx> and click Remove.

  7. The attribute/value pair should have been populated in the “Value to add” field.

  8. Edit the number part of the value to be 172800.

  9. Click Add.

  10. Click OK twice.

Using a command-line interface

The following ntdsutil command connects to <DomainControllerName>, displays the current values for the dynamic object TTL settings, sets the DynamicObjectDefaultTTL to 172800, commits the change, and displays the results:

> ntdsutil "config settings" connections "connect to server <DomainControllerName>"
q "show values" "set DynamicObjectDefaultTTL to 172800" "commit changes"
"show values" q q

Using VBScript

' This code modifies the
' default TTL setting for dynamic objects in a forest
' ------ SCRIPT CONFIGURATION ------
strNewValue = 172800

' Could be DynamicObjectMinTTL instead if you wanted to set that instead
strTTLSetting = "DynamicObjectDefaultTTL"
' ------ END CONFIGURATION --------

const ADS_PROPERTY_APPEND = 3
const ADS_PROPERTY_DELETE = 4

set objRootDSE = GetObject("LDAP://RootDSE")
set objDS = GetObject("LDAP://CN=Directory Service,CN=Windows NT," & _
                      "CN=Services,CN=Configuration," & _
                      objRootDSE.Get("rootDomainNamingContext"))
for each strVal in objDS.Get("msDS-Other-Settings")
   Set objRegEx = New RegExp
   objRegEx.Pattern = strTTLSetting & "="
   objRegEx.IgnoreCase = True
   Set colMatches = objRegEx.Execute(strVal)
   For Each objMatch in colMatches
      Wscript.Echo "Deleting " & strVal
      objDS.PutEx ADS_PROPERTY_DELETE, "msDS-Other-Settings", Array(strVal)
      objDS.SetInfo
   Next
Next

Wscript.Echo "Setting " & strTTLSetting & "=" & strNewValue
objDS.PutEx ADS_PROPERTY_APPEND, _
            "msDS-Other-Settings", _
            Array(strTTLSetting & "=" & strNewValue)
objDS.SetInfo

Discussion

Two configuration settings apply to dynamic objects:

dynamicObjectDefaultTTL

Defines the default TTL that is set for a dynamic object at creation time unless another one is set via entryTTL.

dynamicObjectMinTTL

Defines the smallest TTL that can be configured for a dynamic object.

Unfortunately, these two settings are not stored as discrete attributes. Instead, they are stored as attribute value assertions (AVAs) in the msDS-Other-Settings attribute on the cn=DirectoryServices,cn=WindowsNT,cn=Configuration,<ForestRootDN> object. AVAs are used occasionally in Active Directory on multivalued attributes, in which the values take the form of Setting1=Value1,Setting2=Value2, etc.

For this reason, you cannot simply manipulate AVA attributes as you would another attribute. You have to be sure to add or replace values with the same format, as they existed previously.

Using a command-line interface

You can use ntdsutil in interactive mode or in single-command mode. In this solution, we’ve included all the necessary commands on a single line. You can, of course, step through each command by simply running ntdsutil in interactive mode and entering each command one by one.

Using VBScript

Because you are dealing with AVAs, the VBScript solution is not very straightforward. Getting a pointer to the Directory Service object is easy, but then you must step through each value of the mSDS-Other-Settings attribute until you find the one you are looking for. It is not straightforward because you do not know the exact value of the setting you are looking for; all you know is that it begins with DynamicObjectDefaultTTL=. That is why it is necessary to resort to regular expressions. With a regular expression, you can compare each value against DefaultObjectDefaultTTL= and if you find a match, delete that value only. After you’ve iterated through all of the values and hopefully deleted the one you are looking for, you append the new setting using PutEx. Simple as that!

See Also

Recipe 4.14 for modifying an object and MSDN: Regular Expression (RegExp) Object

4.20. Moving an Object to a Different OU or Container

Problem

You want to move an object to a different container or OU.

Solution

Using a graphical user interface

  1. Open ADSI Edit.

  2. If an entry for the naming context you want to browse is not already displayed, do the following:

    1. Right-click on ADSI Edit in the right pane and click “Connect to…”.

    2. Fill in the information for the naming context, container, or OU containing the object. Click on the Advanced button if you need to enter alternate credentials.

  3. In the left pane, browse to the container or OU that contains the object you want to modify. Once you’ve found the object, right-click on it and select Move.

  4. Browse to the new parent of the object, select it, and click OK.

Note

You can also move most objects within the Active Directory Users and Computers MMC snap-in (dsa.msc) by navigating to the object in question, right-clicking on it, and selecting Move. In Windows Server 2003 and later, you can also drag-and-drop the object to its new location.

Using a command-line interface

To move an object to a new parent container within the same domain, you can use either dsmove or admod, as follows:

> dsmove "<ObjectDN>" -newparent "<NewParentDN>"

Or:

> admod -b <ObjectDN> -move <NewParentDN>

Using VBScript

' This code moves an object from one location to another in the same domain.
' ------ SCRIPT CONFIGURATION ------
strNewParentDN = "LDAP://<NewParentDN>"
strObjectDN    = "LDAP://cn=jsmith,<OldParentDN>"
strObjectRDN   = "cn=jsmith"
' ------ END CONFIGURATION --------

set objCont = GetObject(strNewParentDN)
objCont.MoveHere strObjectDN, strObjectRDN

Using PowerShell

To move an Active Directory object using the Quest AD cmdlets, use the following syntax:

move-QADObject -identity <ObjectDN> -newparent <NewParentDN>

To use the .NET methods, use the following syntax:

$obj = [ADSI] "LDAP://<Object DN>"
$newParent = [ADSI] "LDAP://<New Parent DN>"
$obj.psbase.MoveTo($newParent)

Discussion

Using a graphical user interface

If the parent container of the object you want to move has a lot of objects in it, you may want to add a new connection entry for the DN of the object you want to move. This may save you time searching through the list of objects in the container. You can do this by right clicking ADSI Edit and selecting “Connect to…”. Under Connection Point, select Distinguished Name and enter the DN of the object you want to move.

Using a command-line interface

The DSMove utility can work against any type of object (it has no limitations, as with DSAdd and DSMod). The first parameter is the DN of the object to be moved. The second parameter is the new parent container of the object. The -s parameter can additionally be used to specify a specific server to work against.

Using VBScript

The MoveHere method can be tricky, so an explanation of how to use it to move objects is in order. First, you need to call GetObject on the new parent container. Then call MoveHere on the parent container object with the ADsPath of the object to move as the first parameter and the RDN of the object to move as the second.

The reason for the apparent duplication of cn=jsmith in the MoveHere method is that the same method can also be used for renaming objects within the same container (see Recipe 4.23).

Note

Regardless of the method you use to move objects, you need to ensure that the user who is performing the move has the appropriate permissions to create objects in the destination container and delete objects from the source container.

Using PowerShell

When using the .NET methods within PowerShell, a number of these methods are actually presented within a PowerShell wrapper to allow you to call many different methods in a similar fashion. You can think of this wrapper as being similar to a view in a database table, where you are being presented with a specific way of looking at the information, but there may be (and likely is) a great deal more lurking under the surface. In cases where you need to get “under the surface” in PowerShell, you will use the psbase keyword, which will allow you access to all available .NET methods. Using psbase exposes a number of additional methods and properties, including MoveTo().

See Also

Recipe 4.23, MS KB 313066 (How to Move Users, Groups, and Organizational Units Within a Domain in Windows 2000), and MSDN: IADsContainer::MoveHere

4.21. Moving an Object to a Different Domain

Problem

You want to move an object to a different domain.

Solution

Using a graphical user interface

To migrate user, computer, group, or OU objects between domains in the same forest, use the following steps:

  1. Open the Active Directory Migration Tool (ADMT) MMC snap-in.

  2. Right-click on the Active Directory Migration Tool folder, and select one of the following:

    • User Account Migration Wizard

    • Group Account Migration Wizard

    • Computer Migration Wizard

Using a command-line interface

To migrate objects from the command line using the ADMT utility, use the following syntax:

> ADMT [ USER | GROUP | COMPUTER | SECURITY | SERVICE |
       REPORT | KEY | PASSWORD | CONFIG | TASK ] <Options>

For example, to migrate a computer object, you would use the following syntax:

> ADMT COMPUTER /N <ComputerName> /SD:<Source Domain> /TD:<Target Domain>
/TO:<Target OU>

To move an object using the movetree Resource Kit utility, use the following syntax:

> movetree /start /s SourceDC /d TargetDC /sdn SourceDN /ddn TargetDN

In the following example, the cn=jsmith object in the amer.adatum.com domain will be moved to the emea.adatum.com domain:

> movetree /start /s dc-amer1 /d dc-emea1\
  /ddn cn=jsmith,cn=users,dc=amer,dc=adatum,dc=com\
  /sdn cn=jsmith,cn=users,dc=emea,dc=adatum,dc=com\

Note

Movetree should only be used to migrate object types such as contact objects that cannot currently be migrated by ADMT. In all other cases, Microsoft recommends using ADMT to move objects between domains.

Using VBScript

set objObject = GetObject("LDAP://TargetDC/TargetParentDN")
objObject.MoveHere "LDAP://SourceDC/SourceDN", vbNullString

In the following example, the cn=jsmith object in the amer.adatum.com domain will be moved to the emea.adatum.com domain:

set objObject = GetObject( _
   "LDAP://dc-amer1/cn=users,dc=amer,dc=adatum,dc=com")
objObject.MoveHere _
   "LDAP://dc-emea1/cn=jsmith,cn=users,dc=emea,dc=adatum,dc=com", _
   vbNullString

Discussion

You can move objects between domains assuming you follow a few guidelines:

  • The user performing the move operation must have permission to modify objects in the parent container of both domains.

  • You need to explicitly specify the target DC (serverless binds usually do not work). This is necessary because the “Cross Domain Move” LDAP control is being used behind the scenes. For more information on controls, see Recipe 4.4.

  • The move operation must be performed against the RID master for both domains.

  • Both domains must be in Windows 2000 native mode or higher.

  • When you move a user object to a different domain, its objectSID is replaced with a new SID (based on the new domain), and the old SID is optionally added to the sIDHistory attribute.

See Also

Recipe 4.4 for more information on LDAP controls, MS KB 238394 (How to Use the MoveTree Utility to Move Objects Between Domains in a Single Forest), MSDN: IADsContainer::MoveHere, and MS KB 326480 (How to Use Active Directory Migration Tool version 2 to migrate from Windows 2000 to Windows Server 2003)

4.22. Referencing an External Domain

Problem

You need to create a reference to an external Active Directory domain.

Solution

Using a graphical user interface

  1. Open ADSI Edit from the Windows Support Tools.

  2. If an entry for the naming context you want to browse is not already displayed, do the following:

    1. Right-click on ADSI Edit in the right pane and click “Connect to…”.

    2. Fill in the information for the naming context, container, or OU you want to add an object to. Click on the Advanced button if you need to enter alternate credentials.

  3. Right-click on the top-level node and open a connection to the Configuration NC.

  4. Right-click on the Partitions container and select New→Object. Click Next.

  5. Right-click on crossRef and click Next.

  6. For the cn attribute, enter the FQDN of the external domain, othercorp.com for example. Click Next.

  7. For the nCName attribute, enter the DN of the external domain, such as dc=othercorp,dc=com. Click Next.

  8. For the dnsRoot attribute, enter the DNS name of a server that can respond to LDAP queries about the domain in question, such as dc1.othercorp.com.

  9. Click Next and then Finish to create the crossRef object.

Using a command-line interface

Create an LDIF file called create_crossref.ldf with the following contents:

dn: cn=othercorp.com,cn=partitions,cn=configuration,dc=adatum,dc=com
changetype: add
objectClass: crossRef
cn: othercorp.com
nCName: dc=othercorp,dc=com
dnsRoot: dc1.othercorp.com

Then run the following command:

> ldifde -v -i -f create_crossref.ldf

You can also create a crossRef using AdMod as follows:

> admod -config -rb cn=othercorp.com,cn=partitions
objectClass::crossRef cn::othercorp.com nCName::dc=othercorp,dc=com
dnsRoot::dc1.othercorp.com -add

Using VBScript

set objPartitions =
GetObject("LDAP://cn=partitions,cn=configuration,dc=adatum,dc=com")
set objCrossRef = objPartitions.Create("crossRef", "CN=othercorp.com")
objCrossRef.Put "cn", "othercorp.com" ' mandatory attribute
objCrossRef.Put "nCName", "dc=othercorp,dc=com" ' mandatory attribute
objCrossRef.Put "dnsRoot", "dc1.othercorp.com" ' mandatory attribute
objCrossRef.SetInfo

Using PowerShell

To create a crossref object using the Quest AD cmdlets, use the following syntax:

 new-QADObject -ParentContainer "cn=partitions,cn=configuration,<Forest Root DN>"
-type 'crossref' -name 'othercorp.com' -ObjectAttributes
@{cn='othercorp.com';nCName='dc=othercorp,dc=com';'dnsRoot'='dc1.othercorp.com'}

To create an object using System.DirectoryServices, use the following syntax:

$parentOU = [System.DirectoryServices.DirectoryEntry]
"LDAP://cn=Partitions,cn=Configuration,<Forest Root DN>"
$newCrossRef = $parentOU.Create("crossRef","othercorp.com")
$newCrossRef.put("cn,"cn=othercorp.com")
$newCrossRef.put("nCName","dc=othercorp,dc=com")
$newCrossRef.put("dnsRoot","dc1.othercorp.com")
$newCrossRef.SetInfo()

Discussion

Similar to the way in which DNS servers use iterative queries to resolve hostnames that can only be resolved by remote servers, LDAP uses referrals to resolve queries for objects contained in naming contexts that are not hosted by the local DC. When a DC receives any query, it will search the Partitions container for a crossRef object containing the DN that’s being used as the Base DN of the query. If the DC locates a crossRef that matches the search base of the query, and that crossRef indicates a naming context that’s hosted by the domain controller itself, then the DC will perform the search locally. If the crossRef refers to an NC that’s hosted on a remote server, the DC generates a referral to the server that is pointed to by the crossRef object. If the DC can’t locate a relevant crossRef object, it will use DNS to attempt to generate an additional location to refer the client to.

In most cases, Active Directory will generate LDAP referrals automatically. However, you should manually create a crossRef object to generate LDAP referrals for an external domain, such as referrals to othercorp.com that are generated by the adatum.com domain.

See Also

MS KB 241737 (How to Create a Cross-Reference to an External Domain in Active Directory), MS KB 817872 (How to Create crossRef Objects for a DNS Namespace Subordinate of an Existing Active Directory Forest), MSDN: Referrals [Active Directory], and MSDN: When Referrals Are Generated [Active Directory]

4.23. Renaming an Object

Problem

You want to rename an object and keep it in its current container or OU.

Solution

Using a graphical user interface

  1. Open ADSI Edit.

  2. If an entry for the naming context you want to browse is not already displayed, do the following:

    1. Right-click on ADSI Edit in the right pane and click “Connect to…”.

    2. Fill in the information for the naming context, container, or OU that contains the object you want to rename. Click on the Advanced button if you need to enter alternate credentials.

  3. In the left pane, browse to the container or OU that contains the object you want to modify. Once you’ve found the object, right-click on it and select Rename.

  4. Enter the new name and click OK.

You can also rename a leaf object by using LDP as follows:

  1. Open LDP from the Windows Support Tools.

  2. From the menu, select Connection→Connect.

  3. For Server, enter the name of a domain controller (or leave blank for a serverless bind).

  4. For Port, enter 389.

  5. Click OK.

  6. From the menu, select Connection→Bind.

  7. Enter credentials of a user.

  8. Click OK.

  9. Click Browse→Modify RDN. For Old DN, enter the Distinguished Name of the object that you want to rename. For New DN, enter the object’s new name.

  10. Click Run to rename the object.

Using a command-line interface

To rename an object using the built-in DSMove utility, use the following syntax:

> dsmove "<ObjectDN>" -newname "<NewName>"

To use AdMod, use the following:

> admod -b "<ObjectDN>" -rename "<NewName>"

Using VBScript

' This code renames an object and leaves it in the same location.
' ------ SCRIPT CONFIGURATION ------
strCurrentParentDN = "<CurrentParentDN>"
strObjectOldName = "cn=<OldName>"
strObjectNewName = "cn=<NewName>"
' ------ END CONFIGURATION --------

set objCont = GetObject("LDAP://" & strCurrentParentDN)
objCont.MoveHere "LDAP://" & strObjectOldName & "," & _
                 strCurrentParentDN, strObjectNewName

Using PowerShell

To rename an object using the Quest AD cmdlets, use the following syntax:

 Rename-QADObject -identity '<Object DN>' -NewName '<New Value of 'name'
attribute>'

To rename an object using the .NET methods, use the following:

$obj = [ADSI] "LDAP://<Object DN>"
$newName = "<New Value of 'name' Attribute>"
$obj.psbase.Rename($newName)

Discussion

Before you rename an object, you should ensure that no applications reference it by name. You can make objects rename-safe by requiring all applications that must store a reference to an object to use the GUID of the object, rather than the name.

The GUID (stored in the objectGUID attribute) is effectively unique and does not change when an object is renamed.

Note

Keep in mind that you may wish to perform other cleanup tasks when renaming an object. In the case of a user who is changing her name, you may wish to update her Display Name and sn attributes to match the new CN.

Using a graphical user interface

If the parent container of the object you want to rename has a lot of objects in it, you may want to add a new connection entry for the DN of the object you want to rename. This may save you time searching through the list of objects in the container. You can do this by right-clicking ADSI Edit and selecting “Connect to…” under Connection Point; select Distinguished Name and enter the DN of the object you want to rename.

You can also rename most objects within the Active Directory Users and Computers MMC snap-in (dsa.msc) by navigating to the object in question, right-clicking on it, and selecting Rename.

Using a command-line interface

The two parameters that are needed to rename an object are the original DN of the object and the new RDN (-newname). The -s option can also be used to specify a server name to work against.

Using VBScript

The MoveHere method can be tricky to use, so an explanation of how to use it to rename objects is in order. First, you need to call GetObject on the parent container of the object you want to rename. Then call MoveHere on the parent container object and specify the ADsPath of the object to rename as the first parameter. The new RDN, including the prefix (e.g., cn=) of the object, should be the second parameter.

See Also

MSDN: IADsContainer::MoveHere

4.24. Deleting an Object

Problem

You want to delete an individual object.

Solution

Using a graphical user interface

  1. Open ADSI Edit.

  2. If an entry for the naming context you want to browse is not already displayed, do the following:

    1. Right-click on ADSI Edit in the right pane and click “Connect to…”.

    2. Fill in the information for the naming context, container, or OU that contains the object you want to delete. Click on the Advanced button if you need to enter alternate credentials.

  3. In the left pane, browse to the object you want to delete.

  4. Right-click on the object and select Delete.

  5. Click Yes to confirm.

You can also delete an object using LDP, as follows:

  1. Open LDP from the Windows Support Tools.

  2. From the menu, select Connection→Connect.

  3. For Server, enter the name of a domain controller (or leave blank for a serverless bind).

  4. For Port, enter 389.

  5. Click OK.

  6. From the menu, select Connection→Bind.

  7. Enter credentials of a user.

  8. Click OK.

  9. Click Browse→Delete. For DN, enter the Distinguished Name of the object that you want to delete.

  10. Click Run to delete the object.

Using a command-line interface

You can delete an object using the built-in dsrm utility, as well as AdMod. For dsrm, use the following syntax:

> dsrm "<ObjectDN>"

For AdMod, enter the following:

> admod -b "<ObjectDN>" -del

Using VBScript

strObjectDN = "<ObjectDN>"
set objUser = GetObject("LDAP://" & strObjectDN)
objUser.DeleteObject(0)

Using PowerShell

To delete an object using the Quest AD cmdlets, use the following syntax:

remove-QADObject -identity <Object DN>

To delete an object using the .NET methods, use the following:

$obj = [ADSI] "LDAP://<Object DN>"
$obj.DeleteObject(0)

Discussion

This recipe covers deleting individual objects. If you want to delete a container or OU and all the objects in it, take a look at Recipe 4.25.

Using a graphical user interface

If the parent container of the object you want to delete has a lot of objects in it, you may want to add a new connection entry for the DN of the object you want to delete. This can save you time searching through the list of objects in the container and could help avoid accidental deletions. You can do this by right-clicking ADSI Edit and selecting “Connect to…”. Under Connection Point, select Distinguished Name and enter the DN of the object you want to delete.

You can also delete most objects within the Active Directory Users and Computers MMC snap-in (dsa.msc) by navigating to the object in question, right-clicking on it, and selecting Delete.

Using a command-line interface

The dsrm utility can be used to delete any type of object (there are no limitations based on object type, as with dsadd and dsmod). The only required parameter is the DN of the object to delete. You can also specify -noprompt to keep it from asking for confirmation before deleting. The -s parameter can be used as well to specify a specific server to target. AdMod will not prompt you in this manner.

Using VBScript

Using the DeleteObject method is straightforward. Passing 0 as a parameter is required, but does not have any significance at present.

An alternate and perhaps safer way to delete objects is to use the IADsContainer::Delete method. To use this method, you must first bind to the parent container of the object. You can then call Delete by passing the object class and RDN of the object you want to delete. Here is an example for deleting a user object:

set objCont = GetObject("LDAP://ou=Sales,dc=adatum,dc=com")
objCont.Delete "user", "cn=rallen"

Delete is safer than DeleteObject because you have to be more explicit about what you are deleting. With DeleteObject you only need to specify a distinguished name and it will be deleted. If you happen to mistype the DN or the user input to a web page that uses this method is mistyped, the result could be disastrous.

See Also

Recipe 4.25 for deleting a container, MS KB 258310 (Viewing Deleted Objects in Active Directory), MSDN: IADsContainer::Delete, and MSDN: IADsDeleteOps:: DeleteObject

4.25. Deleting a Container That Has Child Objects

Problem

You want to delete a container or organizational unit and all child objects contained within.

Solution

Using a graphical user interface

Open ADSI Edit and follow the same steps as in Recipe 4.24. The only difference is that you’ll be prompted to confirm twice instead of once before the deletion occurs.

Using a command-line interface

You can delete a container and its child objects using the built-in dsrm utility, as well as AdMod. For dsrm, use the following syntax:

> dsrm "<ObjectDN>" -subtree

For AdMod, enter the following:

> admod -b "<ObjectDN>" -del -treedelete

Using VBScript

The same code from Recipe 4.24 will also delete containers and objects contained within them.

Using PowerShell

To delete an object using the Quest AD cmdlets, use the following syntax:

remove-QADObject -identity <Object DN> -DeleteTree

To delete an object using the .NET methods, use the following:

$obj = [System.DirectoryServices.DirectoryEntry] "LDAP://<Object DN>"
$obj.psbase.DeleteTree()

Discussion

As you can see from the solutions, there is not much difference between deleting a leaf node versus deleting a container that has child objects. However, there is a distinction in what is happening in the background.

Deleting an object that has no children can be done with a simple LDAP delete operation. On the other hand, to delete a container and its children, the tree-delete LDAP control has to be used. If you were to do the deletion from an LDAP-based tool like LDP (the Active Directory Administration Tool), you would first need to enable the Subtree Delete control, which has an OID of 1.2.840.113556.1.4.805. LDP provides another option to do a Recursive Delete from the client side. That will essentially iterate through all the objects in the container, deleting them one by one. The Subtree Delete is much more efficient, especially when dealing with large containers.

As with the other operations we’ve discussed in this chapter (create, rename, move, etc.), the user performing the delete operation needs to have the necessary permissions to delete the object or objects in question. Active Directory permissions are discussed more extensively in Chapter 14.

Using PowerShell

As you can see, deleting a container object through PowerShell is similar to deleting a leaf object, and should be used with care as a result. The Quest AD cmdlet requires the use of the –DeleteTree switch in order to delete a container object rather than a leaf object.

See Also

Recipe 4.24 for information about deleting objects, Chapter 14, and MSDN: IADsDeleteOps::DeleteObject

4.26. Viewing the Created and Last Modified Timestamp of an Object

Problem

You want to determine when an object was either created or last updated.

Solution

Using a graphical user interface

  1. Follow the steps in Recipe 4.2.

  2. Ensure that createTimestamp and modifyTimestamp are included in the list of attributes to be returned by looking at Attributes under Options→Search.

Using a command-line interface

You can view the created and modified timestamps using the built-in DSQuery utility, as well as AdFind. For DSQuery, use the following syntax:

> dsquery * "<ObjectDN>" -attr name createTimestamp modifyTimestamp

For AdFind, use the following:

> adfind -default -rb cn=Users -f "cn=Joe Smith"
createTimestamp modifyTimestamp

Using VBScript

' This code prints the created and last modified timestamp
' for the specified object.
' ------ SCRIPT CONFIGURATION ------
strObjectDN = "<ObjectDN>"
' ------ END CONFIGURATION --------

set objEntry = GetObject("LDAP://" & strObjectDN)
Wscript.Echo "Object Name: " & objEntry.Get("name")
Wscript.Echo " Created: " & objEntry.Get("createTimestamp")
Wscript.Echo " Changed: " & objEntry.Get("modifyTimestamp")

Using PowerShell

The following code uses first the Quest AD cmdlets, followed by the .NET methods:

$obj1 = get-QADObject 'cn=administrator,cn=users,dc=adatum,dc=com'
$obj1.DirectoryEntry.whenChanged
$obj2 = [System.DirectoryServices.DirectoryEntry] "LDAP://<ObjectDN>"
$obj2.whenCreated

Discussion

When an object is created or modified in Active Directory, the createTimestamp and modifyTimestamp attributes get set with the current time. The createTimestamp attribute is replicated between domain controllers, so assuming the latest modification of the object in question has replicated to all domain controllers, they will all contain the timestamp when the object was created. whenChanged and modifyTimestamp are not replicated, which means that their values will be local to an individual domain controller. Additionally, modifyTimestamp is a constructed attribute.

See Also

Recipe 4.2 for viewing the attributes of an object and Chapter 12 for a more detailed description of the Active Directory replication process

4.27. Modifying the Default LDAP Query Policy

Problem

You want to view or modify the default LDAP query policy of a forest. The query policy contains settings that restrict search behavior, such as the maximum number of entries that can be returned from a search.

Solution

Using a graphical user interface

  1. Open ADSI Edit.

  2. In the Configuration partition, browse to Services→Windows NT→Directory Service→Query Policies.

  3. In the left pane, click on the Query Policies container, then right-click on the Default Query Policy object in the right pane, and select Properties.

  4. Double-click on the lDAPAdminLimits attribute.

  5. Click on the attribute you want to modify and click Remove.

  6. Modify the value in the “Value to add” box and click Add.

  7. Click OK twice.

Using a command-line interface

To view the current settings, use the following command:

> ntdsutil "ldap pol" conn "con to server <DomainControllerName>" q "show values"

To change the MaxPageSize value to 2000, you can do the following:

> ntdsutil "ldap pol" conn "con to server <DomainControllerName>" q
ldap policy: set MaxPageSize to 2000
ldap policy: Commit Changes

Using VBScript

' This code modifies a setting of the default
' query policy for a forest
' ------ SCRIPT CONFIGURATION ------
pol_attr  = "MaxPageSize" ' Set to the name of the setting you want to modify
new_value = 1000          ' Set to the value of the setting you want modify
' ------ END CONFIGURATION ---------
Const ADS_PROPERTY_APPEND = 3
Const ADS_PROPERTY_DELETE = 4

set rootDSE = GetObject("LDAP://RootDSE")
set ldapPol = GetObject("LDAP://cn=Default Query Policy,cn=Query-Policies," & _
                "cn=Directory Service,cn=Windows NT,cn=Services," & _
                rootDSE.Get("configurationNamingContext") )
set regex = new regexp
regex.IgnoreCase = true
regex.Pattern = pol_attr & "="
for Each prop In ldapPol.GetEx("ldapAdminLimits")
   if regex.Test(prop) then
      if prop = pol_attr & "=" & new_value then
         WScript.Echo pol_attr & " already equal to " & new_value
      else
         ldapPol.PutEx ADS_PROPERTY_APPEND, "lDAPAdminLimits", _
                    Array( pol_attr & "=" & new_value )
         ldapPol.SetInfo
         ldapPol.PutEx ADS_PROPERTY_DELETE, "lDAPAdminLimits", Array(prop)
         ldapPol.SetInfo
         WScript.Echo "Set " & pol_attr & " to " & new_value
      end if
      Exit For
   end if
next

Discussion

The LDAP query policy contains several settings that control how domain controllers handle searches. By default, one query policy is defined for all domain controllers in a forest, but you can create additional ones and apply them to a specific domain controller or even at the site level (so that all domain controllers in the site use that policy).

Query policies are stored in the Configuration NC as queryPolicy objects. The default query policy is located at: cn=Default Query Policy,cn=Query-Policies,cn=Directory Service,cn=Windows NT,cn=Services, <ConfigurationPartitionDN>. The attribute lDAPAdminLimits of a queryPolicy object is multivalued and contains each setting for the policy in name/value pairs. Table 4-5 contains the available settings.

Table 4-5. LDAP query policy settings

Name

Default value

Description

MaxPoolThreads

4 per proc

Maximum number of threads that are created by the DC for query execution.

MaxDatagramRecv

4096

Maximum number of datagrams that can be simultaneously processed by the DC.

MaxReceiveBuffer

10485760

Maximum size in bytes for an LDAP request that the server will attempt to process. If the server receives a request that is larger then this value, it will close the connection.

InitRecvTimeout

120 secs

Initial receive timeout.

MaxConnections

5000

Maximum number of open connections.

MaxConnIdleTime

900 secs

Maximum amount of time a connection can be idle.

MaxActiveQueries

20

Maximum number of queries that can be active at one time.

MaxPageSize

1000

Maximum number of records that will be returned by LDAP responses.

MaxQueryDuration

120 secs

Maximum length of time the domain controller can execute a query.

MaxTempTableSiz

10000

Maximum size of temporary storage that is allocated to execute queries.

MaxResultSetSize

262144

Controls the total amount of data that the domain controller stores for this kind of search. When this limit is reached, the domain controller discards the oldest of these intermediate results to make room to store new intermediate results.

MaxNotificationPerConn

5

Maximum number of notifications that a client can request for a given connection.

Since the settings are stored as name/value pairs inside a single attribute, also referred to as AVAs, the VBScript solution has to iterate over each value and use a regular expression to determine when the target setting has been found. It does this by matching <SettingName>= at the beginning of the string. See Recipe 4.19 for more on AVAs.

Warning

You should not change the default query policy in production unless you’ve done plenty of testing. Changing some of the settings may result in unexpected application or domain controller behavior, such as a significant failure of your Active Directory domain controllers.

Instead of modifying the default LDAP query policy, you can create a new one from scratch. In the Query Policies container (where the default query policy object is located), create a new queryPolicy object and set the lDAPAdminLimits attribute as just described based on the settings you want configured. Then modify the attribute queryPolicyObject on the nTDSDSA object of a domain controller you want to apply the new policy to. This can be done via the Active Directory Sites and Services snap-in by browsing to the nTDSDSA object of a domain controller (cn=NTDS Settings), right-clicking on it, and selecting Properties. You can then select the new policy from a drop-down menu beside Query Policy. Click OK to apply the new policy.

See Also

Recipe 4.19 and MS KB 315071 (How to View and Set LDAP Policy in Active Directory by Using Ntdsutil.exe)

4.28. Exporting Objects to an LDIF File

Problem

You want to export objects to an LDAP Data Interchange Format (LDIF) file.

Solution

Using a graphical user interface

None of the standard Microsoft tools support exporting LDIF from a GUI.

Using a command-line interface

> ldifde -f output.ldf -l <AttrList> -p <Scope> -r "<Filter>" -d "<BaseDN>"

Using VBScript

There are no COM or VBScript-based interfaces to LDIF. With Perl you can use the Net::LDAP::LDIF module, which supports reading and writing LDIF files.

Discussion

The LDIF specification defined in RFC 2849 describes a well-defined file-based format for representing directory entries. The format is intended to be both human and machine parseable, which adds to its usefulness. LDIF is the de facto standard for importing and exporting a large number of objects in a directory and is supported by virtually every directory vendor, including Microsoft.

Using a command-line interface

The -f switch specifies the name of the file to use to save the entries to, -s is the DC to query, -l is the comma-separated list of attributes to include, -p is the search scope, -r is the search filter, and -d is the base DN. If you encounter any problems using ldifde, the -v switch enables verbose mode and can help identify problems.

See Also

Recipe 4.29 for importing objects using LDIF, RFC 2849 (The LDAP Data Interchange Format [LDIF]—Technical Specification), and MS KB 237677 (Using LDIFDE to Import and Export Directory Objects to Active Directory)

4.29. Importing Objects Using an LDIF File

Problem

You want to import objects into Active Directory using an LDIF file. The file could contain object additions, modifications, and deletions.

Solution

Using a command-line interface

To import objects using the ldifde utility, you must first create an LDIF file with the objects to add, modify, or delete. Here is an example LDIF file that adds a user, modifies the user twice, and then deletes the user:

dn: cn=jsmith,cn=users,dc=adatum,dc=com
changetype: add
objectClass: user
samaccountname: jsmith
sn: JSmith

dn: cn=jsmith,cn=users,dc=adatum,dc=com
changetype: modify
add: givenName
givenName: Jim
-
replace: sn
sn: Smith
-

dn: cn=jsmith,cn=users,dc=adatum,dc=com
changetype: delete

Once you’ve created the LDIF file, you just need to run the ldifde command to import the new objects:

> ldifde -i -f input.ldf

Discussion

For more information on the LDIF format, check RFC 2849.

Using a command-line interface

To import with ldifde, simply specify the -i switch to turn on import mode and -f <filename> for the file. It can also be beneficial to use the -v switch to turn on verbose mode to get more information in case of errors. The Windows Server 2003 version of ldifde also includes the –j switch that will create a logfile for troubleshooting purposes.

See Also

Recipe 4.28 for information on LDIF, RFC 2849 (The LDAP Data Interchange Format [LDIF]—Technical Specification), and MS KB 237677 (Using LDIFDE to Import and Export Directory Objects to Active Directory)

4.30. Exporting Objects to a CSV File

Problem

You want to export objects to a comma-separated variable (CSV) file. The CSV file can then be opened and manipulated from a spreadsheet application or with a text editor.

Solution

Using a command-line interface

You can export objects to a CSV file using the built-in csvde utility, as well as AdFind. For csvde, use the following syntax:

> csvde -f output.csv -l <AttrList> -p <Scope> -r "<Filter>" -d "<BaseDN>"

You can also export information to a CSV file using adfind:

> adfind -b <SearchBase> -f <Filter> -csv <Attr1> <Attr2> <Attr3>

Using PowerShell

You can export objects to a CSV file in PowerShell using the Quest AD cmdlets or the native ADSI methods, as follows:

 get-QADObject -identity <ObjectDN> -IncludeProperty <Property1> <Property2> ... |
Select <Property1> <Property2> ... | export-csv adobjects.csv

$obj = [ADSI]"LDAP://<Object DN>"
$record = ""
foreach ($property in ($obj | get-member)) { $record += $property.name + "|" }
$record | out-file -force adobjects.csv
$record = ""
foreach ($property in ($obj | get-member)) { $record += $obj.($property.name) + "|" }
$record | out-file -append adobjects.csv

Discussion

Once you have a CSV file containing entries, you can use a spreadsheet application such as Excel to view, sort, and manipulate the data.

Using a command-line interface

The parameters used by cvsde are nearly identical to those used by ldifde. The -f switch specifies the name of the file to use to save the entries to, -s is the DC to query, -l is the comma-separated list of attributes to include, -p is the search scope (base, onelevel, or subtree), -r is the search filter, and -d is the base DN. If you encounter any issues, the -v switch enables verbose mode and can help identify problems.

AdFind offers a number of additional switches to customize the behavior of CSV file output, including:

-csv xxx

CSV output. xxx is an optional string that specifies value to use for empty attributes.

-csvdelim x

Delimiter to use for separating attributes in CSV output. The default is (,).

-csvmvdelim x

Delimiter to use for separating multiple values in output. The default is (;).

-csvq x

Character to use for quoting attributes. The default is (“).

See Also

Recipe 4.31 for importing objects using a CSV file

4.31. Importing Objects Using a CSV File

Problem

You want to import objects into Active Directory using a CSV file.

Solution

Using a command-line interface

To import objects using the csvde utility, you must first create a CSV file containing the objects to add. The first line of the file should contain a comma-separated list of attributes you want to set, with DN being the first attribute. Here is an example:

DN,objectClass,cn,sn,userAccountControl,sAMAccountName,userPrincipalName

The rest of the lines should contain entries to add. If you want to leave one of the attributes unset, then leave the value blank (followed by a comma). Here is a sample CSV file that would add two user objects:

DN,objectClass,sn,userAccountControl,sAMAccountName,userPrincipalName
"cn=jim,cn=users,dc=adatum,dc=com",user,Smith,512,jim,jim@adatum.com
"cn=john,cn=users,dc=adatum,dc=com",user,,512,john,john@adatum.com

Once you’ve created the CSV file, you just need to run cvsde to import the new objects:

> csvde -i -f input.csv

Discussion

The major difference between csvde and ldifde is that you can only use csvde to import objects; unlike ldifde, you can’t use it to modify existing objects. Note that each line of the CSV import file, except the header, should contain entries to add objects. You cannot modify attributes of an object or delete objects using csvde; however, you can accomplish this using admod. If you have a spreadsheet containing objects you want to import, first save it as a CSV file and use csvde to import it.

Using a command-line interface

To import with csvde, simply specify the -i switch to turn on import mode and -f <filename> for the file. It can also be beneficial to use the -v switch to turn on verbose mode to get more information in case of errors.

See Also

Recipe 4.30 for exporting objects in CSV format and MS KB 327620 (How to Use Csvde to Import Contacts and User Objects into Active Directory)

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required