现在的位置: 首页 > 综合 > 正文

【转】AD完美信息抓取

2013年01月21日 ⁄ 综合 ⁄ 共 12955字 ⁄ 字号 评论关闭
文章目录

Getting Started

To start querying Active Directory from your C# code, you simply add a reference to the System.DirectoryServices.dll in your project and the following using statement to your code:

Collapse
using System.DirectoryServices;

According to examples provided in the Visual Studio.NET Help, Microsoft recommends using two main classes from the System.DirectoryServices namespace: DirectoryEntry and DirectorySearcher. In most cases, it is enough to use DirectorySearcher class only. Because I am going to talk about querying AD only, not managing AD, I won�t provide detailed explanation of using DirectoryEntry class. But later on, I will show and comment an example of DirectoryEntry usage. In addition, I assume that Active Directory is always up and running in your environment. One more thing I want to mention before we start is that I will separate all AD queries by two large groups: user-oriented queries and common queries. The difference between these types of queries we�ll figure out later. Let�s get started.

User Oriented Queries

User Name

First of all, I have to tell that a user-oriented query always uses a user name as a parameter. Let�s figure out what the user name is. This is the name part of a login name, which users enter while logging in Windows. This name part follows the �\� symbol that separates the domain name and the user name. It�s a good practice to extract the user name from the login name before using it in the AD query. We can do it easily:

Collapse
string ExtractUserName(string path)
{
string [] userPath = path.Split(new char [] {'\\'});
return userPath[userPath.Length-1];
}

First Query

Now we know the user name, and it�s good to know if this user exists in the AD. Let�s do it like:

Collapse
bool IsExistInAD(string loginName)
{
string userName = ExtractUserName(loginName);
DirectorySearcher search = new DirectorySearcher();
search.Filter = String.Format("(SAMAccountName={0})", userName);
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();

if (result == null)
{
return false;
}
else
{
return true;
}
}

This is our first AD query, so let�s see what we are doing here. First of all, I extract the user name from the login name that we received from Windows, using the ExtractUserName method. Then I create a new DirectorySearcher object. DirectorySearcher class provides a lot of constructors, but I use a default constructor on purpose in order to show how we will initialize the DirectorySearcher before starting a query.

Actually, the query itself is located in the Filter property of the DirectorySearcher object. I have to initialize it before starting the query. Here we come to the Active Directory territory. To create a Filter string, you have to be familiar with the AD object structure. But in this article, I won�t talk about AD itself, so simply take a look at the code above and learn that the filter keyword "SAMAccountName" allows us to select an AD object with the given name equal to the login name.

Now I have to assign a PropertiesToLoad property of the DirectorySearcher object. This is a collection containing attribute names of AD objects that we want the query to return. By analogy with SQL query, the Filter property serves as the WHERE clause and the PropertiesToLoad property works as a list of column names that the query will return. The "cn" keyword means a common name of the AD object, and in this query, we are not interested in any other properties to return. Basically, this is a good practice to limit the amount of returning properties as much as you can. It can reduce execution time of the query significantly.

After all, we are ready to run a query. I simply call a FindOne() method of the DirectorySearcher object that returns a special SearchResult object containing a searching result. If the result object is null, that means there is no information in the AD corresponding to our query. In that particular case, it means a user with the given name is not found. Otherwise, we assume that the user exists in the AD. In order to make sure that the user does exist, we can check out the result object. As we remember, the result object should contain the "cn" property. We can test it out like:

Collapse
 lang=csstring cn = (string)result.Properties["cn"][0];

The result object contains a special property named Properties that returns a typed collection containing the values of properties of the object found in the AD. We can also use this method for retrieving additional information about the user from AD. To do this, we only need to add additional PropertiesToLoad arguments before searching and retrieving them after searching. The following code shows that:

Collapse
    search.PropertiesToLoad.Add("samaccountname");
search.PropertiesToLoad.Add("givenname");
search.PropertiesToLoad.Add("sn");
SearchResult result = search.FindOne();
...
string samaccountname = (string)result.Properties[�samaccountname�][0];
string givenname = (string)result.Properties[�givenname�][0];
string surname = (string)result.Properties[�sn�][0];

The example above contains the names of the most widely used properties. You can for sure retrieve any possible property from AD simply using its name.

That�s it actually. Now I am going to show you several more queries, but you already know how to query AD.

User�s Groups

Let�s find out what groups our user belongs to. See the code:

