Below is simple code for creating a user in AD. The code is DC un-specific. It doesn't care which DC it creates it on, it'll use the windows default that the server is connected to.
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, Domain, path, ContextOptions.Negotiate, ManagementUsername, ManagementPassword))
{
try
{
using (UserPrincipal up = new UserPrincipal(pc, username, password, true))
{
up.GivenName = firstName; up.Surname = lastName; up.DisplayName = firstName + " " + lastName; up.UserPrincipalName = username + "@" + Domain; up.Save();
}
}
catch (PasswordException) { return null; }
}
The issue is that there is a replication time (usually domains have 15 minutes) of new accounts. This does not work when trying to implement an on-demand account creation when the account is requested by somebody wanting to use it on a workstation connected to a different DC than the server. They end up having to sit in front of the work station for up to 15 minutes being unable to log in.
Question: Is there a way to connect to a DC based on client IP address to create it on that one? OR is there a way to make the account on all DC's and have the replication be ok with this? OR force the account to replicate programmatically (based on searching through SO, I'm guessing no).
Forest adForest = Forest.GetCurrentForest();
ActiveDirectorySite[] sites = new ActiveDirectorySite[adForest.Sites.Count];
adForest.Sites.CopyTo(sites, 0);
List<ActiveDirectorySubnet> subnets = new List<ActiveDirectorySubnet>();
sites.ToList().ForEach(x =>
{
ActiveDirectorySubnet[] subnetTemp = new ActiveDirectorySubnet[x.Subnets.Count];
x.Subnets.CopyTo(subnetTemp, 0);
subnets.AddRange(subnetTemp);
});
IPAddress address = IPAddress.Parse("IPAddress to look up closest DC");
var currentSubnet = subnets.Where(x => address.IsInRange(x.Name));
var location = currentSubnet.First().Site.Name;
DomainController dc = DomainController.FindOne(new DirectoryContext(DirectoryContextType.Domain, Domain), location);
This gets you the DC associated with that site and domain that is nearest the specified IP address within the topology. Then you pass the DC IP address to the Principal Context.
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, dc.IPAddress, path, ContextOptions.Negotiate, ManagementUsername, ManagementPassword))
{
try
{
using (UserPrincipal up = new UserPrincipal(pc, username, password, true))
{
up.GivenName = firstName; up.Surname = lastName; up.DisplayName = firstName + " " + lastName; up.UserPrincipalName = username + "@" + Domain; up.Save();
}
}
catch (PasswordException) { return null; }
}
And create a user.
Note: IPAddress functions were done via NetTools IPAddressRange class on github and the following custom extensions of it.
/// <summary>
/// All extensions for IPAddress type
/// </summary>
public static class IPAddressExtension
{
/// <summary>
/// Determine whether this IP address is part of the range/subnet
/// </summary>
/// <param name="range">A range of IPAddresses</param>
/// <returns></returns>
public static bool IsInRange(this IPAddress thisIP, IPAddressRange range)
{
return range.Contains(thisIP);
}
/// <summary>
/// Determine whether this IP address is part of the range/subnet
/// </summary>
/// <param name="range">Can be specified in CIDR/UNI (ex: 192.168.10.0/24) </param>
/// <returns></returns>
public static bool IsInRange(this IPAddress thisIP, string rangeIP)
{
IPAddressRange range = IPAddressRange.Parse(rangeIP);
return range.Contains(thisIP);
}
/// <summary>
/// Determine whether this IP address is part of the range/subnet
/// </summary>
/// <param name="ipBegin">Beginning IP address of range</param>
/// <param name="ipEnd">Ending IP address of range</param>
/// <returns></returns>
public static bool IsInRange(this IPAddress thisIP, IPAddress ipBegin, IPAddress ipEnd)
{
IPAddressRange range = new IPAddressRange(ipBegin, ipEnd);
return range.Contains(thisIP);
}
}