I have some code that is looking up group memberships from local groups on a machine. For each member, it tries to load some information about the user (eg. find a group and get the names of each of its members).
The code:
using (DirectoryEntry machine = new DirectoryEntry("WinNT://" + Environment.MachineName + ", Computer"))
{
using (DirectoryEntry group = machine.Children.Find(groupName, "group"))
{
object members = group.Invoke("members", null);
foreach (object groupMember in (IEnumerable) members)
{
using (DirectoryEntry member = new DirectoryEntry(groupMember))
{
member.RefreshCache();
string name = member.Name;
// <code snipped>
}
}
}
}
The code works fine most of the time, but for some group members, it throws a FileNotFoundException when the RefreshCache() method is thrown:
System.IO.FileNotFoundException:
The filename, directory name, or volume label syntax is incorrect.
(Exception from HRESULT: 0x8007007B)
at System.DirectoryServices.Interop.UnsafeNativeMethods.IAds.GetInfo()
at System.DirectoryServices.DirectoryEntry.RefreshCache()
at GroupLookup.GetLocalGroupMembership(String groupName)
What is causing the FileNotFoundException (and what file is it looking for)?
The file-not-found error is commonly used in the Win32 API as a "resource not found" error. So, it's returned for things like missing Registry keys, or - in this case - an ADSI node.
I am definitely not an ADSI expert, but your first call to the DirectoryEntry constructor seems to be using an invalid path style according to MSDN. I believe you'd need the domain name before the machine name.
Update:
Noticed this on another MSDN page: "GetInfo cannot be used for groups that contain members that are wellKnown security principals in the WinNT scope."
Given the stack trace, it seems like this may be causing the problem.
I didn't get to the bottom of what was causing the FileNotFoundException, although I suspect it was to do with the group set-up - the groups had both local and domain users in them.
Because I only needed the users' names and SIDs, and these were already present on the DirectoryEntry, I solved this issue by not calling the RefreshCache method. This lets the code execute without an exception.
Related
My team is using a program written in C# to read all users from a specific OU. The program behaves very strange. Sometimes it is working for a couple of weeks and then without any big changes on our AD or any other related component, it throws an exception. Then it is not working for a couple of weeks and after some time it start to run normally again.
Code
DirectoryEntry searchRoot = new DirectoryEntry("<LDAP string>")
searchRoot.AuthenticationType = AuthenticationTypes.None;
DirectorySearcher search = new DirectorySearcher(searchRoot);
search.Filter = <our filter>;
search.PropertiesToLoad.Add("<some property>");
search.PageSize = 1;
SearchResult result;
SearchResultCollection resultCol = null;
try
{
resultCol = search.FindAll();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
if (resultCol != null)
{
Console.WriteLine("Result Count: " + resultCol.Count); //.Count throws the Exception
}
Exception
Unhandled Exception: System.DirectoryServices.DirectoryServicesCOMException: An operations error occurred.
at System.DirectoryServices.SearchResultCollection.ResultsEnumerator.MoveNext()
at System.DirectoryServices.SearchResultCollection.get_InnerList()
at System.DirectoryServices.SearchResultCollection.get_Count()
Data: System.Collections.ListDictionaryInternal
Error Code: -2147016672
Extended Error: 8431
Extended Error Message: 000020EF: SvcErr: DSID-020A07A7, problem 5012 (DIR_ERROR), data -1018
HResult: -2147016672
Message: An operations error occured.
Source: System.DirectoryServices
Stack Trace: at System.DirectoryServices.SearchResultCollection.ResultsEnumerator.MoveNext()
Target Site: Boolean MoveNext()
Additional Information
Target Framework: .Net Framework 4.6.1 (no additional libraries)
The program is executed on a domain controller
What I have tried
I have created a loop to use the MoveNext() function of the
enumerator and found out that it loads results up to a specific
element and then crashes
It is always the same element
After the first exception all retries fail as well
The user that starts it is a domain admin (but I
have also tried it with an enterprise admin account, so it is
probably not a permission issue)
I have deleted the user that should be read when the exception happens but dring the next run the exception was thrown for a previous user
I have come to a point, where I have no more ideas on solving this problem. I would appreciate all your support.
This answer just summarizes our conversation in comments.
This thread partially matches the error you are getting:
problem 5012 (DIR_ERROR) data -1018
And the answer from a Microsoft MVP is:
That is a checksum error in the database, you have corruption in your
database which is usually due to a failing disk or disk subsystem or
possibly a system crash and data not being written from a write cache.
So it sounds like you might have the same thing going on.
But it may only be one DC that has the problem. So to help you narrow down which one, you can specify the DC in the LDAP path like so:
LDAP://dc1.example.com/OU=Target,OU=My User Group,OU=My Users,DC=example,DC=com
This can help you in two ways:
It can identify the bad DC so you know which one you need to fix (and possibly take it offline until it is fixed), and
You can specifically target a good DC so your script will keep working.
The log I'm trying to work with belongs to another program that runs concurrently with my own. I get a DirectoryNotFoundException stating "Could not find a part of the path " when I try to make the copy. The assert does pass. The exception is thrown at File.Copy(...) itself. With the if(File.Exists(...)) in place, the program is clearly able to see the file before it attempts to copy it.
Edit: Could permissions be a possible cause? The directory is located in the root of the C drive.
Edit: By adding the two asserts suggested by Jim Mischel and stepping through in the cold light of a new day, newControlProgramLog path was revealed as the culprit. GetSaveFilePath() was returning a default path for the particular run state I was testing. I declared the default but never checked to see that it existed on program start up. The directory is now created if it does not exist, and the function now works as intended.
Shout out to Christian Hagelid for calling that it wasn't an issue with controlProgramLogPath from the start.
private void CopyLogsToDataDirectoy()
{
Debug.Assert(Directory.Exists(_controlProgramDirectory));
string controlProgramLogPath = Path.Combine(_controlProgramDirectory, _controlProgramLogFileName);
if (File.Exists(controlProgramLogPath))
{
string dataFilePath = GetSaveFilePath();
string newControlProgramLogName = Path.GetFileNameWithoutExtension(dataFilePath);
newControlProgramLogName = newControlProgramLogName + ".control.log";
string newControlProgramLogPath = Path.GetDirectoryName(dataFilePath);
newControlProgramLogPath = Path.Combine(newControlProgramLogPath, newControlProgramLogName);
File.Copy(controlProgramLogPath, newControlProgramLogPath);
}
}
DirectoryNotFoundException occurs when part of the path you specified does not exist. It does not occur because a file is locked. If you get DirectoryNotFoundException, then it's almost certainly because the string you supplied does not reference a valid directory path. Documentation also says that you can get this exception if your code doesn't have the PathDiscovery permission. I suspect that's pretty unlikely in your case.
You should check the paths in controlProgramLogPath and newControlProgramLogPath immediately before calling File.Copy.
Debug.Assert(Directory.Exists(Path.GetDirectoryName(controlProgramLogPath));
Debug.Assert(Directory.Exists(Path.GetDirectoryName(newControlProgramLogPath));
I suspect that will reveal the problem.
Recently two users in our system started getting this error when trying to add them to a role.
System.Runtime.InteropServices.COMException: Cannot create a file when that file already exists. (Exception from HRESULT: 0x800700B7)
What is interesting is that the same error occurs regardless of the configuration, running locally we use an XML store and in the test environment it uses SQL Server.
Here is code where is blows up - AddMemberName() - as you can see this is pretty straightforward stuff and it's worked well for a while, it's just these two users all of the sudden
public void AddUserToRole(string roleName, string userName, bool upn)
{
string uName = userName;
if (upn)
uName = getAltUserNames(userName).First();
AzAuthorizationStore store = new AzAuthorizationStoreClass();
store.Initialize(2, _provider.StoreLocation, null);
IAzApplication app = store.OpenApplication(_provider.ApplicationName, null);
IAzRole role = app.OpenRole(roleName, null);
role.AddMemberName(uName, null);
role.Submit(0, null);
Marshal.FinalReleaseComObject(role);
}
I tried googling various different terms but can't find much of anything regarding this. I did read this post but it doesn't seem to be the issue.
Thanks
Check you Active Directory usernames and the underlying OU name especially. Check for duplicates and mismatches.
I had an issue once where a user got married and her name changed.
I got the following snippet (SomeName/SomeDomain contains real values in my code)
var entry = new DirectoryEntry("LDAP://CN=SomeName,OU=All Groups,dc=SomeDomain,dc=com");
foreach (object property in entry.Properties)
{
Console.WriteLine(property);
}
It prints OK for the first 21 properties, but then fail with:
COMException {"Unknown error (0x8000500c)"}
at System.DirectoryServices.PropertyValueCollection.PopulateList()
at System.DirectoryServices.PropertyValueCollection..ctor(DirectoryEntry entry, String propertyName)
at System.DirectoryServices.PropertyCollection.PropertyEnumerator.get_Entry()
at System.DirectoryServices.PropertyCollection.PropertyEnumerator.get_Current()
at ActiveDirectory.Tests.IntegrationTests.ObjectFactoryTests.TestMethod1() in MyTests.cs:line 22
Why? How can I prevent it?
Update
It's a custom attribute that fails.
I've tried to use entry.RefreshCache() and entry.RefreshCache(new[]{"theAttributeName"}) before enumerating the properties (which didn't help).
Update2
entry.InvokeGet("theAttributeName") works (and without RefreshCache).
Can someone explain why?
Update3
It works if I supply the FQDN to the item: LDAP://srv00014.ssab.com/CN=SomeName,xxxx
Bounty
I'm looking for an answer which addresses the following:
Why entry.Properties["customAttributeName"] fails with the mentioned exception
Why entry.InvokeGet("customAttributeName") works
The cause of the exception
How to get both working
If one wants to access a custom attribute from a machine that is not
part of the domain where the custom attribute resides (the credentials
of the logged in user don't matter) one needs to pass the fully
qualified name of the object is trying to access otherwise the schema
cache on the client machine is not properly refreshed, nevermind all
the schema.refresh() calls you make
Found here. This sounds like your problem, given the updates made to the question.
Using the Err.exe tool here
http://www.microsoft.com/download/en/details.aspx?id=985
It spits out:
for hex 0x8000500c / decimal -2147463156 :
E_ADS_CANT_CONVERT_DATATYPE adserr.h
The directory datatype cannot be converted to/from a native
DS datatype
1 matches found for "0x8000500c"
Googled "The directory datatype cannot be converted to/from a native" and found this KB:
http://support.microsoft.com/kb/907462
I have the same failure. I´m read and saw a lot of questions about the error 0x8000500c by listing attribute from a DirectoryEntry.
I could see, with the Process Monitor (Sysinternals), that my process has read a schema file. This schema file is saved under
C:\Users\xxxx\AppData\Local\Microsoft\Windows\SchCache\xyz.sch.
Remove this file and the program works fine :)
I just encountered the issue and mine was with a web application.
I had this bit of code which pulls the user out of windows authentication in IIS and pulls their info from AD.
using (var context = new PrincipalContext(ContextType.Domain))
{
var name = UserPrincipal.Current.DisplayName;
var principal = UserPrincipal.FindByIdentity(context, this.user.Identity.Name);
if (principal != null)
{
this.fullName = principal.GivenName + " " + principal.Surname;
}
else
{
this.fullName = string.Empty;
}
}
This worked fine in my tests, but when I published the website it would come up with this error on FindByIdentity call.
I fixed the issue by using correct user for the app-pool of the website. As soon as I fixed that, this started working.
I had the same problem with a custom attribute of a weird data type. I had a utility program that would extract the value, but some more structured code in a service that would not.
The utility was working directly with a SearchResult object, while the service was using a DirectoryEntry.
It distilled out to this.
SearchResult result;
result.Properties[customProp]; // might work for you
result.Properties[customProp][0]; // works for me. see below
using (DirectoryEntry entry = result.GetDirectoryEntry())
{
entry.Properties[customProp]; // fails
entry.InvokeGet(customProp); // fails as well for the weird data
}
My gut feel is that the SearchResult is a little less of an enforcer and returns back whatever it has.
When this is converted to a DirectoryEntry, this code munges the weird data type so that even InvokeGet fails.
My actual extraction code with the extra [0] looks like:
byte[] bytes = (byte[])((result.Properties[customProp][0]));
String customValue = System.Text.Encoding.UTF8.GetString(bytes);
I picked up the second line from another posting on the site.
I'm trying to load tokenGroups from Active Directory but it isn't working once deployed to a Windows Server (2003). I cannot figure out why, since it works fine locally...
Here is my error:
There is no such object on the server.
And here is my code (the sid variable is the current users SecurityIdentifier pulled from HttpContext):
DirectoryEntry userDE = new DirectoryEntry(string.Format("LDAP://<SID={0}>", sid.Value))
userDE.RefreshCache(new[] { "tokenGroups" });
var tokenGroups = userDE.Properties["tokenGroups"] as CollectionBase;
groups = tokenGroups.Cast<byte[]>()
.Select(sid => new SecurityIdentifier(sid, 0)).ToArray();
Any ideas why I would get that error?
UPDATE: The error actually happens on the RefreshCache line
Do you have a valid value for userDE after the constructor call?? Does that user really exist? Or do you need to provide e.g. a server to use in your LDAP path??
The error message No such object on server seems to indicate the user just plain doesn't exist.... (or cannot be found, due to e.g. permissions)
Try this - not sure if that's the problem, but it's worth a try - it should work:
DirectoryEntry userDE = new DirectoryEntry(string.Format("LDAP://<SID={0}>", sid.Value))
userDE.RefreshCache(new string[] { "tokenGroups" });
Try using new string[] instead of just new[].