I am creating Active Directory groups in my app. I make security and distribution groups. The groups will get created just fine, but it takes about 10-15 minutes to show up in the Active Directory Users and Computers.
Is there some kind of forced sync I can perform in C# to make this happen sooner? Or maybe some setting I can change in my directory to change this behavior?
Example code
DirectoryEntry ou1= topLevel.Children.Find("OU=ou1");
DirectoryEntry secGroups = ou1.Children.Find("OU=Security Groups");
DirectoryEntry newGroup = secGroups.Children.Add("CN=" + name + "", "group");
newGroup.CommitChanges();
GroupPrincipal createdGroup = GroupPrincipal.FindByIdentity(this._context, name);
createdGroup.SamAccountName = name;
createdGroup.DisplayName = name;
createdGroup.GroupScope = GroupScope.Universal;
createdGroup.Save();
if (members.Any())
{
foreach (var item in members)
{
createdGroup.Members.Add(this._context, IdentityType.SamAccountName, item);
}
createdGroup.Save();
}
Using ASP.NET MVC, C#, System.DirectoryServices.AccountManagement, System.DirectoryServices.ActiveDirectory.
The most likely answer is that it takes time to propagate to all domain controllers on your network. You may be connected to a different DC via ADUC from the one your application updated.
Something that might help in this situation where you have multiple domain controllers replicating is to target a specific DC for each call you make to the AD server.
So instead of "LDAP://mydomain.com" it becomes something like "LDAP://myDC.mydomain.com"
Related
I have a C#/WPF application that is used to manipulate local users and groups on a system. We only care about local users and groups, regardless of whether the machine is joined to a domain or not. When we create a user in our application, I want to add the user to the "Users" group. Normally this works fine, but if the machine is domain-joined and NOT connected to the network (e.g. a laptop out of the office), I get "the network path is not found" errors when trying to add a local user to the "Users" group.
I think the reason is because the "Users" group contains domain users, as shown in this screenshot.
And this is essentially my code:
public static void AddUserToGroup(UserPrincipal oUserPrincipal, string groupName)
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Machine))
{
GroupPrincipal group = GroupPrincipal.FindByIdentity(pc, groupName);
if (group == null)
{
group = CreateLocalWindowsGroup(groupName);
}
if (!group.Members.Contains(oUserPrincipal)) // this line throws "network path not found" exception if the machine is domain joined, but can't contact the domain controller
group.Members.Add(oUserPrincipal);
group.Save();
}
}
I can't figure out how to approach this with the API, but it seems like it should be possible because I can add the exact same user to the same group manually with the "Local Users and Groups" tool with no issues, regardless of network connectivity. How can I get around this issue?
This is one reason I don't like using the AccountManagement namespace.
The GroupPrincipal.Members property returns a PrincipalCollection, which is just a collection of Principal objects. The actual type will be UserPrincipal or GroupPrincipal depending on what the actual member is.
But, those Principal classes, when they're created, load all the details for that object. So just the act of creating a UserPrincipal for a domain user triggers it to go out to the domain and get all the details for the user.
You're better off using DirectoryEntry directly, which is what the AccountManagement namespace uses in the background anyway. It gives you more control over what's actually happening.
var usersGroup = new DirectoryEntry($"WinNT://{Environment.MachineName}/{groupName}");
usersGroup.Invoke("Add", new object[] { $"WinNT://{Environment.MachineName}/{userName}" });
This assumes a userName variable with the name of the local user. If the user is already in the group, it will throw an exception, so you may want to catch that.
Besides actually working in this case, this will run faster since you're not wasting time collecting details for all the existing members when you have no intention of using any of that data.
Update: To read all the members of a local group, use .Invoke("Members"). Then you have to create a new DirectoryEntry with each member in the collection. For example:
foreach (var member in (IEnumerable) usersGroup.Invoke("Members")) {
using (var memberDe = new DirectoryEntry(member)) {
Console.WriteLine(memberDe.Name);
}
}
The DirectoryEntry class is really a wrapper around the Windows native ADSI Interfaces. For a group, the underlying object will really be IADsGroup. When you call .Invoke on a DirectoryEntry object, that lets you call the IADsGroup methods (you'll see the Members method listed in the documentation there). All of the object-specific classes like IADsGroup and IADsUser all inherit from IADs, so the methods from that are usable too.
This only applies to local groups. With Active Directory groups, you don't have to resort to using the IADs methods.
I need to do LDAP query across all DCs of Forest. I found similar post, from which is following code:
using (Forest currentForest = Forest.GetCurrentForest())
{
using (GlobalCatalog globalCatalog = currentForest.FindGlobalCatalog())
{
using (DirectorySearcher searcher = globalCatalog.GetDirectorySearcher())
{
searcher.Filter = "filter to verify existence of user account according to supplied sid";
SearchResult result = searcher.FindOne();
return (result != null);
}
}
}
Above code works in my test scenario. Is this the correct way to query information across domains? There are a lot of other posts where they manually enter GC location or search through list of domains. Therefore is there anything I should pay attention to or that could go wrong with this code?
Use the Domains property of System.DirectoryServices.ActiveDirectory.Forest to iterate through the domains and then the DomainControllers property of System.DirectoryServices.ActiveDirectory.Domain to iterate though domain controllers, running whatever query you want on each.
I have a pretty simple task – given the list (from file) of user names and their emails, synchronize it with the members of AD (Active Directory) users, meaning – add users that exist in the list and missing in AD group, and remove users that exist in AD group and missing in the list. The complication is that the company is very big and uses a lot of domains around the world based on their location – let’s say it.comany.com, fr.company.com, uk.company.com, us.company.com, etc. – each of them contains tons of users and users to add can be in any of them.
I use the following code to find the AD group:
var groupSearch = new DirectorySearcher(new DirectoryEntry(“LDAP://company.com”), “(&(ObjectClass=Group)(CN=TheNameOfTheADGroup))”);
var dirEntry = new DirectoryEntry(groupSearch.FindOne().Path);
and it finds the group successfully.
Now, I need to add user to this group (knowing only his username, like willsmith). This can be done by calling
dirEntry.Invoke(“Add”, new object[] { dirPath })
where dirPath is the FULL LDAP path of the user. I can get it by searching in specific domain, e.g.:
var userSearch = new DirectorySearcher(new DirectoryEntry(“LDAP://xx.company.com”), “(&(ObjectClass=User)(CN=willsmith))”);
var dirPath = userSearch.FindOne().Path;
//and then use dirPath in Invoke(“Add”…) – see above
but the problem is that I don’t know which exact domain the user is in, and the number of domains is around 30, each user search takes about 2-3 seconds, so it will be a very long process.
Any ideas how to define domain for the user? Or maybe some other solution for the problem?
P.S. I do not use the new AD management capabilities introduced in .NET 3.5 (PrincipalContext, GroupPrincipal.FindByIdentity), because they don’t allow to specify the root for the search (which was the first parameter in DirectorySearcher constructor), which I need because the AD group is located in company.com domain not us.company.com from where is my account.
I am working on a web application, ASP.NET, C#. Users are required to log in using an account local to the machine the app is running on, which I'll call "cyclops" for this example. I want the app to be able to query the local directory of users and groups to determine what groups the user is in. The code looks something like this:
DirectoryEntry entry = new DirectoryEntry("WinNT://cyclops/Users", "SomeServiceAccount",
"SvcAcctP#$$word", AuthenticationTypes.Secure);
entry.RefreshCache();
// Etc.
My two problems are:
That's pretty clearly not the correct path to use, but my research
and experimentation hasn't found the right answer. This MSDN
article talks about local paths, but doesn't fill in the blanks.
Do I use "LDAP://cyclops/Users", "WinNT://localhost/Users",
"WinNT://cyclops/cn=Users"?
As you can see, I'm providing the
credentials of a local service account. That account needs
permission to access the local directory, but I have no idea where
to set those permissions. Is it a specific file somewhere? Does
the account need to be a member of a particular group?
My experimentation has produced many errors: "The group name could not be found.", "The provider does not support searching...", "The server is not operational.", "Unknown error (0x80005004)", etc.
Thank you for your time...
-JW
WinNT requires the following format
WinNT://<domain/server>/<object name>,<object class>
To get groups of a given user, use
using (DirectoryEntry user = new DirectoryEntry("WinNT://./UserAccount,user"))
{
foreach(object group in (IEnumerable)user.Invoke("Groups",null))
{
using(DirectoryEntry g = new DirectoryEntry(group))
{
Response.Write(g.Name);
}
}
}
where
UserAccount is a name of required user.
dot stands for current machine (you can replace it with cyclops or use Environment.MachineName)
user credentials ("SomeServiceAccount", "SvcAcctP#$$word") might be required, depends on setup
To get users in a particular group, use
using (DirectoryEntry entry = new DirectoryEntry("WinNT://./Users,group"))
{
foreach (object member in (IEnumerable)entry.Invoke("Members"))
{
using(DirectoryEntry m = new DirectoryEntry(member))
{
Response.Write(m.Name);
}
}
}
where
Users is a name of group
In my Sharepoint code I display a list of all defined users via:
foreach (SPUser user in SPContext.Current.Web.AllUsers)
{
...
}
The great part is, I can add a domain security group to a Sharepoint group (like Visitors) thus adding many users at once (simpler administration). But my code doesn't see those users at least not until they log-in for the first time (if they have sufficient rights). In this case I can only see the domain security group SPUser object instance with its IsDomainGroup set to true.
Is it possible to get domain group members by means of Sharepoint without resorting to Active Directory querying (which is something I would rather avoid because you probably need sufficient rights to do such operations = more administration: Sharepoint rights + AD rights).
You can use the method SPUtility.GetPrincipalsInGroup (MSDN).
All parameters are self-explaining except string input, which is the NT account name of the security group:
bool reachedMaxCount;
SPWeb web = SPContext.Current.Web;
int limit = 100;
string group = "Domain\\SecurityGroup";
SPPrincipalInfo[] users = SPUtility.GetPrincipalsInGroup(web, group, limit, out reachedMaxCount);
Please note that this method does not resolve nested security groups. Further the executing user is required to have browse user info permission (SPBasePermissions.BrowseUserInfo) on the current web.
Update:
private void ResolveGroup(SPWeb w, string name, List<string> users)
{
foreach (SPPrincipalInfo i in SPUtility.GetPrincipalsInGroup(w, name, 100, out b))
{
if (i.PrincipalType == SPPrincipalType.SecurityGroup)
{
ResolveGroup(w, i.LoginName, users);
}
else
{
users.Add(i.LoginName);
}
}
}
List<string> users = new List<string>();
foreach (SPUser user in SPContext.Current.Web.AllUsers)
{
if (user.IsDomainGroup)
{
ResolveGroup(SPContext.Current.Web, user.LoginName, users);
}
else
{
users.Add(user.LoginName);
}
}
Edit:
[...] resorting to Active Directory querying (which is something I would rather avoid because you probably need sufficient rights to do such operations [...]
That's true, of course, but SharePoint has to lookup the AD as well. That's why a application pool service account is required to have read access to the AD.
In other words, you should be safe executing queries against the AD if you run your code reverted to the process account.
I would suggest you just query Active Directory directly. You are spending a lot of effort to try to get SharePoint to make this call to AD for you. Every account that has Domain User access should be able to query the AD groups you have nested in SharePoint. I would just go to the source.
This way you don't have to worry about Browse User Permissions or anything else. In my opinion trying to proxy this through SharePoint is just making your life more difficult.