Collapse
string GetADUserGroups(string userName) {
DirectorySearcher search = new DirectorySearcher();
search.Filter = String.Format("(cn={0})", userName);
search.PropertiesToLoad.Add("memberOf");
StringBuilder groupsList = new StringBuilder();

SearchResult result = search.FindOne();
if (result != null)
{
int groupCount = result.Properties["memberOf"].Count;

for(int counter = 0; counter < groupCount; counter++)
{
groupsList.Append((string)result.Properties["memberOf"][counter]);
groupsList.Append("|");
}
}
groupsList.Length -= 1; //remove the last '|' symbol

return groupsList.ToString();
}

We can notice that the Filter property and PropertiesToLoad property are what makes the difference from the previous query. And also in this query, I assume there are several outputs in the result object. This method returns a list of the AD groups the user belongs to as a string containing '|'-symbol separated group names. Actually, group names in that list appear in a relatively unusual format, and you can examine this format by yourself.

Common Queries

Common queries allow us to retrieve much more information from AD. This type of queries are potentially dangerous because of potentially huge amount of information that might be received back from AD. So you have to be careful while experimenting with such queries.

Group�s Users

Let�s get a list of users belonging to a particular AD group. The code below shows how to do this:

Collapse
ArrayList GetADGroupUsers(string groupName)
{ SearchResult result;
DirectorySearcher search = new DirectorySearcher();
search.Filter = String.Format("(cn={0})", groupName);
search.PropertiesToLoad.Add("member");
result = search.FindOne();

ArrayList userNames = new ArrayList();
if (result != null)
{
for (int counter = 0; counter <
result.Properties["member"].Count; counter++)
{
string user = (string)result.Properties["member"][counter];
userNames.Add(user);
}
}
return userNames;
}

Here I use the same search filter as for user search, but different set of PropertiesToLoad. So the query returns to us an object with the common name equal to the group name and a properties list containing a list of other objects that are a "member" of the group object obtained. Then I iterate through the list of the "member" objects and add them to the ArrayList being returned. I encourage you to investigate a format of the ArrayList items in order to figure out what actually you have back from AD. You�ll see it�s not a simple user name.

All Users

The last query I want to discuss in this article is meant to return a list of all the AD domain users. This is an extremely risky operation because of a large amount of users in the AD domain and possible incorrect implementation of AD. I�d like to explain the second sentence. This query uses a special search filter that looks for every object with a specific property that should mean this object is a user object. But sometimes administrators design AD structure even without this property or enter new records to AD not setting this property. So it�s very possible to get a list of all objects in AD instead of domain user objects only. But it�s enough of bad things, let�s get to the code.

Collapse
ArrayList GetAllADDomainUsers(string domainpath)
{
ArrayList allUsers = new ArrayList();

DirectoryEntry searchRoot = new DirectoryEntry(domainpath);
DirectorySearcher search = new DirectorySearcher(searchRoot);
search.Filter = "(&(objectClass=user)(objectCategory=person))";
search.PropertiesToLoad.Add("samaccountname");

SearchResult result;
SearchResultCollection resultCol = search.FindAll();
if (resultCol != null)
{
for(int counter=0; counter < resultCol.Count; counter++)
{
result = resultCol[counter];
if (result.Properties.Contains("samaccountname"))
{
allUsers.Add((String)result.Properties["samaccountname"][0]);
}
}
}
return allUsers;
}

If we go through the code above, we can see some differences from the previous examples. First, I use a DirectoryEntry object. I do it to connect to a specific context of AD and limit my search by this context. In my case, I suppose to use a single domain context and thus I have to provide a path to the domain I�m interested in. I have to use a specific form of this path in terms of LDAP language. It could look like the following:

Collapse
"LDAP://DC=<domain>"

Here <domain> is the name of a particular domain. Another example is using a current domain. To find out the name of the current domain, I can get it from the user login name (remember ExtractUserName method?), or I can determine it programmatically like the following:

Collapse
DirectoryEntry entryRoot = new DirectoryEntry("LDAP://RootDSE");
string domain = entryRoot.Properties["defaultNamingContext"][0];
DirectoryEntry entryDomain = new DirectoryEntry("LDAP://" + domain);

This is an example of using a DirectoryEntry class.

Well, let�s get back to the code. The next thing you may want to pay attention is a Filter string. It contains no parameters, but instead it consists of an AD specific pattern. (If you have no idea what that pattern means, probably it�s a good idea to get familiar with AD some day ;-)) And the last difference is the use of FindAll() method instead of the FindOne().

Best Practice

