I have find the Ipaddress and Hostname using the active directory
ComputerPrincipal method.but not find the username in this method.i need the username in my domain .how to do this?
here it my code
namespace Directory
{
class Program
{
static void Main(string[] args)
{
using (var context = new PrincipalContext(ContextType.Domain, "xxx.com"))
{
using (var searcher = new PrincipalSearcher(new ComputerPrincipal(context)))
{
foreach (var result in searcher.FindAll())
{
var auth = result as AuthenticablePrincipal;
if (auth != null)
{
Console.WriteLine("Name: " + auth.Name);
Console.WriteLine("Last Logon Time: " + auth.LastLogon);
if (auth.Name != "Txx-Cxx-Gxx-Pxx")
{
IPAddress[] ipaddress = Dns.GetHostAddresses(auth.Name);
foreach (IPAddress ipaddr in ipaddress)
{
Console.WriteLine(ipaddr);
}
Console.WriteLine();
}
else
{
Console.WriteLine("Error");
}
}
}
}
}
Use UserPrincipalName property of AuthenticablePrincipal class. for example:
var auth = result as AuthenticablePrincipal;
var userName = auth.UserPrincipalName;
See here all properties, methods etc.. that AuthenticablePrincipal has.
To get the Windows logged in User
string m_strUserName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
Please refer other details provided by WindowsIdentity
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 am trying to connect to the online test LDAP server specified here using System.DirectoryServices.AccountManagement like this:
try
{
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "ldap.forumsys.com:389", "dc=example,dc=com", "cn=read-only-admin,dc=example,dc=com", "password"))
{
using (var searcher = new PrincipalSearcher(new UserPrincipal(ctx )))
{
foreach (var result in searcher.FindAll().Take(usersCount))
{
DirectoryEntry de = result.GetUnderlyingObject() as DirectoryEntry;
}
}
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
But it throws the following exception:
Object reference not set to an instance of an object.
Could you please tell what is wrong with my code and how to be able to connect to that LDAP server?
PS: I am able to connect to that server using Apache Directory Studio
Stack Trace :
at System.DirectoryServices.AccountManagement.PrincipalContext.ReadServerConfig(String serverName, ServerProperties& properties)
at System.DirectoryServices.AccountManagement.PrincipalContext.DoServerVerifyAndPropRetrieval()
at System.DirectoryServices.AccountManagement.PrincipalContext..ctor(ContextType contextType, String name, String container, ContextOptions options, String userName, String password)
at System.DirectoryServices.AccountManagement.PrincipalContext..ctor(ContextType contextType, String name, String container, String userName, String password)
at ConsoleApp1.Program.GetGroups(String userName) in C:\Users\Simple Code\source\repos\ConsoleApp1\ConsoleApp1\Program.cs:line 48
As said here, the problem could be that you try to connect to an Apache Directory Studio with the class PrincipalContext that not supports this OpenLDAP,
so one way to go is using the DirectoryEntry class
Using DirectoryEntry it works for me as following:
using (var searcher = new DirectorySearcher(new DirectoryEntry("LDAP://ldap.forumsys.com:389/dc=example,dc=com", "", "", AuthenticationTypes.None)))
{
searcher.Filter = "((objectClass=person))";
searcher.PropertiesToLoad.Add("mail");//email
searcher.PropertiesToLoad.Add("givenName");//first name
searcher.PropertiesToLoad.Add("sn"); //last name
searcher.PropertiesToLoad.Add("telephoneNumber");
searcher.PropertiesToLoad.Add("description");
searcher.PropertiesToLoad.Add("memberOf"); // groups
var activeDirectoryStaffs = searcher.FindAll();
if (activeDirectoryStaffs != null)
{
for (int i = 0; i < activeDirectoryStaffs.Count; i++)
{
SearchResult result = activeDirectoryStaffs[i];
var Email = result.Properties.Contains("mail") ? (string)result.Properties["mail"][0]:null;
var Mobile = result.Properties.Contains("telephoneNumber") ? (string)result.Properties["telephoneNumber"][0] : null;
var FirstName = result.Properties.Contains("givenName") ? (string)result.Properties["givenName"][0] : null;
var LastName = result.Properties.Contains("sn") ? (string)result.Properties["sn"][0] : null;
var Description = result.Properties.Contains("description") ? (string)result.Properties["description"][0] : null;
}
}
}
I am working on a module where I need to fetch members of an Active Directory group. This functionality already exists in the project but it was built for .Net3.5. The same is not working for .Net4.5. After some googling I found that I need to use "Principal Context" object to get the Directory entry object.
The problem here is, I need to do the testing in Test AD, which is different from my production AD.
The old way I used was allowing me to specify the test AD server path,
DirectoryEntry entry = new DirectoryEntry(ADLdapPath, ADAdminUser, ADAdminPassword, AuthenticationTypes.Secure);
Can anyone please help me find a way to specify LDAP path(AD server path) while creating "Principal Context" so that I can do the testing in Test environment.
I've used the following helper (modified) which is part of my AD tool belt to create PrincipalContext for working with AD. This should get you started. Modify it to suit your needs. Hope it helps.
public class ADHelper {
public static PrincipalContext CreatePrincipalContext(string domain = null) {
string container = null;
if (IsNullOrWhiteSpace(domain)) {
domain = GetCurrentDnsSuffix();
if (domain != null && domain.EndsWith(".com", StringComparison.InvariantCultureIgnoreCase)) {
container = GetContainers(domain);
} else {
domain = null;
}
}
var hostName = GetHostName();
if (IsNullOrWhiteSpace(domain)) {
domain = hostName;
}
ContextType contextType;
if (domain.Equals(hostName, StringComparison.InvariantCultureIgnoreCase) &&
domain.Equals(Environment.MachineName, StringComparison.InvariantCultureIgnoreCase)) {
contextType = ContextType.Machine;
} else {
contextType = ContextType.Domain;
}
PrincipalContext principalContext = null;
if (contextType == ContextType.Machine) {
principalContext = new PrincipalContext(contextType, domain);
} else {
principalContext = new PrincipalContext(contextType, domain, container, Constants.LDAPUser, Constants.LDAPPassword);
}
return principalContext;
}
public static string GetCurrentDnsSuffix() {
string dnsHostName = null;
if (NetworkInterface.GetIsNetworkAvailable()) {
var nics = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up);
foreach (var ni in nics) {
var networkConfiguration = ni.GetIPProperties();
var dnsSuffix = networkConfiguration.DnsSuffix;
if (dnsSuffix != null) {
dnsHostName = dnsSuffix;
break;
}
var address = networkConfiguration.DnsAddresses.FirstOrDefault();
if (address != null) {
try {
var dnsHost = Dns.GetHostEntry(address.ToString());
dnsHostName = dnsHost.HostName;
} catch (System.Net.Sockets.SocketException e) {
traceError(e);
} catch (Exception e) {
traceError(e);
}
}
}
}
return dnsHostName;
}
private static string GetContainers(string ADServer) {
string[] LDAPDC = ADServer.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < LDAPDC.GetUpperBound(0) + 1; i++) {
LDAPDC[i] = string.Format("DC={0}", LDAPDC[i]);
}
String ldapdomain = Join(",", LDAPDC);
return ldapdomain;
}
public static string GetHostName() {
var ipProperties = IPGlobalProperties.GetIPGlobalProperties();
return ipProperties.HostName;
}
}
I can then use it in something like this
public static List<string> GetAllUserNames(string domain = null) {
List<string> userNames = new List<string>();
using (var principalContext = createPrincipalContext(domain)) {
//Get a list of user names in MyDomain that match filter
using (UserPrincipal userPrincipal = new UserPrincipal(principalContext)) {
using (PrincipalSearcher principalSearcher = new PrincipalSearcher(userPrincipal)) {
var results = principalSearcher
.FindAll()
.Where(c =>
(c is UserPrincipal) &&
(c as UserPrincipal).Enabled.GetValueOrDefault(false) &&
!string.IsNullOrEmpty(c.DisplayName)
);
foreach (UserPrincipal p in results) {
var temp = p.StructuralObjectClass;
string value = string.Format("{0} ({1})", p.DisplayName, p.EmailAddress ?? Join("\\", p.Context.Name, p.SamAccountName));
userNames.Add(value);
}
}
}
}
return userNames;
}
Basically I found a post that has a solution for a problem we are having in our application and the solution was:
private static void listGroupMembers(string groupDistinguishedName, PrincipalContext ctx, List<UserPrincipal> users)
{
DirectoryEntry group = new DirectoryEntry("LDAP://" + groupDistinguishedName);
foreach (string dn in group.Properties["member"])
{
DirectoryEntry gpMemberEntry = new DirectoryEntry("LDAP://" + dn);
System.DirectoryServices.PropertyCollection userProps = gpMemberEntry.Properties;
object[] objCls = (userProps["objectClass"].Value) as object[];
if (objCls.Contains("group"))
listGroupMembers(userProps["distinguishedName"].Value as string, ctx, users);
if (!objCls.Contains("foreignSecurityPrincipal"))
{
UserPrincipal u = UserPrincipal.FindByIdentity(ctx, IdentityType.DistinguishedName, dn);
if(u!=null) // u==null for any other types except users
users.Add(u);
}
}
}
However I am trying to search a Local group so if I change the directory entry to say:
DirectoryEntry groupEntry =
new DirectoryEntry(string.Format("WinNT://{0}/{1},group", Environment.MachineName, groupName));
Then it doesn't work and it says that the property doesn't exist. How can I do the above but for a local group and user?
Basically to fix this I ended up doing:
protected bool IsUserInLocalGroup(string userName, string group)
{
using (DirectoryEntry computerEntry = new DirectoryEntry("WinNT://{0},computer".FormatWith(Environment.MachineName)))
using(DirectoryEntry groupEntry = computerEntry.Children.Find(group, "Group"))
{
foreach (object o in (IEnumerable)groupEntry.Invoke("Members"))
{
using (DirectoryEntry entry = new DirectoryEntry(o))
{
if (entry.SchemaClassName.Equals("User", StringComparison.OrdinalIgnoreCase) && entry.Name.Equals(userName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
return false;
}
}
How can I get the distinguished name from Active Directory of the currently logged in user in C#?
Check following snippet. You have pass to Identity.Name from IPrincipal. I assume that the user is already authenticated in Active Directory (ie. using standard IIS authorization methods).
private string GetUserName(string identity)
{
if (identity.Contains("\\"))
{
string[] identityList = identity.Split('\\');
return identityList[1];
}
else
{
return identity;
}
}
public string GetUserDn(string identity)
{
var userName = GetUserName(identity);
using (var rootEntry = new DirectoryEntry("LDAP://" + adConfiguration.ServerAddress, null, null, AuthenticationTypes.Secure))
{
using (var directorySearcher = new DirectorySearcher(rootEntry, String.Format("(sAMAccountName={0})", userName)))
{
var searchResult = directorySearcher.FindOne();
if (searchResult != null)
{
using (var userEntry = searchResult.GetDirectoryEntry())
{
return (string)userEntry.Properties["distinguishedName"].Value;
}
}
}
}
return null;
}
Why wouldn't you just use: System.DirectoryServices.AccountManagement.UserPrincipal.Current.DistinguishedName