title: C#: Multithreaded (task) PowerShell cmdlet
body: <p>Per the subject, I'm trying to put together a multithreaded PowerShell cmdlet and am struggling. I can get the code to run successfully as a console app, and I can get the single threaded cmdlet to work, but haven't been able to merge the two as of yet.</p>
  <p>Also, please understand that I am new to C# and have very little clue about what I'm doing, so apologies in advance for the atrocity that is my code. Tried to simplify as much as I could before posting.</p>
  <p>Here's what I've got so far:</p>
  <pre><code>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,&quot;LDAPObject&quot;)]
      [OutputType(typeof(List&lt;SearchResult&gt;))]
      public class GetLDAPObjectCmdlet: Cmdlet
      {
          [Parameter(ValueFromPipeline = true)]
          public string searchRoot { get; set; }
          [Parameter]
          public TimeSpan maxWaitTime { get; set; }
  
          private List&lt;Task&lt;searchResults&gt;&gt; taskList = new List&lt;Task&lt;searchResults&gt;&gt;();
  
          protected override void BeginProcessing()
          {
              base.BeginProcessing();
          }
  
          protected override void ProcessRecord()
          {
              base.ProcessRecord();
  
              var ts = new CancellationTokenSource();
              CancellationToken ct = ts.Token;
  
              Task&lt;searchResults&gt; searchTask = Task&lt;searchResults&gt;.Factory.StartNew(() =&gt; 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(&quot;Task cancelled.&quot;);
              }
  
              int resultCount = 0;
              foreach (Task&lt;searchResults&gt; taskItem in taskList)
              {
                  if ((taskItem.Status == TaskStatus.RanToCompletion) &amp;&amp; (taskItem.Result.src != null))
                  {
                      resultCount += taskItem.Result.src.Count;
                  }
  
                  foreach (string message in taskItem.Result.warningMessages)
                  {
                      WriteWarning(message);
                  }
              }
  
              sw.Stop();
  
              if (tasksCompleted)
              {
                  WriteVerbose(&quot;The search returned `'&quot; + resultCount.ToString() + &quot;' results in '&quot; + (sw.Elapsed.TotalSeconds).ToString() + &quot;' seconds.&quot;);
              }
              else
              {
                  WriteVerbose(&quot;The search did not complete before the deadline.&quot;);
              }
          }
  
          protected override void EndProcessing()
          {
              base.EndProcessing();
  
              for (int i = 0; i &lt; 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&lt;string&gt;();
              results.verboseMessages = new List&lt;string&gt;();
  
              results.warningMessages.Add(&quot;Search root: '&quot; + searchRootDn + &quot;'.&quot;);
              DirectoryEntry searchRoot = new DirectoryEntry(&quot;LDAP://&quot; + searchRootDn);
              try
              {
                  Guid guid = searchRoot.Guid;
              }
              catch (Exception ex)
              {
                  throw new Exception(&quot;Invalid search root&quot;);
              }
  
              string searchFilter = &quot;some LDAP filter here&quot;;
              results.warningMessages.Add(&quot;Search filter: '&quot; + searchFilter + &quot;'.&quot;);
  
              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&lt;string&gt; warningMessages { get; set; }
          public List&lt;string&gt; verboseMessages { get; set; }
      }
  }
  </code></pre>
  <p>This gives me:</p>
  <blockquote>
  <p>System.InvalidCastException
  HResult=0x80004002
  Message=Unable to cast COM object of type 'System.__ComObject' to interface type 'IDirectorySearch'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{109BA8EC-92F0-11D0-A790-00C04FD8D5A8}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
  Source=System.DirectoryServices
  StackTrace:
  at System.DirectoryServices.SearchResultCollection.get_SearchObject()
  at System.DirectoryServices.SearchResultCollection.ResultsEnumerator.MoveNext()
  at System.DirectoryServices.SearchResultCollection.get_InnerList()
  at System.DirectoryServices.SearchResultCollection.get_Count()
  at MultiThreadedSearch.GetLDAPObjectCmdlet.ProcessRecord() in C:\Users\obfuscated\GETLDAPObjectCmdlet.cs:line 73
  at System.Management.Automation.CommandProcessor.ProcessRecord()</p>
  </blockquote>
  <p>on the <em>resultCount += taskItem.Result.src.Count;</em> line.</p>
  <p>Note that I didn't actually need the <em>searchResult</em> class; I just introduced it for want of a means by which to allow the Search function to be able to contribute content to <em>WriteWarning</em>, <em>WriteVerbose</em>, etc.</p>
  <p>Thoughts are most appreciated!</p>