Поиски и свойства AD и использование словаря

Чтобы определить, потеряны ли учетные записи компьютеров, я хотел бы запросить все контроллеры домена всех доверенных доменов, чтобы получить lastLogon и lastLogonTimeStamp для всех компьютеров. У меня есть программа, которая работает (по крайней мере, в моей тестовой среде), но у меня есть несколько вопросов, на которые, надеюсь, вы сможете ответить.

  1. Являются ли методы, которые я использую для поиска доменов и контроллеров домена, а затем получения информации AD, с использованием наименьшего количества ресурсов (ЦП и ОЗУ сети/контроллера домена)? Как их можно улучшить?

  2. Возможно ли иметь более 1 значения в паре ключ/значение словаря? Наличие словаря для LastLogIn и другого словаря для LastLogInTimestamp кажется пустой тратой времени.

  3. Ссылаясь на словарь и свойства AD: как я могу проверить несуществующие значения, а не использовать Try/Catch?

    try { // Является ли этот DC более актуальным, чем предыдущий? if (dict_LastLogIn[pc] ‹ (long)result.Properties["lastlogon"][0]) { dict_LastLogIn[pc] = (long)result.Properties["lastlogon"][0]; } } catch { // Элемент еще не существует.. try { dict_LastLogIn[pc] = (long)result.Properties["lastlogon"][0]; } catch { // .. или // Последнего LastLogin нет... dict_LastLogIn[pc] = 0; } }

Вот весь код:

using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;


namespace dictionary
{
    class Program
    {
        internal static Dictionary<string, long> dict_LastLogIn =
            new Dictionary<string, long>();
        internal static Dictionary<string, long> dict_LastLogInTimeStamp =
            new Dictionary<string, long>();
        internal static Dictionary<string, DateTime> output =
            new Dictionary<string, DateTime>();

        internal static bool AreAllDCsResponding = true;

        static void Main(string[] args)
        {
            Console.BufferWidth = 150;
            Console.BufferHeight = 9999;
            Console.WindowWidth = 150;

            Dictionary<String, int> dict_domainList = new Dictionary<String, int>();
            Dictionary<String, int> dict_dcList = new Dictionary<String, int>();

            //Get the current domain's trusts.
            Domain currentDomain = Domain.GetCurrentDomain();
            Console.WriteLine("Retrieved the current Domain as {0}", currentDomain.ToString());
            var domainTrusts = currentDomain.GetAllTrustRelationships();
            Console.WriteLine("  {0} trusts were found.", domainTrusts.Count);
            //Add the current domain to the dictonary.  It won't be in domainTrusts!
            dict_domainList.Add(currentDomain.ToString(), 0);
            // Then add the other domains to the dictonary...
            foreach (TrustRelationshipInformation trust in domainTrusts)
            {
                dict_domainList.Add(trust.TargetName.Substring(0, trust.TargetName.IndexOf(".")).ToUpper(), 0);
                Console.WriteLine("    Adding {0} to the list of trusts.", trust.TargetName.Substring(0, trust.TargetName.IndexOf(".")).ToUpper());
            }
            // Now get all DCs per domain
            foreach (var pair in dict_domainList)
            {
                DirectoryContext dc = new DirectoryContext(DirectoryContextType.Domain, pair.Key);
                Domain _Domain = Domain.GetDomain(dc);
                foreach (DomainController Server in _Domain.DomainControllers)
                {
                    dict_dcList.Add(Server.Name, 0);
                    Console.WriteLine("      Adding {0} to the list of DCs.", Server.Name.ToUpper());

                }
                // Now search through every DC
                foreach (var _pair in dict_dcList)
                {
                    Console.WriteLine("        Querying {0} for Computer objects.", _pair.Key.ToUpper());
                    Search(pair.Key);
                    Console.WriteLine("\n");
                    Console.WriteLine("The following Computer objects were found:");
                }

                if (AreAllDCsResponding == true)
                {
                    ConvertTimeStamp(dict_LastLogIn);
                }
                else
                {
                    ConvertTimeStamp(dict_LastLogInTimeStamp);
                }
                Console.ReadLine();
            }
        }

        internal static void Search(string domainName)
        {
            DirectoryEntry entry = new DirectoryEntry("LDAP://" + domainName);
            DirectorySearcher mySearcher = new DirectorySearcher(entry);
            mySearcher.Filter = ("(&(ObjectCategory=computer))");//(lastlogon=*)(lastlogonTimeStamp=*))");
            mySearcher.SizeLimit = int.MaxValue;
            mySearcher.PropertiesToLoad.Add("DistinguishedName");
            mySearcher.PropertiesToLoad.Add("lastlogon");
            mySearcher.PropertiesToLoad.Add("lastlogonTimeStamp");
            try
            {
                foreach (System.DirectoryServices.SearchResult result in mySearcher.FindAll())
                {
                    string pc = result.Properties["DistinguishedName"][0].ToString();
                    try
                    {   // Is this DC more current than the last?
                        if (dict_LastLogIn[pc] < (long)result.Properties["lastlogon"][0])
                        {
                            dict_LastLogIn[pc] = (long)result.Properties["lastlogon"][0];
                        }
                    }
                    catch
                    {   // The item doesn't exist yet..
                        try
                        {
                            dict_LastLogIn[pc] = (long)result.Properties["lastlogon"][0];
                        }
                        catch
                        {   // .. or
                            // There is no last LastLogin...
                            dict_LastLogIn[pc] = 0;
                        }
                    }

                    try
                    {
                        // Not yet replicated?...
                        if (dict_LastLogInTimeStamp[pc] < (long)result.Properties["lastlogonTimeStamp"][0])
                        {
                            dict_LastLogInTimeStamp[pc] = (long)result.Properties["lastlogonTimeStamp"][0];
                        }
                    }
                    catch
                    {   // The item doesn't exist yet..
                        try
                        {
                            dict_LastLogInTimeStamp[pc] = (long)result.Properties["lastlogonTimeStamp"][0];
                        }
                        catch
                        {   // .. or
                            // There is no last LastLoginTimeStamp...
                            dict_LastLogInTimeStamp[pc] = 0;
                        }
                    }
                }
            }
            catch (System.Runtime.InteropServices.COMException)
            {
                //If even one DC doesn't answer, don't use LastLogon!  
                //Use the less accurate, but replicated(!) LastLogonTimeStamp. 
                AreAllDCsResponding = false;
            }
        }

        internal static void ConvertTimeStamp(Dictionary<string, long> _dict)
        {
            foreach (var pair in _dict)
            {
                output.Add(pair.Key, DateTime.FromFileTime(pair.Value));
                Console.WriteLine("{0} - {1}", pair.Key, DateTime.FromFileTime(pair.Value));
            }
        }
    }
}

Спасибо за любую помощь, которую вы можете предложить.


person Daro    schedule 03.12.2011    source источник


Ответы (1)


На высоком уровне я не уверен, какова ваша конечная цель с этим, но вы уверены, что действительно хотите запрашивать каждый контроллер домена для lastLogon? Это может быть очень дорого. Кроме того, почему вы ходите по цепочкам доверия? Вы уверены, что вам не нужны все домены в данном лесу (Forest.Domains)?

В ответ на ваши вопросы:

  1. Это выглядит нормально с точки зрения производительности, однако вы должны сделать пару вещей:

    • Tweak your filter to (&(objectCategory=computer)(objectClass=computer))
    • Добавить mySearcher.PageSize = 1000
    • Удалить mySearcher.SizeLimit = int.MaxValue
  2. Вы можете использовать Tuple - http://msdn.microsoft.com/en-us/library/system.tuple(VS.90).aspx. Или просто определите пользовательский класс как ваше значение, а затем объявите словарь как ваш словарь:

    открытый класс LogonTimeStamps {

    общественный длинный LastLogon { получить; установлен; }

    общественный длинный LastLogonTimeStamp { получить; установлен; }

    }

  3. Для Словаря используйте myDictionary.ContainsKey(yourKey). Для AD вы должны иметь возможность использовать result.Properties.Contains("yourAttribute").

person Brian Desmond    schedule 04.12.2011
comment
Брайан, спасибо за ваш вклад. - Финал? Конечно, всегда есть что-то еще… Мне нужна информация для всех доменов в наших многочисленных лесах, отсюда и трасты. Во-первых, он обновит базу данных с самым последним возможным «Время последнего подключения к AD». Компьютеры, не подключенные к сети, скажем, 1 год, будут отключены. Что касается 1, то ваш фильтр конечно лучше. Зачем уменьшать размер страницы? Есть ли польза? Я буду получать около 10 000 результатов на запрос, поэтому MaxSize не может оставаться на значении по умолчанию, равном 1000. - person Daro; 04.12.2011
comment
2. Используя словарь, я могу получить доступ к паре на основе отличительного имени напрямую, без циклов. Могут ли кортежи или пользовательские классы сделать это? Как? Извините, но я не программист, а просто админ, который хочет расширить свой набор инструментов... 3. Мне нужно разобраться с этим. - Я также думаю, что непосредственно перед обращением к каждому контроллеру домена я пропингую их, что сэкономит до двух минут на недостижимый контроллер домена. - person Daro; 04.12.2011
comment
Максимальный размер страницы по умолчанию в AD составляет 1000, если кто-то не возился с ним. DirectorySearcher будет автоматически перелистывать записи, если вы установите это значение. Параметр SizeLimit необязателен. Что касается словаря, вы будете хранить Dictionary<string, LogonTimeStamp>, поэтому вы все равно будете получать к нему доступ через DN. Наконец, для производительности вы также можете рассмотреть многопоточность, чтобы запрашивать каждый контроллер домена в отдельном потоке. Вы можете сделать это довольно легко с классом ThreadPool. - person Brian Desmond; 04.12.2011
comment
Я получаю только 1000 результатов, если не устанавливаю MaxSize. Возможно, что-то было изменено. Многопоточность звучит превосходно, но что произойдет, если два или более потока попытаются одновременно изменить одну и ту же пару ключ/значение в словаре? Предполагая, что я хочу создать поток Search(pair.Key);, как это можно реализовать? - person Daro; 04.12.2011
comment
Многопоточность - это действительно отдельный вопрос IMO. На высоком уровне вы используете ключевое слово lock() для обработки параллелизма. Я не уверен, почему здесь играет SizeLimit — в документах говорится, что он игнорируется для › 1000. Размер страницы здесь важен. - person Brian Desmond; 07.12.2011