Basics of Writing Attribute Values

Much of the information we have learned about reading attribute data we can also apply to writing. However, there are a few basic rules to understand right off the bat.

The rules may seem obvious by now, but they come up as issues surprisingly often in practice.

DirectorySearcher makes it easy to switch to "edit mode" by using the SearchResult.GetDirectoryEntry method:

//given a SearchResult result that you wish to modify using (DirectoryEntry entry = result.GetDirectoryEntry()) { //now, perform modifications on the DirectoryEntry }

To switch from the GC provider to LDAP, create a new DirectoryEntry object with the LDAP provider.

Note: Switching from GC to LDAP May Require Changing Physical Servers Too

In order to switch from GC to LDAP, we need a domain controller in the domain where the object "lives." The global catalog server we were querying might be from a different domain, so this is not always straightforward.

 

Setting Initial Values

If an attribute has no value, we can use one of the following two approaches to set an initial value.

For example, to set the description attribute on a DirectoryEntry called entry, we might do this:

entry.Properties["description"].Add("a new description"); //or entry.Properties["description"].Value = "a new description"; entry.CommitChanges();

We recommend avoiding using the array index accessor for setting values in general and especially for setting initial values, as it does not behave exactly the same way against all versions of the .NET Framework and ADSI, and it might cause unexpected problems with routine upgrades and service packs. For example:

//don't do this; it might not work as expected entry.Properties["description"][0] = "a new description";

If we wish to set multiple values initially, we can use the Value property again, use the AddRange method, or call Add repeatedly:

//Do this entry.Properties["otherTelephone"].Value = new string[] {"222-222-2222", "333-333-3333"}; //or this entry.Properties["otherTelephone"].AddRange(new string[] {"222-222-2222", "333-333-3333"}); //or this entry.Properties["otherTelephone"].Add("222-222-2222"); entry.Properties["otherTelephone"].Add("333-333-3333"}); entry.CommitChanges();

It may seem obvious, but keep in mind that using the Value property overwrites the entire attribute, which is why it is appropriate for setting initial values and not for simply adding to the existing values.

Clearing an Attribute

If an attribute is set and we wish to clear it, we can simply call the Clear method:

entry.Properties["description"].Clear(); entry.CommitChanges();

Alternately, the Value property can be used to clear an attribute value by setting it to null (Nothing in Visual Basic), but the Clear method seems to convey our intentions better.

Replacing an Existing Attribute Value

If the attribute value is already populated and we want to replace it completely with a different value, the easiest way to do this is with the Value property. Using our first example again:

entry.Properties["description"].Value = "a second description"; entry.CommitChanges();

When we set the Value property, it has the benefit of completely replacing the existing value with whatever we set in a single LDAP modification operation.

Adding and Removing Values from Multivalued Attributes

If the attribute has multiple values and we wish to modify only parts of it, we should use the Add, AddRange, and Remove methods. This is a very common thing to do when we're modifying membership on a group and we want to add or remove individual members:

//given a DirectoryEntry entry bound to a group: entry.Properties["member"].Add( "CN=someuser,CN=Users,DC=domain,DC=com"); entry.Properties["member"].Remove( "CN=someotheruser,CN=Users,DC=domain,DC=com"); entry.CommitChanges();

If we are careful to check the values in the attribute before modification using the Contains method, we can avoid errors caused by adding duplicate entries or removing nonexistent entries.

Attribute Modification Summary

As we have shown, the Value property is extremely useful for attribute modifications. We can use it in just about any situation to set single and multiple values. It is also very efficient, as it uses a single PutEx call under the hood that results in a single LDAP modification operation. Additionally, the Add, AddRange, Remove, and Clear methods are very helpful and efficient under the hood, for similar reasons.

We generally recommend avoiding using array indexers on PropertyValueCollection for modifications. At the very least, changing a value using the array index will result in a remove and an add operation under the hood. In some situations, this simply will not work at all. The upcoming sidebar, Caution with Attribute Writing, provides more details about this problem. The other point here is that LDAP does not guarantee that multivalued attributes are stored or read in any specific order, so it is best not to think about them in that way, either.

