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 Mode
Deleting specified objects…
DN: OU=testou,DC=domain2,DC=com…: [k385002.joe.com] Error 0xb (11) – Administration Limit Exceeded
Extended 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.