C#: Многопоточный (задачный) командлет PowerShell

По теме, я пытаюсь собрать многопоточный командлет PowerShell и борюсь. Я могу заставить код успешно работать как консольное приложение, и я могу заставить работать однопоточный командлет, но пока не могу объединить их.

Кроме того, пожалуйста, поймите, что я новичок в C# и очень мало знаю о том, что я делаю, поэтому заранее извиняюсь за зверство моего кода. Перед публикацией постарался максимально упростить.

Вот что у меня есть:

      using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Management.Automation;
using System.DirectoryServices;
using System.Diagnostics;
using System.Threading;

namespace Sample
{
    [Cmdlet(VerbsCommon.Get,"LDAPObject")]
    [OutputType(typeof(List<SearchResult>))]
    public class GetLDAPObjectCmdlet: Cmdlet
    {
        [Parameter(ValueFromPipeline = true)]
        public string searchRoot { get; set; }
        [Parameter]
        public TimeSpan maxWaitTime { get; set; }

        private List<Task<searchResults>> taskList = new List<Task<searchResults>>();

        protected override void BeginProcessing()
        {
            base.BeginProcessing();
        }

        protected override void ProcessRecord()
        {
            base.ProcessRecord();

            var ts = new CancellationTokenSource();
            CancellationToken ct = ts.Token;

            Task<searchResults> searchTask = Task<searchResults>.Factory.StartNew(() => Search(ct, searchRoot));
            taskList.Add(searchTask);

            Stopwatch sw = Stopwatch.StartNew();

            bool tasksCompleted = new bool();
            try
            {
                tasksCompleted = Task.WaitAll(taskList.ToArray(), maxWaitTime);
            }
            catch (OperationCanceledException ex)
            {
                WriteWarning("Task cancelled.");
            }

            int resultCount = 0;
            foreach (Task<searchResults> taskItem in taskList)
            {
                if ((taskItem.Status == TaskStatus.RanToCompletion) && (taskItem.Result.src != null))
                {
                    resultCount += taskItem.Result.src.Count;
                }

                foreach (string message in taskItem.Result.warningMessages)
                {
                    WriteWarning(message);
                }
            }

            sw.Stop();

            if (tasksCompleted)
            {
                WriteVerbose("The search returned `'" + resultCount.ToString() + "' results in '" + (sw.Elapsed.TotalSeconds).ToString() + "' seconds.");
            }
            else
            {
                WriteVerbose("The search did not complete before the deadline.");
            }
        }

        protected override void EndProcessing()
        {
            base.EndProcessing();

            for (int i = 0; i < taskList.Count; i++)
            {
                taskList[i].Dispose();
            }
        }

        protected override void StopProcessing()
        {
            base.StopProcessing(); 
        }
        
        private static searchResults Search(CancellationToken ct, string searchRootDn)
        {
            if (ct.IsCancellationRequested)
            {
                ct.ThrowIfCancellationRequested();
            }
            searchResults results = new searchResults();
            results.warningMessages = new List<string>();
            results.verboseMessages = new List<string>();

            results.warningMessages.Add("Search root: '" + searchRootDn + "'.");
            DirectoryEntry searchRoot = new DirectoryEntry("LDAP://" + searchRootDn);
            try
            {
                Guid guid = searchRoot.Guid;
            }
            catch (Exception ex)
            {
                throw new Exception("Invalid search root");
            }

            string searchFilter = "some LDAP filter here";
            results.warningMessages.Add("Search filter: '" + searchFilter + "'.");

            DirectorySearcher directorySearcher = new DirectorySearcher();
            directorySearcher.SearchRoot = searchRoot;
            directorySearcher.SearchScope = SearchScope.Subtree;
            directorySearcher.PageSize = 1000;
            directorySearcher.Filter = searchFilter;

            results.src = directorySearcher.FindAll();
            directorySearcher.Dispose();
            return results;
        }

    }

    class searchResults
    {
        public SearchResultCollection src { get; set; }
        public List<string> warningMessages { get; set; }
        public List<string> verboseMessages { get; set; }
    }
}

Это дает мне:

System.InvalidCastException HResult = 0x80004002 Message = Невозможно преобразовать COM-объект типа «System.__ComObject» в тип интерфейса «IDirectorySearch». Эта операция завершилась неудачно, так как вызов QueryInterface для COM-компонента для интерфейса с IID "{109BA8EC-92F0-11D0-A790-00C04FD8D5A8}" завершился сбоем из-за следующей ошибки: такой интерфейс не поддерживается (исключение из HRESULT: 0x80004002 (E_NOINTERFACE)) . Source=System.DirectoryServicesStackTrace: в System.DirectoryServices.SearchResultCollection.get_SearchObject() в System.DirectoryServices.SearchResultCollection.ResultsEnumerator.MoveNext() в System.DirectoryServices.SearchResultCollection.get_InnerList() в System.DirectoryServices.SearchResultCollection.get_Count() в MultiThreadedSearch.GetLDAPObjectCmdlet.ProcessRecord() в C:\Users\obfuscated\GETLDAPObjectCmdlet.

на resultCount += taskItem.Result.src.Count;линия.

Обратите внимание, что на самом деле мне не нужен класс searchResult ; Я только что представил его из-за отсутствия средства, с помощью которого функция поиска могла бы вносить контент в WriteWarning, WriteVerbose и т. д.

Мысли ценятся выше всего!

1 ответ

Похоже, я сам решил свою проблему. Неожиданно оказалось, что проблема связана с тем, как реализован интерфейс DirectorySearcher. В частности, результаты поиска LDAP не кэшируются до первого вызова для перечисления SearchResultsCollection, в частности, когда я пытался получить доступ к свойству Count . Это хорошо описано здесь:

Обычно мы используем оператор foreach для перечисления результатов. В отличие от некоторых других классов коллекций в .NET Framework, SearchResultCollection внутренне реализует собственный частный объект IEnumerator, который извлекает результаты из каталога. Любой доступ к свойствам SearchResultCollection, например проверка свойства Count, приведет к тому, что весь набор результатов будет извлечен из каталога, а затем пронумерован. Это может иметь большое влияние, особенно при возврате большого количества результатов, по нескольким причинам. Во-первых, использование цикла foreach позволило бы при необходимости прервать поиск, не перебирая полностью все результаты. Это более эффективно, так как мы прерываем поиск раньше и не заставляем сервер сначала возвращать все результаты. Второй, мы можем обрабатывать каждый результат, поступающий с сервера, используя цикл foreach, не дожидаясь завершения всего поиска. Использование цикла for в сочетании со свойством Count предотвращает оба этих сценария. Таким образом, мы обычно рекомендуем использовать цикл foreach при перечислении наших результатов.

Другие вопросы по тегам