2015年10月2日 星期五

[C#][Active Directory] 查詢LDAP Path及相關屬性

 Ative Directory


背景

最近在寫和Active Directory相關的程式,感覺對於操控LDAP結構的程式碼如果沒有分層,真的是很難閱讀啊! 所以整理一下在後端上的寫法。


環境

l   Windows 7 Enterprise
l   Visual Studio 2015 Ent.


實作


在開發AD相關程式前,最重要的是必須先取得AD Domain的名稱,

可直接在Command line下以下指令查詢:

wmic computersystem get domain

另一個方式是開啟Acive Director管理工具查看。





備註1. AD LDAP下的資料夾,我們稱之為OU (Organization Unit)
備註2. OU及其內的使用者或群組, 都是一個單位:Directory Entry
備註3. 每個Directory Entry都會有一個代表它自己的LDAP Path (路徑)



Service Layer : HOW will we get information from AD

我選擇將一些基礎查詢的服務加進來服務層,定義如何查詢 主要方法有以下:
l   以網域名稱註冊一個AD服務
l   指定LDAP Path,並取得底下所有符合篩條件的Directory Entry
l   指定Directory Entry,並取得底下所有符合篩條件的子節點 (Sub Directory Entries)
l   取得一個Directory Entry的所有屬性。



public class ActiveDirectoryService : IDisposable
{
        /// <summary>
        /// 判斷AD Domain是否存在,並回傳一個對應的ActiveDirectory.Domain物件。 若沒有設定Domain Id則以本機所在的AD網域為主
        /// </summary>
        /// <param name="domainId">網域</param>
        /// <param name="domain">out ActiveDirectory.Domain</param>
        /// <returns>true(OK)/false(無此AD網域)</returns>
        public bool RegisterDirectoryService(
            string domainId, out System.DirectoryServices.ActiveDirectory.Domain domain)
        {
            if (String.IsNullOrEmpty(domainId))
            {
                domain = Domain.GetCurrentDomain();
                return true;
            }
            else
            {
                domain = null;
                DirectoryContext context = new
DirectoryContext(DirectoryContextType.Domain, domainId);
                trys
                {
                    domain =
System.DirectoryServices.ActiveDirectory.Domain.GetDomain(
context);
                    return true;
                }
                catch (Exception)
                {
                    return false;
                }
            }
        }


/// <summary>
        /// 取得LDAP路徑下所有符合條件的子節點
        /// </summary>
        /// <param name="dirPath">LDAP目錄</param>
        /// <param name="filterForDirectorySearcher">Filter條件</param>
        /// <returns>DirectoryEntry集合</returns>
        public IEnumerable<DirectoryEntry> GetDirectoryEntries(String dirPath, String filterForDirectorySearcher)
        {
            var dir = new DirectoryEntry(dirPath);
            return this.GetDirectoryEntries(dir, filterForDirectorySearcher);
        }

        /// <summary>
        /// 取得該節點下所有符合條件的子節點
        /// </summary>
        /// <param name="dir">LDAP目錄</param>
        /// <param name="filterForDirectorySearcher">Filter條件</param>
        /// <returns>DirectoryEntry集合</returns>
        public IEnumerable<DirectoryEntry> GetDirectoryEntries(DirectoryEntry dir, String filterForDirectorySearcher)
        {
            //create the return object
            //var rtnDirectoryEntries = new List<DirectoryEntry>();


            //create instance fo the direcory searcher
            DirectorySearcher deSearch = new DirectorySearcher(dir);

            //set the search filter
            deSearch.Filter = filterForDirectorySearcher;
            SearchResultCollection results = deSearch.FindAll();

            deSearch.SearchScope = SearchScope.Subtree; //預設搜尋所有子節點


            //if found then return, otherwise return Null
            if (results != null && results.Count > 0)
            {
                foreach (SearchResult rslt in results)
                {
                    yield return rslt.GetDirectoryEntry();
                }
            }

        }

        /// <summary>
        /// 取得在指定DirectoryEntry下的Properties
        /// </summary>
        /// <param name="dir">DirectoryEntry</param>
        /// <param name="constraints">指定屬性名稱</param>
        /// <returns>KeyValuePair Emerable</returns>
        public IEnumerable<KeyValuePair<String, object>> GetDirectoryEntryProperties(DirectoryEntry de, String[] constraints = null)
        {

            if (de.Properties != null && de.Properties.Count > 0)
            {
                foreach (string property in de.Properties.PropertyNames)
                {
                    if (constraints == null || constraints.Any(x => x.Equals(property, StringComparison.OrdinalIgnoreCase)))
                    {
                        yield return new KeyValuePair<string, object>(property, de.Properties[property][0]);

                    }
                }
            }

        }
        public void Dispose()
        {
        }
}



Repository : WHAT information will we get from AD

Repository,我們可以決定要找那些資訊,以此範例的目的為列出所有AD User的資訊, 所以我們需要:
1.  尋找所有OU
2.  列出在一個OU下的所有Users

public class ActiveDirectoryRepository : IDisposable
{
        private ActiveDirectoryService _adService = null;

        System.DirectoryServices.ActiveDirectory.Domain _adDomain = null;

        public ActiveDirectoryRepository(String domainId)
        {
            this._adService = new ActiveDirectoryService();
            this._adService.RegisterDirectoryService(domainId, out this._adDomain);
        }

        public IEnumerable<DirectoryEntry> GetAllOU()
        {
            String filter = "(objectCategory=organizationalUnit)";
            return this._adService.GetDirectoryEntries(
this._adDomain.GetDirectoryEntry(), filter);
        }

        public IEnumerable<DirectoryEntry> GetUserDirectoryEntriers(DirectoryEntry de)
        {
          
                String filter = "(objectCategory=user)";
                return this._adService.GetDirectoryEntries(de, filter);

        }

        public IEnumerable<KeyValuePair<String, object>> GetDirectoryEntryProperties(DirectoryEntry de, String[] constraints = null)
        {
             return  this._adService.GetDirectoryEntryProperties(de, constraints);
        }
        public void Dispose()
        {
            this._adService.Dispose();
            this._adService = null;
            this._adDomain = null;
        }
}



主程式

切完階層之後,主程式可專注於在流程上即可。
底下程式碼將列出所有AD User的資訊~

String domainId = "XXXXX.com";

using (var adOURepository = new ActiveDirectoryRepository(domainId))
{
#region Get All OU
    var ouDirectoryEntries = adOURepository.GetAllOU();
    #endregion

    #region In each OU, get all the USERs inside
    foreach (var ouDirectoryEntry in ouDirectoryEntries)
    {
        var userDirectoryEntries =
adOURepository.GetUserDirectoryEntriers(ouDirectoryEntry);

if (userDirectoryEntries != null)
{
#region output an user's information
              foreach (var de in userDirectoryEntries)
{
                  Debug.WriteLine(de.Path);
                  var propertiesContainer =
adOURepository.GetDirectoryEntryProperties(de);
                  propertiesContainer.Each(
x => Debug.WriteLine(
x.Key + "("+ x.Value.GetType() +") : " + x.Value));
               }
               #endregion
}
}





Reference


沒有留言:

張貼留言