I learned something new this week that caught me by surprise, I guess it shouldn’t have but I don’t recall seeing the documentation before so was shocked when someone asked me about it.
So I get this email from a reader of AD3E, not unlike many emails I get every day only it isn’t something that I can answer right off the top of my head
Joe,
I have a problem that I am hoping you can help with…
I have a large OU that contains thousands of objects that needs to be wiped out each day from an ADAM directory.
When I try to call the objOU.DeleteObject(0) method from a vbscript, I get a message similar to “The administrative limit of this command has been reached.” If I execute the method repeatedly, the OU eventually completely goes away, but it doesn’t seem like a “clean” way of doing this. Is there either a different method I should be using to delete the OU, or is there a configurable parameter in the directory that will allow this large of a command to run? Its not really a timeout, so I don’t know what parameter would be. Even deleting the OU in ADAM ADSIEDIT throws an error saying it could not complete the request, and that I can run the command again to continue deleting the object(s).Your help would be greatly appreciated.
(and yes, I do have your book, Active Directory, 3rd Edition. It’s great.)
My first thought was, hmm that’s odd. My second thought was what is ADSI screwing up now. 😉 My third thought was that would be a weird error message out of ADSI… So I tossed in 50,000 test users into my local ADAM instance (I run ADAM on pretty much every Windows machine in my house) really quick.
First create the testou
admod -h . -b OU=testou,DC=domain2,DC=com -exterr -add objectclass::organizationalunit
then create the users
admod -h . -sc adamau:50000;password;CN=testuser,OU=testou,DC=domain2,DC=com
That results in a DIT in the 140MB or so range. It took about 9 minutes on my 2.6Ghz 1.5GB Dell Inspiron 8500 laptop, doing that on a real machine would result in it going much faster.
So then I wrote a quick script to use DeleteObject(0) which should, according to the documentation, delete all objects under the container specified as well as the container itself.
set o=getobject(“LDAP://k385002/OU=testou,DC=domain2,DC=com”)
wscript.echo o.name
o.deleteobject(0)
Results…
F:\Temp>test
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.OU=testou
F:\Temp\test.vbs(3, 1) (null): The administrative limit for this request was exceeded.
Well that validated the email.
So time to test LDAP…
F:\temp>admod -h . -b OU=testou,DC=domain2,DC=com -del -treedelete -exterr
AdMod V01.10.00cpp Joe Richards (joe@joeware.net) February 2007
DN Count: 1
Using server: k385002.joe.com:389
Directory: Active Directory Application ModeDeleting specified objects…
DN: OU=testou,DC=domain2,DC=com…: [k385002.joe.com] Error 0xb (11) – Administration Limit ExceededExtended Error: 000020CD: SvcErr: DSID-030907BC, problem 5008 (ADMIN_LIMIT_EXCEEDED), data 0
ERROR: Too many errors encountered, terminating…
The command did not complete successfully
Ouch… Error 20CD is
F:\temp>err 20cd
# for hex 0x20cd / decimal 8397 :
ERROR_DS_TREE_DELETE_NOT_FINISHED winerror.h
# The tree deletion is not finished. The request must be
# made again to continue deleting the tree.
# 1 matches found for “20cd”
I’ll admit, this surprised me. As mentioned before, I had never heard there was a limit. I dug into the source and found a hard coded limit of 16*1024 also known as 16384 objects. This was obviously discernible just by looking at the number of objects that were deleted as well but I would rather look at the source and “easily”[1] know for sure on something like this in case it was a memory allocation thing and that was all that could be allocated[2]. Once that many objects are deleted, the function returns with the Admin Limit error. This is actually documented, sort of, if you know what to go look for and I expect 98.37% +/- 1% of the ADSI programmers would NOT know where to look. You have to look at the underlying control used which is LDAP_SERVER_TREE_DELETE_OID. You can see that documentation here:
http://msdn2.microsoft.com/en-us/library/aa366991.aspx
You will note that it indicates the issue in the section Error Messages.
AdminLimitExceeded (11) | The limit of the number of objects that can be deleted in one operation is exceeded. However, all objects processed up to the limit will be deleted. The DelRequest with the Tree Delete control may be resubmitted until a success response is received. |
That is pretty obviously it… but it doesn’t actually tell you what the limit is. I can understand though, the OS could change tomorrow and the value where the error occurs could change.
My personal opinion on this is that it shouldn’t return that error ever, if you submit a request to delete a branch, the branch should be deleted. Otherwise the code you are writing is going to have to handle it. It isn’t very difficult to handle, you simply add a loop around the delete operation but certainly this is more confusing than just simply doing what was asked in the first place. Some could claim this is a safety mechanism but seriously… who is going to go oh…. I didn’t realize there were so many objects in that OU, maybe I better double-check before I request the whole thing be deleted again? You have no clue what objects were deleted unless you kept a list of the objects in some other store or in memory, you just know that some X objects were deleted and the function call decided to not do the rest. The beauty is, with how this is set up, MSFT could change this tomorrow and it is likely it wouldn’t break any code other than maybe more likelihood to pop a timeout which is always a possibility people can hit. There is no guarantee that you wouldn’t hit a timeout anyway.
I asked a few of my really bright friends what they thought and none of them had a clue that the DS did this either but were thrilled to hear about it so they now knew. It wasn’t until I pinged some really bright MSFT DS friends that I found someone who was aware of it and that is because it was something they had to code around themselves.
Oh so how do you code around it? You use some sort of loop like this (pseudocode)
errnum=ExecuteDeleteCommand;
while (errnum==11)
{
errnum=ExecuteDeleteCommand;
}if (errnum)
{ // some error other than admin limit occurred (maybe access denied?)
display error message for errnum
}
or if you prefer
errnum=11
while (errnum==11)
{
errnum=ExecuteDeleteCommand;
}if (errnum)
{ // some error other than admin limit occurred (maybe access denied?)
display error message for errnum
}
If I have the ExecuteDeleteCommand in a function/subroutine on its own I will likely follow the first form, if it is a single one liner API call I will likely follow the second form.
So what do you guys think?
- Does it make sense that the delete tree should come back and say “too many objects but I will delete what I can”
- Should it return and say “too many objects, I am not deleting anything unless you tell me how much you expect to delete”
- Should it just do what you told it to do?
- Should delete tree not even be a valid option for people to do anymore? Should maybe you have to go and delete every object individually? Would that make things safer do you think? Is delete tree just too powerful for the admins and coders working with Active Directory, do they not have the brains, willpower, and quality of workmanship to not accidentally blow away their directory by accident with delete tree?
Let me know what you think. I will make sure the feedback makes it back to my friends on the DS Team.
joe
[1] Easily is obviously a relative term here, anyone who has browsed through the Windows source knows what I am talking about, especially if doing it through MSDN premium. I still can’t figure out how to trace the RPC calls back to their actual source files, I always end up chasing the stubs to a dead end.
[2] You would test on several systems with different configs under different load conditions to validate it wasn’t a memory allocation issue.
I still can’t believe you can delete or move an OU or a user with only a simple “are you sure” warning…
Anthony, that is a function of the tool, not the underlying API, for instance with ADUC, if you delete a branch with a 100,000 objects in it, ADUC has to submit that delete branch request 7 times. Its one of the reasons why people shouldn’t use GUIs with direct native rights for a majority of the management versus using some form of proxy administration tool or some other tool with business logic or validated writes built into it.
You know this would be perfect for a do/while loop 🙂
do
{
errnum = ExecuteDeleteCommand;
} while(errnum == 11)
Very true Michael. Interestingly, I don’t know why, but the only time I tend to use do-while type looping is when playing with Pascal with the repeat-until loop. Just doesn’t feel right to me in c++ and perl for some reason.
It’s not what I would see as desirable behavior. My preferences are:
-delete all of the objects, no warning. It’s easy enough to set a deny Delete Subtree ACL (or remove the Allow), and I think this gets exposed in the GUI in Longhorn. So it’s easy enough for admins to protect themselves against an accidental OU deletion.
-throw a warning/error before deleting any objects if the number is going to exceed some pre-set limit. I’d still want to be able to override this with a parameter, but at least MS could argue that someone had to make a concerted effort to whack the object and child objects.
I’m having a (possibly related) problem with ‘Administartive Limits’. Any ideas?
I have my own ADAM schema with a simple class with a couple of multi-valued DN attributes. I wanted to see how many references I could stuff into a single ‘membership’ list.
Documentation on the web suggests ~1500 entries in an attribute, with soft limits of 500kb limit per attribute and 1MB per object.
I found that after 1283 references (all to local objects in the same ADAM directly) I got “The administrative limit for this request was exceeded. (Exception from HRESULT: 0x80072024)”
Interestingly if I try to add even a single entry to the second multi-valued DN attribute on the object (or even set its ‘displayName’) I get the same error – so this suggests that the limit is on the object size rather than the attribute size.
I’ve tried my own code (.Net directory services), LDP and ADSIEdit and all get the same result. I’ve also tried tweaking the LDAP Administartive policies using dsmgmt, and fiddling with page mode, and directory services caching but this hasn’t helped. (I’m pretty sure the limit is on the write, not on any related read).
Any ideas what limit I’m hitting – or what I can do to avoid hitting it? I can supply sample code if it helps – but it is pretty trivial – two multi valued DN attributes, one object with both these attirbutes, and a loop to create dummy objects and stuff the list.
Any help removing the limits (or just explaination of what is happening) would be greatly appreciated. I’m using ADAM SP1 on Windows XP.
Richard:
Make your attributes linked value attributes. They don’t have an upper limit for multiple values as they are stored in a separate table.
>So what do you guys think?
Hmmm … so I would add here 5’th option – why not to have another control OID to specify delete subtree withoth taking care about limits?