In the examples above, I was using a simplified code for demonstration purpose only. But the real code could be much more complex. Especially in the part of PropertiesToLoad and parsing of search outcome. To help you work with AD, I�d like to share some useful tips that have been practically proven.

  1. Always limit the amount of information returned by DirectorySearcher object by setting its public property PropertiesToLoad.
  2. Querying Active Directory is a very time-consuming process. Besides, information in AD always doesn�t change frequently. So the good practice is to cache information received from AD as much as possible.
  3. Results returned by DirectorySearcher object depend on the AD implementation very much. So even if you expect some object properties to be returned, always check them for null before using.
  4. Querying AD is a network operation. That means you never know if there are some problems with network unless they happened. As a developer, you should assume the worst scenario and always place your AD-querying code in the try-catch block.
  5. And the last one. Usually, you don�t know how much information AD contains. So keep off using very common queries that potentially can return a very large and even huge amount of information as a result. There are two main risks in such queries: first, the query can last for a very long time like tens of minutes and even hours; and second, the size of the result returned can exceed the memory size and crush your program.

启用账号AD

 

 

 

private void EnableUser(DirectoryEntry  de)

int val = (int)de.Properties["userAccountControl"].Value;
                de.Properties["userAccountControl"].Value = val & ~Convert.ToInt32( ADHelper.ADS_USER_FLAG_ENUM.ADS_UF_ACCOUNTDISABLE );

                                de.CommitChanges();
de.Close;
}

 

 

 

http://msdn.microsoft.com/en-us/library/ms180833(vs.80).aspx

http://www.codeproject.com/KB/system/everythingInAD.aspx#11

 

 

 

用户帐户属性 
字符名“常规”标签
说明
姓 Sn
名 Givename
英文缩写 Initials
显示名称 displayName
描述 Description
办公室 physicalDeliveryOfficeName 
电话号码 telephoneNumber
电话号码:其它 otherTelephone 多个以英文分号分隔
电子邮件 Mail
网页 wWWHomePage
网页:其它 url 多个以英文分号分隔
“地址”标签
国家/地区 C 如:中国CN,英国GB
省/自治区 St 
市/县 L
街道 streetAddress
邮政信箱 postOfficeBox
邮政编码 postalCode
 
“帐户”标签
用户登录名 userPrincipalName 形如:S1@mcse.com
用户登录名(以前版本) sAMAccountName 形如:S1
登录时间 logonHours 
登录到 userWorkstations 多个以英文逗号分隔
用户帐户控制 userAccountControl (启用:512,禁用:514, 密码永不过期:66048)
帐户过期 accountExpires
 
“配置文件”标签
配置文件路径 profilePath
登录脚本 scriptPath
主文件夹:本地路径 homeDirectory
连接 homeDrive
到 homeDirectory
“电话”标签
家庭电话 homePhone (若是其它,在前面加other。)
寻呼机 Pager 如:otherhomePhone。
移动电话 mobile 若多个以英文分号分隔。
传真 FacsimileTelephoneNumber 
IP电话 ipPhone
注释 Info

“单位”标签
职务 Title
部门 Department
公司 Company

“隶属于”标签
隶属于  memberOf  用户组的DN不需使用引号, 多个用分号分隔 
“拨入”标签 远程访问权限(拨入或VPN) msNPAllowDialin
允许访问 值:TRUE
拒绝访问 值:FALSE
回拨选项 msRADIUSServiceType
由呼叫方设置或回拨到 值:4
总是回拨到 msRADIUSCallbackNumber 

没有找到我需要的 万般无奈之际,只能自己想办法通过代码来获取想要的信息

研究了下其实也非常简单

 

 string text = string.Empty;
                DirectoryEntry entry = new DirectoryEntry(ConfigurationManager.AppSettings["ldap"], ConfigurationManager.AppSettings["adminname"], ConfigurationManager.AppSettings["password"]);
                //获取子结点
                System.DirectoryServices.DirectoryEntry subentry = entry.Children.Find(ouname, "organizationalUnit");

                //对子对象进行循环
                foreach (DirectoryEntry res in subentry.Children)
                {
                    if (res.Name.ToString().Substring(0, 3) == "CN=" && res.SchemaClassName == "user") //user
                    {
                        foreach (object p in res.Properties)
                        {
                            Response.Write(((System.DirectoryServices.PropertyValueCollection)(p)).PropertyName.ToString() + "=========\n" + ((System.DirectoryServices.PropertyValueCollection)(p)).Value);
                        }
                    }

                    Response.Write("---------------------------------------------------------");
                    if (res.Name.Substring(0, 3) == "OU=")
                    {
                        foreach (object p in res.Properties)
                        {
                            Response.Write(((System.DirectoryServices.PropertyValueCollection)(p)).PropertyName.ToString() + "==============\n"+((System.DirectoryServices.PropertyValueCollection)(p)).Value);
                        }
                    }

 

可以得到非常全面的AD属性

 

 

 

【上篇】
【下篇】

抱歉!评论已关闭.