Caution with Attribute Writing

As we already explained, SDS uses the ADSI PutEx method to handle write operations to ADSI objects. PutEx supports three types of operations: Add, Delete, and Replace. Add and Delete allow us to change individual values in an attribute, and Replace overwrites the entire value.

The original versions of SDS in .NET 1.0 and 1.1 had a very simple way of calling PutEx. Instead of allowing PropertyValueCollection to use the Add and Delete PutEx operations, SDS simply did a Replace operation on each change to PropertyValueCollection. Then, when changes were flushed back to the directory via CommitChanges, the entire attribute value was replaced in the directory.

Although this might seem inefficient (and it was in some respects), the simplicity of this model worked pretty well, except with one very important exception.

The Problem

As we covered earlier in this chapter, large multivalued attribute types in Active Directory and ADAM contain a maximum of MaxValRange (usually either 1,000 or 1,500) values in PropertyValueCollection. The problem, of course, is that doing a Replace operation on these types of attributes has the net result of truncating the attribute's value collection, effectively removing all of the values in the attribute that didn't happen to be in PropertyValueCollection at the time of the operation. Whoops!

The net result of this was that seemingly innocent edits to these attributes could have large and disastrous impacts. The really bad part of this is that one very commonly used and interesting attribute in Active Directory and ADAM, the member attribute that is used to define group memberships, was especially susceptible to this issue. Many developers inadvertently truncated very large Active Directory and ADAM groups, which obviously can be quite disastrous.

The Fix and the Consequences

The Directory Services team at Microsoft decided to fix this issue in .NET 1.0 SP3 and .NET 1.1 SP1 by changing the behavior of PropertyValueCollection. In the current releases (including the 1.x service packs and .NET 2.0), the collection uses all three PutEx operations to send only the "deltas" to the directory. This avoids the problem we just described, and improves the efficiency of write operations to the directory, especially on large attribute values.

So, what's the problem? Unfortunately, this change introduced another problem. It seems that not all versions of ADSI allow multiple modification operations on the same attribute between updates to the directory. Essentially this means that we cannot do both a Delete and then an Add without an intervening call using CommitChanges, for example. The last modification would replace the first one and only the Add would be performed. Thus, a simple operation like changing a value at a certain index in PropertyValueCollection could result in a Delete and then an Add, which might not work.

To prevent developers from having all sorts of unexpected behavior performing seemingly innocent changes to attributes, SDS uses a new IADsOptionOptions flag (ADS_OPTION_ACCUMULATIVE_MODIFICATION) which will determine at runtime whether multiple modifications are supported. If the platform does not support the required ADSI behavior, a NotSupportedException is thrown.

This new behavior was disconcerting to many developers on the platforms that do not support multiple modifications, to say the least! Many developers used the syntax:

entry.Properties["someAttribute"][0] = "some new value";  

to initialize or change attribute values and their previously working code suddenly started throwing exceptions when the .NET service packs were applied to their systems. Additionally, since the same code might behave differently on a developer's workstation than on the machine it was deployed to (depending on the version of ADSI), this was even more difficult to troubleshoot.

As of this writing, this issue does not affect Windows XP SP2+ and Windows 2003 SP1, and other versions of Windows require a hotfix (or the next service pack, if available). Thus, we should be especially aware of what code we write and where it will be deployed, taking care to get the latest hotfixes if necessary.

To steer clear of potential issues, one thing we can do that will help is always to use the Value property when intending to replace the current attribute value or to initialize it. For example:

entry.Properties["someAttribute"].Value = "some new value";  

This results in only one ADSI PutEx operation, as PropertyValueCollection is smart enough to treat writes to the Value property as Replace operations. Additionally, using the AddRange method rather than multiple calls to Add can result in more efficient code and fewer problems.

Quite a few Knowledge Base articles related to this issue are available, including 886541, 835763, and 894277. To determine if this new behavior is supported, we can use either IADsObjectOptions with the ADS_OPTION_ACCUMULATIVE_MODIFICATION flag or the new DirectoryEntryConfiguration object in .NET 2.0.

Категории