I'm trying to add an user to the active directory via a C# script. I've found this script on the internet (I didn't made it myself). The problem is that I get this error when I'm trying to add an user:
The specified directory service attribute or value does not exist.
This is the code I have right now:
private void buttonCreateUser_Click(object sender, EventArgs e)
{
CreateADSUser(textboxUsername.Text, textboxPassword.Text);
}
public string CreateADSUser(string username, string password)
{
String RootDSE;
try
{
DirectorySearcher DSESearcher = new DirectorySearcher();
RootDSE = DSESearcher.SearchRoot.Path;
RootDSE = RootDSE.Insert(7, "CN=Users,");
DirectoryEntry myDE = new DirectoryEntry(RootDSE);
DirectoryEntries myEntries = myDE.Children;
DirectoryEntry myDirectoryEntry = myEntries.Add("CN=" + username, "user");
myDirectoryEntry.Properties["userPrincipalName"].Value = username;
myDirectoryEntry.Properties["name"].Value = username;
myDirectoryEntry.Properties["Password"].Value = password;
myDirectoryEntry.Properties["samAccountName"].Value = username;
myDirectoryEntry.Properties["FullName"].Value = username;
myDirectoryEntry.Properties["AccountDisabled"].Value = 0;
myDirectoryEntry.Properties["PasswordRequired"].Value = 1;
// Permanent Password?
myDirectoryEntry.Properties["permpass"].Value = 1;
myDirectoryEntry.CommitChanges();
DSESearcher.Dispose();
myDirectoryEntry.Dispose();
textboxReports.Text = "Worked!";
return "Worked!";
}
catch (Exception ex)
{
textboxReports.Text = ex.Message;
return ex.Message;
}
}
Nevermind, I've got the fix!
This is what it looks like right now:
using (var pc = new PrincipalContext(ContextType.Domain))
{
using (var up = new UserPrincipal(pc))
{
up.SamAccountName = textboxUsername.Text; // Username
up.EmailAddress = textboxEmail.Text; // Email
up.SetPassword(textboxPassword.Text); // Password
up.Enabled = true;
up.ExpirePasswordNow();
up.Save();
}
}
The issue here is that none of these properties actually exist:
myDirectoryEntry.Properties["Password"].Value = password;
myDirectoryEntry.Properties["FullName"].Value = username;
myDirectoryEntry.Properties["AccountDisabled"].Value = 0;
myDirectoryEntry.Properties["PasswordRequired"].Value = 1;
myDirectoryEntry.Properties["permpass"].Value = 1;
This one isn't one you write to:
myDirectoryEntry.Properties["name"].Value = username;
In order (from top to bottom) here are the actual attribute names:
Password - unicodePwd
FullName - displayName
AccountDisabled - userAccountControl
PasswordRequired - userAccountControl (actually you set the inverse - only if a password isn't required)
permPass - unicodePwd (not sure what the goal was with this one)
By System.DirectoryServices.AccountManagement ..
PrincipalContext ouContex = new PrincipalContext(ContextType.Domain, "TestDomain.local", "OU=TestOU,DC=TestDomain,DC=local");
for (int i = 0; i < 3; i++)
{
try
{
UserPrincipal up = new UserPrincipal(ouContex);
up.SamAccountName = "TestUser" + i;
up.SetPassword("password");
up.Enabled = true;
up.ExpirePasswordNow();
up.Save();
}
catch (Exception ex)
{
}
}
By System.DirectoryServices
To use this namespace you need to add reference System.DirectoryServices.dll
DirectoryEntry ouEntry = new DirectoryEntry("LDAP://OU=TestOU,DC=TestDomain,DC=local");
for (int i = 3; i < 6; i++)
{
try
{
DirectoryEntry childEntry = ouEntry.Children.Add("CN=TestUser" + i, "user");
childEntry.CommitChanges();
ouEntry.CommitChanges();
childEntry.Invoke("SetPassword", new object[] { "password" });
childEntry.CommitChanges();
}
catch (Exception ex)
{
}
}
Related
Update:
For me, LDAP way only worked for finding email addresses inside AD groups, for eg: named ITSolutionDeliveryDevelopers group. NOT inside Exchange Distribution Lists, for eg: named abc#domainname.com.
// I was able to figure out entry as suggested by #Gabriel Luci and
// all of the following possible formats worked for me:
// ngroupnet.com is my company domain name.
var entry = new DirectoryEntry();
var entry = new DirectoryEntry("LDAP://ngroupnet.com");
var entry = new DirectoryEntry("LDAP://ngroupnet.com", "MyAccountUsername", "MyAccountPassword");
var entry = new DirectoryEntry("LDAP://ngroupnet.com", "MyName#mycompany.com", "MyEmailAccountPassword");
For my complete answer, take a look below: https://stackoverflow.com/a/71518937/8644294
Original Question:
What is the best way to get all the individual email addresses comprising an exchange distribution list?
For eg: I have this distribution list called abc#domainname.com that has email addresses:
a#domainname.com
b#domainname.com
c#domainname.com
Now I need to get these addresses using C# code.
I found solution using LDAP but I felt it'd be a hassle to figure out LDAP path to my Active Directory.
// How do I get this LDAP Path, username and password?
// Is the username and password service account credentials of the app?
// And do they need to be registered in AD?
var entry = new DirectoryEntry("LDAP Path");//, username, password);
LDAP Way:
public static List<string> GetDistributionListMembers(string dlName = "abc#domainname.com")
{
var result = new List<string>();
try
{
// How do I get this LDAP Path?
var entry = new DirectoryEntry("LDAP Path");//, username, password);
var search = new DirectorySearcher(entry);
search.Filter = $"CN={dlName}";
int i = search.Filter.Length;
string str = "", str1 = "";
foreach (SearchResult AdObj in search.FindAll())
{
foreach (String objName in AdObj.GetDirectoryEntry().Properties["member"])
{
str += Convert.ToString(objName) + "<Br>";
int selIndex = objName.IndexOf("CN=") + 3;
int selEnd = objName.IndexOf(",OU") - 3;
str1 += objName.Substring(selIndex, selEnd).Replace("\\", "");
DirectorySearcher dsSearch = new DirectorySearcher(entry);
dsSearch.Filter = "CN=" + objName.Substring(selIndex, selEnd).Replace("\\", "");
foreach (SearchResult rs in dsSearch.FindAll())
{
//str1 += "<p align='right'><font face='calibri' color='#2266aa' size=2>" + Convert.ToString(rs.GetDirectoryEntry().Properties["mail"].Value) + "|" + Convert.ToString(rs.GetDirectoryEntry().Properties["displayName"].Value) + "|" + Convert.ToString(rs.GetDirectoryEntry().Properties["sAMAccountName"].Value) + "|" + Convert.ToString(rs.GetDirectoryEntry().Properties["department"].Value) + "|" + Convert.ToString(rs.GetDirectoryEntry().Properties["memberOf"].Value) + "</font></p>";
str1 = Convert.ToString(rs.GetDirectoryEntry().Properties["mail"].Value);
result.Add(str1);
}
}
}
return result;
}
catch (Exception ex)
{
//Do some logging or what have you.
throw;
}
}
So I just went with the EWS route.
EWS Way:
public static static List<string> GetDistributionListMembers(string dlName = "abc#domainname.com")
{
try
{
var service = new ExchangeService();
var cred = new WebCredentials("sharedmailbox#domain.com", "some_password");
service.Credentials = cred;
service.Url = new Uri("https://outlook.office365.com/ews/exchange.asmx");
service.TraceEnabled = true;
service.TraceFlags = TraceFlags.All;
var expandedEmailAddresses = new List<string>();
ExpandGroupResults myGroupMembers = service.ExpandGroup(dlName);
foreach (EmailAddress address in myGroupMembers.Members)
{
expandedEmailAddresses.Add(address.Address);
}
return expandedEmailAddresses;
}
catch (Exception ex)
{
// The DL doesn't have any members. Handle it how you want.
// Handle/ Log other errors.
}
}
Is EWS approach a good way?
If Yes, then I'm good. If not, I'll have to figure out that LDAP path.
Or if there's even a better way, please let me know.
If the computer you run this from is joined to the same domain as the group you're looking for, then you don't need to figure out the LDAP path. You can just do:
var search = new DirectorySearcher();
If your computer is not joined to the same domain, then you just use the domain name:
var entry = new DirectoryEntry("LDAP://domainname.com");
This requires that there is no firewall blocking port 389 between your computer and the domain controller(s). If you need to pass credentials, then do that:
var entry = new DirectoryEntry("LDAP://domainname.com", username, password);
The credentials can be any user on the domain.
That said, there are a lot of inefficiencies in your code that will make it run much slower than needed. I wrote an article about this that can help you update your code: Active Directory: Better Performance
Is EWS approach a good way?
If it works, it works. I'm not an expert on EWS (although I have used it), but I'm fairly certain that's using Basic Authentication, which is going to be disabled in October.
If all the Mailboxes are on Office365 then i would suggest you use the Graph API instead eg https://learn.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-1.0&tabs=http . There are several advantage in terms of security eg you could use Application permissions and all you need is access to the directory while if you did the same thing in EWS it would require full access to at least one mailbox.
LDAP will be best performing of the 3 if ultimate speed in the only thing that is important.
My complete solution for future reference. :)
EWS Way - For expanding Exchange Distribution Lists
public class SomeHelper
{
private static ExchangeService _exchangeService = null;
public static async Task<HashSet<string>> GetExchangeDistributionListMembersAsync(IEnumerable<string> dlNames)
{
var allEmailAddresses = new HashSet<string>();
foreach (var dlName in dlNames)
{
if (!SomeCache.TryGetCachedItem(dlName, out var dlMembers))
{
var groupEmailAddresses = new List<string>();
var exchangeService = await GetExchangeServiceAsync();
try
{
var myGroupMembers = exchangeService.ExpandGroup(dlName);
// Add the group members.
foreach (var address in myGroupMembers.Members)
{
groupEmailAddresses.Add(address.Address);
}
}
catch (Exception ex)
{
//If it can't expand the dlName, just return it.
groupEmailAddresses.Add(dlName);
//groupEmailAddresses.Add($"Attempting to expand '{dlName}' resulted in error message: '{ex.Message}'.");
}
// Cache the groupEmailAddresses for 7 days.
// Because Distribution Lists rarely change and expanding DL is an expensive operation.- AshishK Notes
SomeCache.AddItemToCache(dlName, groupEmailAddresses, 10080);
allEmailAddresses.UnionWith(groupEmailAddresses);
}
else
{
allEmailAddresses.UnionWith((List<string>)dlMembers);
}
}
return allEmailAddresses;
}
private static async Task<ExchangeService> GetExchangeServiceAsync()
{
if (_exchangeService == null)
{
_exchangeService = new ExchangeService();
var exchangeUrl = "https://outlook.office365.com/ews/exchange.asmx";
var cred = new WebCredentials("sharedmailbox#domain.com", "some_password");
_exchangeService.Credentials = cred;
//_exchangeService.AutodiscoverUrl("sharedmailbox#domain.com");
_exchangeService.Url = new Uri(exchangeUrl);
_exchangeService.TraceEnabled = true;
_exchangeService.TraceFlags = TraceFlags.All;
return _exchangeService;
}
else
{
return _exchangeService;
}
}
}
public class SomeCache
{
private static readonly ObjectCache _cache = MemoryCache.Default;
public static void AddItemToCache(string key, object itemToAdd, int cacheDurationMinutes)
{
var _policy = new CacheItemPolicy
{
Priority = CacheItemPriority.Default,
AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(cacheDurationMinutes)
};
_cache.Set(key, itemToAdd, _policy);
}
public static bool TryGetCachedItem(string key, out object cachedObject)
{
try
{
cachedObject = _cache[key] as object;
}
catch (Exception ex)
{
cachedObject = null;
}
return !(cachedObject == null);
}
}
LDAP Way - For expanding Active Directory Groups
public static List<string> GetADGroupDistributionListMembers(string adGroupName)
{
var returnResult = new List<string>();
var entry = GetDirectoryEntry();
DirectorySearcher groupSearch = new DirectorySearcher(entry)
{
Filter = "(SAMAccountName=" + adGroupName + ")"
};
groupSearch.PropertiesToLoad.Add("member");
SearchResult groupResult = groupSearch.FindOne(); // getting members who belong to the adGroupName
if (groupResult != null)
{
for (int iSearchLoop = 0; iSearchLoop < groupResult.Properties["member"].Count; iSearchLoop++)
{
string userName = groupResult.Properties["member"][iSearchLoop].ToString();
int index = userName.IndexOf(',');
userName = userName.Substring(0, index).Replace("CN=", "").ToString(); // the name of the user will be fetched.
DirectorySearcher search = new DirectorySearcher(entry)
{
Filter = "(name=" + userName + ")"
};
search.PropertiesToLoad.Add("mail");
SearchResult result = search.FindOne(); //finding the mail id
if (result != null)
{
returnResult.Add(result.Properties["mail"][0].ToString());
}
}
}
return returnResult;
}
public static DirectoryEntry GetDirectoryEntry()
{
DirectoryEntry entryRoot = new DirectoryEntry("LDAP://RootDSE");
string Domain = (string)entryRoot.Properties["defaultNamingContext"][0];
DirectoryEntry de = new DirectoryEntry
{
Path = "LDAP://" + Domain,
AuthenticationType = AuthenticationTypes.Secure
};
return de;
}
I'm writing some kind of a mini AD tool (with VS-C#) to our organization and got into an issue.
I have a main function that searches the user (when I click on it in a listview) and some functions that manipulate the user's object.
public DirectoryEntry GetUser(string username)
{
try
{
Forest currentForest = Forest.GetCurrentForest();
GlobalCatalog gc = currentForest.FindGlobalCatalog();
using (DirectorySearcher searcher = gc.GetDirectorySearcher())
{
searcher.Filter = "(&((&(objectCategory=Person)(objectClass=User)))(samaccountname=" + username + "*))";
SearchResult results = searcher.FindOne();
if (!(results == null))
{
DirectoryEntry de = new DirectoryEntry(results.Path, strAdminUser, strAdminPass, AuthenticationTypes.Secure);
de.RefreshCache(new string[] { "canonicalName" });
de.Path = de.Properties["canonicalName"].Value.ToString();
de.CommitChanges();
return de;
}
else
{
return null;
}
}
}
catch (DirectoryServicesCOMException e)
{
System.Windows.Forms.MessageBox.Show(e.Message);
return null;
}
}
and here's an example of a function that checks if the user is locked:
public bool IsUserLocked(string username)
{
try
{
DirectoryEntry de = GetUser(username);
string attName = "msDS-User-Account-Control-Computed";
de.RefreshCache(new string[] { attName });
const int UF_LOCKOUT = 0x0010;
int userFlags = /*(int)*/Convert.ToInt32(de.Properties[attName].Value);
if ((userFlags & UF_LOCKOUT) == UF_LOCKOUT)
{
return true;
}
de.Dispose();
return false;
}
catch (DirectoryServicesCOMException e)
{
System.Windows.Forms.MessageBox.Show(e.Message);
return false;
}
}
The function that checks the locked status of a user always fails with an error: "Unspecified error", but if I'm not changing the Directory Entry's path in the first function I get "The server is unwilling to process the request" error (I'm using proper service username and password with all the permissions needed) but still it happens.
Can someone spot the issue?
How about using System.DirectoryServices.AccountManagement namespace? If you have no issue using the new namespace, there's a simpler way to check if user account is locked and unlock if needed.
public bool IsUserLocked (string username)
{
using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "yourdomain.com")
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(ctx, username)
{
if (user != null) return user.IsAccountLockedOut();
}
}
return null;
}
And similarly, you can unlock the user account if needed.
...
if (user != null)
{
user.UnlockAccount();
user.Save();
}
Got it...
This has solved my issue:
de.Path = results.Path.Replace("GC://DCNAME.", "LDAP://");
Since I'm searcing on the Global Catalog, I had to replace a portion in the path to match it to the correct path:
public DirectoryEntry GetUser(string username)
{
try
{
Forest currentForest = Forest.GetCurrentForest();
GlobalCatalog gc = currentForest.FindGlobalCatalog();
using (DirectorySearcher searcher = gc.GetDirectorySearcher())
{
searcher.Filter = "(&((&(objectCategory=Person)(objectClass=User)))(samaccountname=" + username + "*))";
SearchResult results = searcher.FindOne();
if (!(results == null))
{
DirectoryEntry de = new DirectoryEntry(results.Path, strAdminUser, strAdminPass, AuthenticationTypes.Secure);
de = new DirectoryEntry(results.Path);
de.Path = results.Path.Replace("GC://DCNAME.", "LDAP://");
de.CommitChanges();
//System.Windows.Forms.MessageBox.Show(de.Path);
return de;
}
else
{
return null;
}
}
}
catch (DirectoryServicesCOMException e)
{
System.Windows.Forms.MessageBox.Show(e.Message);
return null;
}
}
Now the path returns to the function called GetUser in the correct format :)
public async Task<IActionResult> Contact1()
{
if (Convert.ToBoolean(HttpContext.Session.GetString("login")))
{
var pass = new ContactViewModel();
var username = HttpContext.Session.GetString("username");
Program.readname(HttpContext.Session.GetString("username"));
var names = HttpContext.Session.GetString("studentnames");
var obj1 = JsonConvert.DeserializeObject<Program.Data>(names);
if (Program.datecheck(username, DateTime.Today.Date))
{
try{
var handler = new HttpClientHandler { Credentials = new NetworkCredential(user, password) };
using (var client = Program.CreateHttpClient(handler, user, database3))
{
string check = username + Convert.ToString(DateTime.Today.Date);
var readresponse = client.GetStringAsync(check).Result;
var obj2 = JsonConvert.DeserializeObject<Program.Data>(readresponse);
}
catch(Exception ee)
{ ViewBag.m6 = ee.Message; ViewBag.attendance = "Attendace is not take yet";}
}
pass.studentattend = obj2.studentattend1;
}
}
else { ViewBag.attendance = "Attendace is not take yet"; }
pass.studentname = obj1.studentname1;
pass.studentrollno = obj1.studentrollno1;
pass.date = DateTime.Today.Date;
HttpContext.Session.SetInt32("classselect", 1);
ViewData["Message"] = "Student Attendance of Class: " + HttpContext.Session.GetString("classname1");
ViewBag.Login = HttpContext.Session.GetString("login");
ViewBag.name = HttpContext.Session.GetString("name");
ViewBag.classname1 = HttpContext.Session.GetString("classname1");
ViewBag.classname2 = HttpContext.Session.GetString("classname2");
ViewBag.classname3 = HttpContext.Session.GetString("classname3");
ViewBag.classname4 = HttpContext.Session.GetString("classname4");
return View("/Views/Home/Contact.cshtml", pass);
}
else
{
ViewData["Message"] = "Please Login First!!";
return View("/Views/Home/Login.cshtml");
}
}
The above code is runnig well in my local ISS server but when i run this on bluemix then i am getting blank page. I tried to find out the problem and get to the conclusion that if the control does not enter in the if part of that code:
if (Program.datecheck(username, DateTime.Today.Date))
{
var handler = new HttpClientHandler { Credentials = new NetworkCredential(user, password) };
using (var client = Program.CreateHttpClient(handler, user, database3))
{
string check = username + Convert.ToString(DateTime.Today.Date);
var readresponse = client.GetStringAsync(check).Result;
var obj2 = JsonConvert.DeserializeObject<Program.Data>(readresponse);
pass.studentattend = obj2.studentattend1;
}
}
else { ViewBag.attendance = "Attendace is not take yet"; }
then it will run fine.I am unable to find what is wrong in that query.
I'm trying to get the manager name from the active directory but am receiving the error "An operations error occured" when it throws the exception.
Code is below:
public override void ItemAdding(SPItemEventProperties properties)
{
base.ItemAdding(properties);
try
{
var requester = properties.Web.CurrentUser;
properties.AfterProperties["Requester"] = requester;
//Get the manager name from the active directory
var domain = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName;
DirectoryEntry dir = new DirectoryEntry("LDAP://" + domain);
//Exeception occurs on this line below.
string managerName = dir.Properties["Manager"].Value.ToString();
properties.AfterProperties["Manager"] = managerName;
}
catch(Exception ex)
{
}
}
Edit
Was able to get this figured out using the code below:
try
{
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, Environment.UserName);
string samAccountName = "";
if (user != null)
{
// do something here....
samAccountName = user.SamAccountName;
}
//Get the manager name from the active directory
var domain = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName;
using(DirectoryEntry dir = new DirectoryEntry("LDAP://" + domain))
{
using (DirectorySearcher ds = new DirectorySearcher(dir, "samAccountName=" + samAccountName))
{
SearchResult result = ds.FindOne();
string managerName = result.Properties["manager"][0].ToString();
}
}
}
catch (Exception ex)
{
var message = ex.Message;
}
You are trying to access the Manager from the domain, not from requester.
In a winform I would do it like this assuming that requester == samAccountName:
try
{
//Get the manager name from the active directory
var domain = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName;
using (DirectoryEntry dir = new DirectoryEntry("LDAP://" + domain))
{
using (DirectorySearcher ds = new DirectorySearcher(dir, "samAccountName=" + requster))
{
SearchResult sr = ds.FindOne();
//Exeception occurs on this line below, if the attribute is not set.
string managerName = sr.Properties["Manager"][0].ToString();
}
}
}
catch (Exception ex)
{
}
I'd like check the the login and the password match with the AD info. I tried with this piece of coode but I get an exception on FindOne (bad username or password .. but they are correct). I know there is the PrincipalContext solution but I need to be able to set the server (Production, Dev, ...)
Thanks,
var Ad = new DirectoryEntry("LDAP://server1.domain.com", username, password);
var AdSearcher = new DirectorySearcher(Ad);
AdSearcher.Filter = String.Format("(anr={0})", username);
AdSearcher.PropertiesToLoad.Add("sAMAccountName");
AdSearcher.PropertiesToLoad.Add("displayName");
var AdSearcherResults = AdSearcher.FindOne();
var userFullName = AdSearcherResults.Properties["displayName"][0].ToString();
var userUid = AdSearcherResults.Properties["sAMAccountName"][0].ToString();
if (Membership.ValidateUser(username, userUid))
return true;
return false;
Update1 I tried this too :
using (var context = new PrincipalContext(ContextType.Domain, "server1.domain.com"))
{
var isValid = context.ValidateCredentials(username, password);
}
My computer is not connected on the domain but should be work I think.
My code for ActiveDirectory Auth.
public DirectoryEntry connDirectory(string usr, string pwd)
{
string ip = iniMan.IniRead("LDAP", "adres");
DirectoryEntry oDE;
oDE = new DirectoryEntry(ip, usr, pwd, AuthenticationTypes.Secure);
return oDE;
}
public bool AD_Login(string kullanici_adi, string sifre)
{
try
{
DirectoryEntry entLogin = connDirectory(kullanici_adi, sifre);
object loginObj = entLogin.NativeObject;
return true;
}
catch (Exception ex)
{
return false;
}
}
void TestMetod(){
if(AD_Login("ozan","ozan"){
//ok
}
}