I am trying to set up a DirSync control. Previously I have [successfully] used the methods found in the System.DirectoryServices.Protocols, but I found that the results it returned were only partial objects - I wasn't able to have it return the homeDrive attribute from a user even if I defined it in the SearchRequest's Attributes property.
Thus, I'm attempting to set up DirSync following some of the documentation and examples using System.DirectoryServices instead. I was successful in connecting to my test server (only accessible by IP), and I was successful in targeting just one OU and searching for a user, as such:
byte[] cookie = null;
root = new DirectoryEntry(
"LDAP://[MyIPHere]/OU=test ou,DC=company,DC=com", "username", "password");
//Section A - Use this section for a regular search
DirectorySearcher src = new DirectorySearcher(root);
src.SearchScope = SearchScope.Subtree;
src.Filter = "(&(objectClass=user)(sAMAccountName=myuserhere)";
//Section B - Use this section for a DirSync
//src.DirectorySynchronization = new DirectorySynchronization(
DirectorySynchronizationOptions.IncrementalValues, cookie);
//src.Filter = "(&(objectCategory=person)(objectClass=user))";
//Execute the code whichever section is used
SearchResultCollection result = src.FindAll();
int count = result.Count;
Console.WriteLine(count.ToString());
foreach (SearchResult res in result)
{
//do things
}
However, when I try to use section B instead of section A, I get an error on the line where I'm setting the int count. (I have tried passing no parameters to the constructor of src.DirectorySynchronization as in the example, same result):
COMException was unhandled
Access is denied.
I only get the error when I try to access the properties of the result object, or try to iterate. If I set a breakpoint on the int count line and look at the result object, I see the following in the value column of the result's Count:
'result.Count' threw an exception of type
'System.Runtime.InteropServices.COMException'
I have ensured that my account has the Replicating Directory Changes security access both for the specified OU and the test domain at large (and all other security access possible). I have also tried using a separate domain admin account.
I have the same issue if I try running this on our production domain, passing no credentials when constructing the DirectoryEntry object.
Considering that I can successfully retrieve other search results, what is it about this DirectorySynchronization that is causing the access issues, and why isn't it happening when I call src.FindAll()?
(I'm open to other options, but I'd like to avoid the USNChanged tracking method for now due to it returning the full objects back, and requiring additional coding on my end.)
The base of a DirSync search must be the root of a directory partition, i.e "DC=company,DC=com" in your case.
See http://msdn.microsoft.com/en-us/library/ms677626(v=vs.85).aspx
"Base of the search" in the table.
Some more good C# example for DirSync:
http://msdn.microsoft.com/en-us/magazine/cc188700.aspx#S1
See section "Finding Your Way with DirectorySearcher".
If you want to track changes only on a OU/container, yes, you will have to use USNChange.
Related
I'm using Novell in asp.net core 2.2 application to interact with AD. Following functions are working as expected.
Getting all users, Getting users from specific OU
Create an User
Update an User
Reset password and etc
But when i try to move the entry to new container it gives following exception
Naming Violation
((Novell.Directory.Ldap.LdapException)e).LdapErrorMessage : "00000057: LdapErr: DSID-0C090E72, comment: Error in attribute conversion operation, data 0, v4563"
Here is the code block i'm using.
var dn = $"CN={user.FirstName} {user.LastName},{this._ldapSettings.ContainerName}";
//dn => CN=arshath shameer,CN=Users,DC=wxyzdev,DC=xyzdev,DC=ca
var newRDn = $"CN={user.FirstName} {user.LastName},OU=DeletedUsers,DC=wxyzdev,DC=xyzdev,DC=ca";
// newRDn => CN=arshath shameer,OU=DeletedUsers,DC=wxyzdev,DC=xyzdev,DC=ca
using (var ldapConnection = this.GetConnection())
{
//ldapConnection.Delete(dn);
ldapConnection.Rename(dn, newRDn, dn, true);
}
I'm following this link.
There are 2 issues to fix :
RDN means relative DN : the part in the DN that actually makes an
entry distinguishable from others in the same container, for example :
CN=arshath shameer in CN=arshath
shameer,CN=Users,DC=wxyzdev,DC=xyzdev,DC=ca. In your case, since you don't want to rename but to move an entry, it doesn't change :
var newRDn = $"CN={user.FirstName} {user.LastName}";
When moving an entry - contrary to renaming - the RDN stays the same,
but the parentDN changes :
var parentDN = "OU=DeletedUsers,DC=wxyzdev,DC=xyzdev,DC=ca";
Now let's move the entry :
ldapConnection.Rename(dn, newRDN, parentDN, true);
You may also need to check whether {this._ldapSettings.ContainerName} is replaced with CN=Users,DC=wxyzdev,DC=xyzdev,DC=ca to ensure dn variable is correctly set.
I had encountered this issue. Having come across this thread from google, it was not clear to use "CN=arshath shameer".
Please use "CN=arshath shameer" instead of "CN=arshath
shameer,CN=Users,DC=wxyzdev,DC=xyzdev,DC=ca" in the newRDN parameter.
Thanx.
FRS.
We have a winforms application that gets the groups from the active directory. Now everything worked fine till we have exchanged the old domaincontroller with a new one.
Since then, our application throws an exception when the method UserPrincipal.GetGroups() is called. The Exception is thrown because it tries to connect to the old DC.
The Exception message translated:
The Server is not operational surottdc04.TOSOT.CH
Does anyone have any idea, if the old dc-information is cached somewhere or where does the application get the old information?
In the following screenshot, you can see the code-section where the Exception is thrown:
As you can see, in the watch window, there is the correct new DC surottdc06, this was propably taken from the context of the current logged user. But in the Exception, there is still the old DC surottdc04, why?
UPDATE
So far we have found out that when we pass the context as paramter to the method, then it works but without the context, the method tries to connect to the old DC.
This is one possible solution, but the question is still, where does the method get the old DC information and tries to connect there, when the method is called parameterless?
public void GetGroups()
{
var sid = WindowsIdentity.GetCurrent().User.Value;
using (var context = new PrincipalContext(ContextType.Domain, "tosot.ch"))
{
using (var userPrinciple = UserPrincipal.FindByIdentity(context, sid))
{
/*
* this works, we just pass the context which we've used to
* create the UserPrincipal instance again to fetch the groups
*/
var ret = userPrinciple.GetGroups(context);
/*
* this works NOT: when calling without context argument,
* it seems, the context used is not the same
* as the userPrinciple instance is linked to.
* Instead it uses a selfmade context with an yet exsting,
* but currently not online domain controller - why that??
* (this 'old' domain controller is currently not running,
* but it's yet not removed from the domain ...)
*/
ret = userPrinciple.GetGroups();
}
}
}
My guess is that DNS is still returning the IP of the old DC.
From the command line, run:
nslookup tosot.ch
Do you see the IP for surottdc04? If so, that's your problem.
I have experienced this problem, although I have never been in a position to fix it. These instructions might help, but it looks like an old article so it may not be done the same way anymore: https://support.microsoft.com/en-us/help/555846
Update: Or you can use C# to see what DCs it sees for the current domain. See if the old one still shows up:
foreach (DomainController dc in Domain.GetCurrentDomain().DomainControllers) {
Console.WriteLine(dc.Name);
}
Following is the code that I have written,
var prevSecInfo = Directory.GetAccessControl(path);
if (Utilities.ShowChangePermissionsWindow(path)) {
var currSecInfo = Directory.GetAccessControl(path);
if (currSecInfo != prevSecInfo)
Utilities.ApplyPermissionsOnSubdirectories(path);
}
So, currently, I am getting the access control info before displaying the permissions window.
Next, I am displaying the permissions window which is actually the Security tab of the file/folder properties window. Changes can be made in permissions once it opens up.
But, in case no changes are made, I don't want to call my ApplyPermissionsOnSubdirectories() method. Hence, I am getting the access control info again in another variable and comparing the previous and the current info.
But, this isn't working. The comparison returns false even when no permissions are changed.
How can I check whether permissions have changed for the given path?
You cannot compare the contents of two reference type objects this way.
if (currSecInfo != prevSecInfo) will always return false, unless they both reference to the same object.
Unfortunately, DirectorySecurity type also does not provide Equals overriden method.
There is a StackOverflow article with some ready-made solutions for comparing permissions:
compare windows file (or folder) permissions
While working on the problem above, I found another solution which looks less complex and shorter is terms of code.
DirectorySecurity securityInfoBefore = Directory.GetAccessControl(path, AccessControlSections.Access);
string aclStrBefore = securityInfoBefore.GetSecurityDescriptorSddlForm(AccessControlSections.Access).ToString();
Here, path is the absolute path to the file/folder.
The aim to get the DirectorySecurity object before the permissions are changed and getting the SecurityDescriptorSddlForm as a string.
Now you can add your code to change the permissions. After the permissions are changed, add the following code,
DirectorySecurity securityInfoAfter = Directory.GetAccessControl(path, AccessControlSections.Access);
string aclStrAfter = securityInfoAfter.GetSecurityDescriptorSddlForm(AccessControlSections.Access).ToString();
The next step would be to compare the before and after strings.
if (aclStrBefore.Equals(aclStrAfter)) {
// permissions have not changed
} else {
// permissions have changed
}
This has helped me so far. Please feel free to add to my answer or correct it if required.
I have a custom LDAP schema installed on my OpenLDAP server which is as follows:
attributeType ( 999.0.01
NAME 'picturePath'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024}
)
objectClass ( 999.1.01
NAME 'indieStackTeam'
DESC 'Team definition for IndieStack'
SUP groupOfUniqueNames
STRUCTURAL
MAY ( picturePath )
)
In my ASP.NET MVC 2 application, I'm querying for the picturePath property like so (and it is confirmed that picturePath exists in the list of keys):
this.Picture = properties["picturePath"].Value as string;
When I attempt to do this under .NET 3.5 I get the following exception:
[COMException (0x8000500c): Unknown error (0x8000500c)]
System.DirectoryServices.PropertyValueCollection.PopulateList() +347013
System.DirectoryServices.PropertyValueCollection..ctor(DirectoryEntry entry, String propertyName) +49
System.DirectoryServices.PropertyCollection.get_Item(String propertyName) +150
However, when the same code runs under Mono (on the same server as OpenLDAP) it works perfectly fine. Clients such as LDAPAdmin can also read the picturePath property correctly.
More so, it's only when I go to read the value that it fails; I can see the property is there in the keys list, I just can't access it.
Unfortunately unknown error doesn't tell me a lot about what's going wrong, but I'm finding the .NET implementation of System.DirectoryServices is very flaky (you get the same unknown error if you connect to the LDAP server using lowercase in 'DC=').
Has anyone had this problem before and if so, how is it solved?
Two things you should check:
1) does that particular user object indeed have a value in picturePath? You might want to check for existance of the property before accessing it:
if(properties.Contains("picturePath") && properties["picturePath"].Count > 0)
{
....
}
2) If I remember correctly, to get access to custom attributes, you should explicitly refresh the cache for a user object before doing anything:
DirectoryEntry de = ......; // find / assign that DirectoryEntry somehow
de.RefreshCache(); // to load all properties from the directory
or:
de.RefreshCache(new string[] { "picturePath" }); // to just load the "picturePath" attribute
Also: the classes in System.DirectoryServices are really mostly geared towards being used against Active Directory - there might be "surprises" or subtle incompatibilities when used against some other LDAP server - like OpenLDAP.
It seems that the .NET LDAP client expects a correctly formed OID for attribute types and object classes.
You'll note that I was using OIDs of the form 999.X.YY, which while they might be syntactically correct, aren't usually encountered in the real world. My guess is the LDAP client parses OIDs and since these don't conform to what is expected, it throws an error.
I changed the OIDs to 1.3.6.1.4.1.40000.1.3.1 and 1.3.6.1.4.1.40000.1.4.1 respectively (I've also applied for a PEN, which will give me an assigned number instead of '40000'), refreshed the schema in the server and recreated the entries and the LDAP client now correctly reads the custom attributes.
I need to write a program that will take a existing local windows user, change the "Start the following program at logon field" in their environment tab and change the password to a new password.
I am using local users instead of AD, will System.DirectoryServices work? Also any tutorials on using System.DirectoryServices would be very helpful.
EDIT --
Ok, so I can find the existing user and I know how to update it. However I do not know what to put in the ???. What AD property represents "Start the following program at logon field" in their environment tab on the user settings page?
DirectorySearcher search = new DirectorySearcher();
search.Filter = String.Format("(SAMAccountName={0})", prams.user);
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
if (result == null)
{
Console.Error.WriteLine("No user of name {0} in the system", prams.user);
return -2;
}
DirectoryEntry entry = result.GetDirectoryEntry();
entry.InvokeSet("???", new object[] { newProgramLognField });
EDIT two---
I now know that the field I need is "Parameters" however I this term is very hard to search for in the MSDN because it is such a generic term. I think I have the correct one because when i run the code
DirectoryEntry entry = new DirectoryEntry("WinNT://testComputer/testUser");
Console.WriteLine("connected");
string value = (string)entry.Properties["Parameters"].Value;
Console.WriteLine(value);
I get the result
P☺CtxCfgPresent????☺CtxCfgFlags1
????☺CtxShadow????*☻☺CtxMinEncryptionLevel? #☺CtxWorkDirectory??????????????????
??????????????"C☺CtxInitialProgram??????????????????????????????????????????????
????????????????????????????????????????????????????????????????????????????????
????????
I am guessing that InitalProgram portion is close to what I want, most likely the ??? after is is the existing path that is there but has be mucked up.
Now the question is how do I write correctly to it?
Edit-3
I try changing the casting from a string to a PropertyValueCollection however that just gives me the error at run time
Unhandled Exception: System.InvalidCastException: Unable to cast object of type
'System.String' to type 'System.DirectoryServices.PropertyValueCollection'.
at UserUpdater.Program.Main(String[] args) in e:\Visual Studio 2008\Projects\
UserUpdater\UserUpdater\Program.cs:line 91
My copy of the TechNet Script Repository says that this WSH script can be used to change the password.
Set objComputer = GetObject _
("LDAP://CN=atl-dc-01,CN=Computers,DC=Reskit,DC=COM")
objComputer.SetPassword "whatever"