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;
}
Related
I am trying to build a collection of all local groups and their respected members, however the challenge I am having with the listed code bellow is that "Administrators" group members is empty, all other groups on the server returns their members just not administrators. any ideas?
private void BuildGroupMembership(string remoteHost, string targetdomain, string userName, string password, bool domainOnly)
{
var groupsList = new List<string>();
PrincipalContext pContext = null;
PrincipalContext searchContext = null;
if (string.IsNullOrEmpty(remoteHost))
{
remoteHost = Environment.MachineName;
}
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
{
pContext = new PrincipalContext(ContextType.Machine, remoteHost, null, ContextOptions.Negotiate, userName, password);
searchContext = new PrincipalContext(ContextType.Domain, targetdomain, null, ContextOptions.Negotiate, userName, password);
}
else
{
pContext = new PrincipalContext(ContextType.Machine, remoteHost, null, ContextOptions.Negotiate);
searchContext = new PrincipalContext(ContextType.Domain, targetdomain, null, ContextOptions.Negotiate);
}
try
{
var pSearcher = new PrincipalSearcher(new GroupPrincipal(pContext));
foreach (var principal in pSearcher.FindAll().Where(principal => !groupsList.Contains(principal.Name))) groupsList.Add(principal.Name);
foreach (var group in groupsList)
try
{
var groupItem = new Group {GroupName = group};
Groups.Add(groupItem);
var grp = GroupPrincipal.FindByIdentity(pContext, group);
if (grp != null)
{
var allmembers = grp.GetMembers(false).ToList();
var members = domainOnly ? allmembers.Where(x => x.ContextType == ContextType.Domain).ToList() : allmembers.ToList();
foreach (var p in members)
try
{
var adGroup = GroupPrincipal.FindByIdentity(searchContext, IdentityType.Sid, p.Sid.Value);
if (adGroup != null)
{
groupItem.GroupMembers.Add(new GroupMember
{
MemberDomain = adGroup.DistinguishedName.Substring(adGroup.DistinguishedName.IndexOf("DC="), adGroup.DistinguishedName.Length - adGroup.DistinguishedName.IndexOf("DC=")).Replace("DC=", "").Replace(",", "."),
MemberName = p.SamAccountName,
MemberSID = p.Sid.ToString(),
IsGroup = true
});
continue;
}
var adUser = UserPrincipal.FindByIdentity(searchContext, IdentityType.Sid, p.Sid.ToString());
if (adUser != null)
{
groupItem.GroupMembers.Add(new GroupMember
{
MemberDomain = adUser.DistinguishedName.Substring(adUser.DistinguishedName.IndexOf("DC="), adUser.DistinguishedName.Length - adUser.DistinguishedName.IndexOf("DC=")).Replace("DC=", "").Replace(",", "."),
MemberName = p.SamAccountName,
MemberSID = p.Sid.ToString(),
IsGroup = false
});
}
}
catch
{
// ignored
}
grp.Dispose();
}
}
catch
{
}
pContext.Dispose();
searchContext.Dispose();
}
catch (COMException ex)
{
throw new AuthenticationException(ex.Message);
}
}
This doen't answer completely your question but might help you. Using WMI is so much faster than using PrincipalContext, ... (at least in my case). In my app only Administrators and Users where needed.
static Regex partComponentRegex = new Regex("^[^:]+:Win32_UserAccount.Domain=\"(?<Domain>.+?)\",Name=\"(?<Name>.+?)\"$");
static IEnumerable<User> GetUsersFromSidType(WellKnownSidType wellKnownSidType)
{
string gName = GetGroupName(wellKnownSidType);
using (ManagementObjectSearcher groupSearcher = new ManagementObjectSearcher(
string.Format("SELECT * FROM Win32_GroupUser WHERE GroupComponent =\"Win32_Group.Domain='{0}',Name='{1}'\"",
Environment.MachineName,
gName)))
{
foreach (var group in groupSearcher.Get())
{
Match m = partComponentRegex.Match(group["PartComponent"].ToString());
if (m.Success)
{
using (ManagementObjectSearcher userSearcher = new ManagementObjectSearcher(
string.Format("SELECT * FROM Win32_UserAccount WHERE Name='{0}' AND Domain='{1}'",
m.Groups["Name"], m.Groups["Domain"])))
{
foreach (var user in userSearcher.Get())
{
yield return new User()
{
Disabled = (bool)user["Disabled"],
Domain = user["Domain"].ToString(),
FullName = user["FullName"].ToString(),
Name = user["Name"].ToString(),
SID = user["SID"].ToString()
};
}
}
}
}
}
}
static string GetGroupName(WellKnownSidType wellKnownSidType)
{
SecurityIdentifier sid = new SecurityIdentifier(wellKnownSidType, null);
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(
string.Format("SELECT * FROM Win32_Group WHERE SID='{0}'",
sid.Value)))
{
var e = searcher.Get().GetEnumerator();
if (e.MoveNext())
return e.Current["Name"].ToString();
return null;
}
}
First of all, please forgive me if I'm not using the correct terminologies. Correct me wherever I'm using the wrong terminology.
The objective is to programmatically retrieve the lastLogon date of a given username.
We have what I believe is a forest; two AD servers like - adserver01.aa.mycompany.com and adserver02.aa.mycompany.com
I connected to these servers from a third machine using Microsoft's ADExplorer to inspect the objects. There I see some users having lastLogon date available in adserver01, but not in adserver02. For example, the value for lastLogon is 0x0 in adserver02 whereas it is a valid date in adserver01 for some users.
The code I've developed so far as a Windows Forms application, works fine if only one AD Server is involved. How do I check both servers and return the non-zero value if available, in either for lastLogon date attribute?
private static string GetLastActivityDate(string UserName)
{
string domainAndUserName = String.Format(#"LDAP://aa.mycompany.com/CN={0},OU=CLIENT_PROD,OU=clients.mycompany.com,DC=aa,DC=mycompany,DC=com", UserName);
string OUAdminUserName = "abc";
string OUAdminPassword = "xyz";
AuthenticationTypes at = AuthenticationTypes.Secure;
DateTime lastActivityDate;
string returnvalue;
long lastLogonDateAsLong;
using (DirectoryEntry entryUser = new DirectoryEntry(domainAndUserName, OUAdminUserName, OUAdminPassword, at))
using (DirectorySearcher mysearcher = new DirectorySearcher(entryUser))
try
{
using (SearchResultCollection results = mysearcher.FindAll())
{
if (results.Count >= 1)
{
DirectoryEntry de = results[0].GetDirectoryEntry();
lastLogonDateAsLong = GetInt64(de, "lastLogon");
try
{
if (lastLogonDateAsLong != -1)
{
lastActivityDate = DateTime.FromFileTime(lastLogonDateAsLong);
returnvalue = lastActivityDate.ToString();
}
else
{
returnvalue = "-Not available-";
}
}
catch (System.ArgumentOutOfRangeException aore)
{
returnvalue = "Not available";
}
}
else
{
returnvalue = string.Empty;
}
}
}
catch (System.DirectoryServices.DirectoryServicesCOMException dsce)
{
returnvalue = "- Not available -";
}
return returnvalue;
}
Thank you.
EDIT:
private static Int64 GetInt64(DirectoryEntry entry, string attr)
{
DirectorySearcher ds = new DirectorySearcher(
entry,
String.Format("({0}=*)", attr),
new string[] { attr },
SearchScope.Base
);
SearchResult sr = ds.FindOne();
if (sr != null)
{
if (sr.Properties.Contains(attr))
{
return (Int64)sr.Properties[attr][0];
}
}
return -1;
}
Forgot to mention, the AD schema, structure etc, looks exactly alike in the two servers.
Check this post
http://www.codeproject.com/Articles/19181/Find-LastLogon-Across-All-Windows-Domain-Controlle
I had the same issue but but only for one domain,
I solved it by using the following code however i'm checking the lastLogin of all users
public static Dictionary<string, DateTime> UsersLastLogOnDate()
{
var lastLogins = new Dictionary<string, DateTime>();
DomainControllerCollection domains = Domain.GetCurrentDomain().DomainControllers;
foreach (DomainController controller in domains)
{
try
{
using (var directoryEntry = new DirectoryEntry(string.Format("LDAP://{0}", controller.Name)))
{
using (var searcher = new DirectorySearcher(directoryEntry))
{
searcher.PageSize = 1000;
searcher.Filter = "(&(objectClass=user)(!objectClass=computer))";
searcher.PropertiesToLoad.AddRange(new[] { "distinguishedName", "lastLogon" });
foreach (SearchResult searchResult in searcher.FindAll())
{
if (searchResult.Properties.Contains("lastLogon"))
{
var lastLogOn = DateTime.FromFileTime((long)searchResult.Properties["lastLogon"][0]);
var username = Parser.ParseLdapAttrValue(searchResult.Properties["distinguishedName"][0].ToString());
if (lastLogins.ContainsKey(username))
{
if (DateTime.Compare(lastLogOn, lastLogins[username]) > 0)
{
lastLogins[username] = lastLogOn;
}
}
else
{
lastLogins.Add(username, lastLogOn);
}
}
}
}
}
}
catch (System.Runtime.InteropServices.COMException comException)
{
// Domain controller is down or not responding
Log.DebugFormat("Domain controller {0} is not responding.",controller.Name);
Log.Error("Error in one of the domain controllers.", comException);
continue;
}
}
return lastLogins;
}
On top of the code you can use the following to get all domains in a forest.
Forest currentForest = Forest.GetCurrentForest();
DomainCollection domains = currentForest.Domains;
foreach(Domain domain in domains)
{
// check code above
}
There may be a simpler approach? There is actually another attribute lastLogonTimestamp, added with 2003 domain level I think, that tries to keep a single, consistent value across the domain for the last login. Alas, it has a bizarre replication time pattern, and could be up to two weeks out of date.
I want to Add remove a user from a group in System.DirectoryServices.Protocol namespace.
I have the samples mentioned here :
link to my other question
But can't find an exampel fo how to add and remove a user from a group using S.DS.P.
Does anyone know of any samples for this operation?
Thanks,
Cal-
You'd just want to send a ModifyRequest on the member attribute with a DirectoryAttributeOperation.Add. The value you should pass is the DN of the user you want to add to the group
Here a example to add a object to a group.
public static void AddObjectToGroup(string objectName, string groupName)
{
// var credential = new NetworkCredential();
// var identifier = new LdapDirectoryIdentifier("");
using (var connection = new LdapConnection("company.com"))
{
connection.SessionOptions.StartTransportLayerSecurity(null);
connection.Bind();
string objectDn = null;
var request = new SearchRequest("DC=company,DC=com", $"Name={objectName}", SearchScope.Subtree, "distinguishedName");
var response = (SearchResponse)connection.SendRequest(request);
if (response != null)
{
var enumerator = response.Entries.GetEnumerator();
enumerator.Reset();
if (enumerator.MoveNext() && enumerator.Current is SearchResultEntry entry)
{
objectDn = entry.Attributes["distinguishedName"].GetValues(typeof(string)).Select(x => (string)x).FirstOrDefault();
}
}
Log.Information($"object DN: {objectDn}");
string groupDn = null;
request = new SearchRequest("DC=company,DC=com", $"Name={groupName}", SearchScope.Subtree, "distinguishedName");
response = (SearchResponse)connection.SendRequest(request);
if (response != null)
{
var enumerator = response.Entries.GetEnumerator();
enumerator.Reset();
if (enumerator.MoveNext() && enumerator.Current is SearchResultEntry entry)
{
groupDn = entry.Attributes["distinguishedName"].GetValues(typeof(string)).Select(x => (string)x).FirstOrDefault();
}
}
Log.Information($"group DN: {groupDn}");
request = new SearchRequest("DC=company,DC=com", $"(&(objectCategory=Group)(distinguishedName={groupDn}))", SearchScope.Subtree);
response = (SearchResponse)connection.SendRequest(request);
if (response != null && !string.IsNullOrWhiteSpace(objectDn))
{
var members = response.Entries[0].Attributes["member"];
var existing = new List<string>();
for (int i = 0; i < members.Count; i++)
{
existing.Add(members[i].ToString());
}
if (existing.Contains(objectDn)) return;
var dam = new DirectoryAttributeModification { Name = "member" };
dam.Add(objectDn);
dam.Operation = DirectoryAttributeOperation.Add;
var mr = new ModifyRequest(groupDn, dam);
var dr = connection.SendRequest(mr);
Log.Information($"Add Response: {dr.ResultCode.ToString()}");
}
}
}
Just for those who follow, here is how I actually solved the problem using System.DirectoryServices.AccountManagement
string adServer = "";
string adServerUser = "";
string adServerPassword = "";
string adServerContainer = "";
GetSettings( ref adServer, ref adServerUser, ref adServerPassword, ref adServerContainer );
if ( ((!string.IsNullOrEmpty(adServer) && !string.IsNullOrEmpty(adServerUser)) && !string.IsNullOrEmpty(adServerPassword)) && !string.IsNullOrEmpty(adServerContainer))
{
try
{
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, adServer, adServerContainer, adServerUser, adServerPassword))
{
using (GroupPrincipal group = GroupPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, this.textBox_GroupAdd.Text))
{
if (group == null)
{
FlexibleMessageBox.Show("group could not be found");
return;
}
PrincipalSearchResult<Principal> x = group.GetMembers();
using (UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, this.textBox_adName.Text))
{
string userSid = string.Format("<SID={0}>", ToSidString(user));
DirectoryEntry groupDirectoryEntry = (DirectoryEntry) group.GetUnderlyingObject();
groupDirectoryEntry.Properties["member"].Add(userSid);
groupDirectoryEntry.CommitChanges();
}
}
}
}
catch (Exception ex)
{
FlexibleMessageBox.Show(ex.ToString());
}
FlexibleMessageBox.Show("group add done");
}
and here is the guts of the remove from group
using (UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, this.textBox_adName.Text))
{
string userSid = string.Format("<SID={0}>", ToSidString(user));
DirectoryEntry groupDirectoryEntry = (DirectoryEntry) group.GetUnderlyingObject();
groupDirectoryEntry.Properties["member"].Remove(userSid);
groupDirectoryEntry.CommitChanges();
}
Given a group like this in Active Directory:
MainGroup
GroupA
User1
User2
GroupB
User3
User4
I can easily determine if User3 is member of MainGroup or any of its subgroups with code like this:
using System;
using System.DirectoryServices;
static class Program {
static void Main() {
DirectoryEntry user = new DirectoryEntry("LDAP://CN=User3,DC=X,DC=y");
string filter = "(memberOf:1.2.840.113556.1.4.1941:=CN=MainGroup,DC=X,DC=y)";
DirectorySearcher searcher = new DirectorySearcher(user, filter);
searcher.SearchScope = SearchScope.Subtree;
var r = searcher.FindOne();
bool isMember = (r != null);
}
}
I would like to know if there is a similar way to get all the users that are member of a group or any of its subgroups, i.e. in the example for MainGroup get User1, User2, User3 and User4.
The obvious way of getting all the users is to recursively query each subgroup, but I was wondering if there is an easier way to do it.
Using the same approach with the memberOf:1.2.840.113556.1.4.1941: filter, but using the domain root instead of the user as a search base is not feasible, as the query takes too long (probably it computes all the group memberships recursively for all users in the domain and checks if they are member of the given group).
Which is the best way to get all members of a group, including its subgroups?
Just in case this might benefit someone else: here is the solution I ended up with. It is just a recursive search, with some extra checks to avoid checking the same group or user twice, e.g. if groupA is member of groupB and groupB is member of groupA or a user is member of more than one group.
using System;
using System.DirectoryServices;
using System.Collections.Generic;
static class Program {
static IEnumerable<SearchResult> GetMembers(DirectoryEntry searchRoot, string groupDn, string objectClass) {
using (DirectorySearcher searcher = new DirectorySearcher(searchRoot)) {
searcher.Filter = "(&(objectClass=" + objectClass + ")(memberOf=" + groupDn + "))";
searcher.PropertiesToLoad.Clear();
searcher.PropertiesToLoad.AddRange(new string[] {
"objectGUID",
"sAMAccountName",
"distinguishedName"});
searcher.Sort = new SortOption("sAMAccountName", SortDirection.Ascending);
searcher.PageSize = 1000;
searcher.SizeLimit = 0;
foreach (SearchResult result in searcher.FindAll()) {
yield return result;
}
}
}
static IEnumerable<SearchResult> GetUsersRecursively(DirectoryEntry searchRoot, string groupDn) {
List<string> searchedGroups = new List<string>();
List<string> searchedUsers = new List<string>();
return GetUsersRecursively(searchRoot, groupDn, searchedGroups, searchedUsers);
}
static IEnumerable<SearchResult> GetUsersRecursively(
DirectoryEntry searchRoot,
string groupDn,
List<string> searchedGroups,
List<string> searchedUsers) {
foreach (var subGroup in GetMembers(searchRoot, groupDn, "group")) {
string subGroupName = ((string)subGroup.Properties["sAMAccountName"][0]).ToUpperInvariant();
if (searchedGroups.Contains(subGroupName)) {
continue;
}
searchedGroups.Add(subGroupName);
string subGroupDn = ((string)subGroup.Properties["distinguishedName"][0]);
foreach (var user in GetUsersRecursively(searchRoot, subGroupDn, searchedGroups, searchedUsers)) {
yield return user;
}
}
foreach (var user in GetMembers(searchRoot, groupDn, "user")) {
string userName = ((string)user.Properties["sAMAccountName"][0]).ToUpperInvariant();
if (searchedUsers.Contains(userName)) {
continue;
}
searchedUsers.Add(userName);
yield return user;
}
}
static void Main(string[] args) {
using (DirectoryEntry searchRoot = new DirectoryEntry("LDAP://DC=x,DC=y")) {
foreach (var user in GetUsersRecursively(searchRoot, "CN=MainGroup,DC=x,DC=y")) {
Console.WriteLine((string)user.Properties["sAMAccountName"][0]);
}
}
}
}
static List<SearchResult> ad_find_all_members(string a_sSearchRoot, string a_sGroupDN, string[] a_asPropsToLoad)
{
using (DirectoryEntry de = new DirectoryEntry(a_sSearchRoot))
return ad_find_all_members(de, a_sGroupDN, a_asPropsToLoad);
}
static List<SearchResult> ad_find_all_members(DirectoryEntry a_SearchRoot, string a_sGroupDN, string[] a_asPropsToLoad)
{
string sDN = "distinguishedName";
string sOC = "objectClass";
string sOC_GROUP = "group";
string[] asPropsToLoad = a_asPropsToLoad;
Array.Sort<string>(asPropsToLoad);
if (Array.BinarySearch<string>(asPropsToLoad, sDN) < 0)
{
Array.Resize<string>(ref asPropsToLoad, asPropsToLoad.Length+1);
asPropsToLoad[asPropsToLoad.Length-1] = sDN;
}
if (Array.BinarySearch<string>(asPropsToLoad, sOC) < 0)
{
Array.Resize<string>(ref asPropsToLoad, asPropsToLoad.Length+1);
asPropsToLoad[asPropsToLoad.Length-1] = sOC;
}
List<SearchResult> lsr = new List<SearchResult>();
using (DirectorySearcher ds = new DirectorySearcher(a_SearchRoot))
{
ds.Filter = "(&(|(objectClass=group)(objectClass=user))(memberOf=" + a_sGroupDN + "))";
//ds.PropertiesToLoad.Clear();
ds.PropertiesToLoad.AddRange(asPropsToLoad);
//ds.PageSize = 1000;
//ds.SizeLimit = 0;
foreach (SearchResult sr in ds.FindAll())
lsr.Add(sr);
}
for(int i=0;i<lsr.Count;i++)
if (lsr[i].Properties.Contains(sOC) && lsr[i].Properties[sOC].Contains(sOC_GROUP))
lsr.AddRange(ad_find_all_members(a_SearchRoot, (string)lsr[i].Properties[sDN][0], asPropsToLoad));
return lsr;
}
static void Main(string[] args)
{
foreach (var sr in ad_find_all_members("LDAP://DC=your-domain,DC=com", "CN=your-group-name,OU=your-group-ou,DC=your-domain,DC=com", new string[] { "sAMAccountName" }))
Console.WriteLine((string)sr.Properties["distinguishedName"][0] + " : " + (string)sr.Properties["sAMAccountName"][0]);
}
To get the members recursively take this (the trick is GetMembers(true) instead of false which is the default value):
private List<string> GetGroupMembers(string groupName)
{
var members = new List<string>();
try
{
using (var pc = new PrincipalContext(ContextType.Domain, Common.THE_DOMAIN))
{
var gp = GroupPrincipal.FindByIdentity(pc, groupName);
if (gp == null) return members;
foreach (Principal p in gp.GetMembers(true))
members.Add(p.Name);
members.Sort();
}
}
catch (Exception)
{
return new List<string>();
}
return members;
}
I also wanted to know if a user or a computer is in an ActiveDirectory group. And this worked for me also with nested groups (it is part of a WebService but you can use the code also in a standalone application):
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
[HttpGet]
[Route("IsUserInGroup")]
public HttpResponseMessage IsUserInGroup(string userName, string groupName)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);
try
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, Common.THE_DOMAIN))
{
var gp = GroupPrincipal.FindByIdentity(pc, groupName);
var up = UserPrincipal.FindByIdentity(pc, userName);
if (gp == null)
{
response.Content = Common.ConvertToJsonContent($"Group '{groupName}' not found in Active Directory");
response.StatusCode = HttpStatusCode.NotFound;
return response;
}
if (up == null)
{
response.Content = Common.ConvertToJsonContent($"User '{userName}' not found in Active Directory");
response.StatusCode = HttpStatusCode.NotFound;
return response;
}
DirectoryEntry user = new DirectoryEntry($"LDAP://{up.DistinguishedName}");
DirectorySearcher mySearcher = new DirectorySearcher(user)
{
SearchScope = System.DirectoryServices.SearchScope.Subtree,
Filter = $"(memberOf:1.2.840.113556.1.4.1941:={gp.DistinguishedName})"
};
SearchResult result = mySearcher.FindOne();
response.StatusCode = HttpStatusCode.OK;
response.Content = Common.ConvertToJsonContent(result != null);
}
}
catch (Exception ex)
{
response.StatusCode = HttpStatusCode.BadRequest;
response.Content = Common.ConvertToJsonContent($"{MethodBase.GetCurrentMethod().Name}: {ex.Message}");
}
return response;
}
[HttpGet]
[Route("IsComputerInGroup")]
public HttpResponseMessage IsComputerInGroup(string computerName, string groupName)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);
try
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, Common.THE_DOMAIN))
{
var gp = GroupPrincipal.FindByIdentity(pc, groupName);
var cp = ComputerPrincipal.FindByIdentity(pc, computerName);
if (gp == null)
{
response.Content = Common.ConvertToJsonContent($"Group '{groupName}' not found in Active Directory");
response.StatusCode = HttpStatusCode.NotFound;
return response;
}
if (cp == null)
{
response.Content = Common.ConvertToJsonContent($"Computer '{computerName}' not found in Active Directory");
response.StatusCode = HttpStatusCode.NotFound;
return response;
}
DirectoryEntry computer = new DirectoryEntry($"LDAP://{cp.DistinguishedName}");
DirectorySearcher mySearcher = new DirectorySearcher(computer)
{
SearchScope = System.DirectoryServices.SearchScope.Subtree,
Filter = $"(memberOf:1.2.840.113556.1.4.1941:={gp.DistinguishedName})"
};
SearchResult result = mySearcher.FindOne();
response.StatusCode = HttpStatusCode.OK;
response.Content = Common.ConvertToJsonContent(result != null);
}
}
catch (Exception ex)
{
response.StatusCode = HttpStatusCode.BadRequest;
response.Content = Common.ConvertToJsonContent($"{MethodBase.GetCurrentMethod().Name}: {ex.Message}");
}
return response;
}
I am writing an installer class for my web service. In many cases when I use WMI (e.g. when creating virtual directories) I have to know the siteId to provide the correct metabasePath to the site, e.g.:
metabasePath is of the form "IIS://<servername>/<service>/<siteID>/Root[/<vdir>]"
for example "IIS://localhost/W3SVC/1/Root"
How can I look it up programmatically in C#, based on the name of the site (e.g. for "Default Web Site")?
Here is how to get it by name. You can modify as needed.
public int GetWebSiteId(string serverName, string websiteName)
{
int result = -1;
DirectoryEntry w3svc = new DirectoryEntry(
string.Format("IIS://{0}/w3svc", serverName));
foreach (DirectoryEntry site in w3svc.Children)
{
if (site.Properties["ServerComment"] != null)
{
if (site.Properties["ServerComment"].Value != null)
{
if (string.Compare(site.Properties["ServerComment"].Value.ToString(),
websiteName, false) == 0)
{
result = int.Parse(site.Name);
break;
}
}
}
}
return result;
}
You can search for a site by inspecting the ServerComment property belonging to children of the metabase path IIS://Localhost/W3SVC that have a SchemaClassName of IIsWebServer.
The following code demonstrates two approaches:
string siteToFind = "Default Web Site";
// The Linq way
using (DirectoryEntry w3svc1 = new DirectoryEntry("IIS://Localhost/W3SVC"))
{
IEnumerable<DirectoryEntry> children =
w3svc1.Children.Cast<DirectoryEntry>();
var sites =
(from de in children
where
de.SchemaClassName == "IIsWebServer" &&
de.Properties["ServerComment"].Value.ToString() == siteToFind
select de).ToList();
if(sites.Count() > 0)
{
// Found matches...assuming ServerComment is unique:
Console.WriteLine(sites[0].Name);
}
}
// The old way
using (DirectoryEntry w3svc2 = new DirectoryEntry("IIS://Localhost/W3SVC"))
{
foreach (DirectoryEntry de in w3svc2.Children)
{
if (de.SchemaClassName == "IIsWebServer" &&
de.Properties["ServerComment"].Value.ToString() == siteToFind)
{
// Found match
Console.WriteLine(de.Name);
}
}
}
This assumes that the ServerComment property has been used (IIS MMC forces its used) and is unique.
public static ManagementObject GetWebServerSettingsByServerComment(string serverComment)
{
ManagementObject returnValue = null;
ManagementScope iisScope = new ManagementScope(#"\\localhost\root\MicrosoftIISv2", new ConnectionOptions());
iisScope.Connect();
if (iisScope.IsConnected)
{
ObjectQuery settingQuery = new ObjectQuery(String.Format(
"Select * from IIsWebServerSetting where ServerComment = '{0}'", serverComment));
ManagementObjectSearcher searcher = new ManagementObjectSearcher(iisScope, settingQuery);
ManagementObjectCollection results = searcher.Get();
if (results.Count > 0)
{
foreach (ManagementObject manObj in results)
{
returnValue = manObj;
if (returnValue != null)
{
break;
}
}
}
}
return returnValue;
}
private static string FindWebSiteByName(string serverName, string webSiteName)
{
DirectoryEntry w3svc = new DirectoryEntry("IIS://" + serverName + "/W3SVC");
foreach (DirectoryEntry site in w3svc.Children)
{
if (site.SchemaClassName == "IIsWebServer"
&& site.Properties["ServerComment"] != null
&& site.Properties["ServerComment"].Value != null
&& string.Equals(webSiteName, site.Properties["ServerComment"].Value.ToString(), StringComparison.OrdinalIgnoreCase))
{
return site.Name;
}
}
return null;
}
Maybe not the best way, but here is a way :
loop through all the sites under "IIS://servername/service"
for each of the sites check if the name is "Default Web Site" in your case
if true then you have your site id
Example :
Dim oSite As IADsContainer
Dim oService As IADsContainer
Set oService = GetObject("IIS://localhost/W3SVC")
For Each oSite In oService
If IsNumeric(oSite.Name) Then
If oSite.ServerComment = "Default Web Site" Then
Debug.Print "Your id = " & oSite.Name
End If
End If
Next