1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Runtime.InteropServices;
6 using System.Collections;
7 using System.Collections.Specialized;
8 using System.DirectoryServices.Interop;
9 using System.ComponentModel;
10 
11 using INTPTR_INTPTRCAST = System.IntPtr;
12 
13 namespace System.DirectoryServices
14 {
15     /// <devdoc>
16     /// Performs queries against the Active Directory hierarchy.
17     /// </devdoc>
18     public class DirectorySearcher : Component
19     {
20         private DirectoryEntry _searchRoot;
21         private string _filter = defaultFilter;
22         private StringCollection _propertiesToLoad;
23         private bool _disposed = false;
24 
25         private static readonly TimeSpan s_minusOneSecond = new TimeSpan(0, 0, -1);
26 
27         // search preference variables
28         private SearchScope _scope = System.DirectoryServices.SearchScope.Subtree;
29         private bool _scopeSpecified = false;
30         private int _sizeLimit = 0;
31         private TimeSpan _serverTimeLimit = s_minusOneSecond;
32         private TimeSpan _clientTimeout = s_minusOneSecond;
33         private int _pageSize = 0;
34         private TimeSpan _serverPageTimeLimit = s_minusOneSecond;
35         private ReferralChasingOption _referralChasing = ReferralChasingOption.External;
36         private SortOption _sort = new SortOption();
37         private bool _cacheResults = true;
38         private bool _cacheResultsSpecified = false;
39         private bool _rootEntryAllocated = false;             // true: if a temporary entry inside Searcher has been created
40         private string _assertDefaultNamingContext = null;
41         private string _attributeScopeQuery = "";
42         private bool _attributeScopeQuerySpecified = false;
43         private DereferenceAlias _derefAlias = DereferenceAlias.Never;
44         private SecurityMasks _securityMask = SecurityMasks.None;
45         private ExtendedDN _extendedDN = ExtendedDN.None;
46         private DirectorySynchronization _sync = null;
47         internal bool directorySynchronizationSpecified = false;
48         private DirectoryVirtualListView _vlv = null;
49         internal bool directoryVirtualListViewSpecified = false;
50         internal SearchResultCollection searchResult = null;
51 
52         private const string defaultFilter = "(objectClass=*)";
53 
54         /// <devdoc>
55         /// Initializes a new instance of the <see cref='System.DirectoryServices.DirectorySearcher'/> class with <see cref='System.DirectoryServices.DirectorySearcher.SearchRoot'/>,
56         /// <see cref='System.DirectoryServices.DirectorySearcher.Filter'/>, <see cref='System.DirectoryServices.DirectorySearcher.PropertiesToLoad'/>, and <see cref='System.DirectoryServices.DirectorySearcher.SearchScope'/> set to their default values.
57         /// </devdoc>
DirectorySearcher()58         public DirectorySearcher() : this(null, defaultFilter, null, System.DirectoryServices.SearchScope.Subtree)
59         {
60             _scopeSpecified = false;
61         }
62 
63         /// <devdoc>
64         /// Initializes a new instance of the <see cref='System.DirectoryServices.DirectorySearcher'/> class with
65         /// <see cref='System.DirectoryServices.DirectorySearcher.Filter'/>, <see cref='System.DirectoryServices.DirectorySearcher.PropertiesToLoad'/>, and <see cref='System.DirectoryServices.DirectorySearcher.SearchScope'/> set to their default
66         ///  values, and <see cref='System.DirectoryServices.DirectorySearcher.SearchRoot'/> set to the given value.
67         /// </devdoc>
DirectorySearcher(DirectoryEntry searchRoot)68         public DirectorySearcher(DirectoryEntry searchRoot) : this(searchRoot, defaultFilter, null, System.DirectoryServices.SearchScope.Subtree)
69         {
70             _scopeSpecified = false;
71         }
72 
73         /// <devdoc>
74         /// Initializes a new instance of the <see cref='System.DirectoryServices.DirectorySearcher'/> class with
75         /// <see cref='System.DirectoryServices.DirectorySearcher.PropertiesToLoad'/> and <see cref='System.DirectoryServices.DirectorySearcher.SearchScope'/> set to their default
76         /// values, and <see cref='System.DirectoryServices.DirectorySearcher.SearchRoot'/> and <see cref='System.DirectoryServices.DirectorySearcher.Filter'/> set to the respective given values.
77         /// </devdoc>
DirectorySearcher(DirectoryEntry searchRoot, string filter)78         public DirectorySearcher(DirectoryEntry searchRoot, string filter) : this(searchRoot, filter, null, System.DirectoryServices.SearchScope.Subtree)
79         {
80             _scopeSpecified = false;
81         }
82 
83         /// <devdoc>
84         /// Initializes a new instance of the <see cref='System.DirectoryServices.DirectorySearcher'/> class with
85         /// <see cref='System.DirectoryServices.DirectorySearcher.SearchScope'/> set to its default
86         /// value, and <see cref='System.DirectoryServices.DirectorySearcher.SearchRoot'/>, <see cref='System.DirectoryServices.DirectorySearcher.Filter'/>, and <see cref='System.DirectoryServices.DirectorySearcher.PropertiesToLoad'/> set to the respective given values.
87         /// </devdoc>
DirectorySearcher(DirectoryEntry searchRoot, string filter, string[] propertiesToLoad)88         public DirectorySearcher(DirectoryEntry searchRoot, string filter, string[] propertiesToLoad) : this(searchRoot, filter, propertiesToLoad, System.DirectoryServices.SearchScope.Subtree)
89         {
90             _scopeSpecified = false;
91         }
92 
93         /// <devdoc>
94         /// Initializes a new instance of the <see cref='System.DirectoryServices.DirectorySearcher'/> class with <see cref='System.DirectoryServices.DirectorySearcher.SearchRoot'/>,
95         /// <see cref='System.DirectoryServices.DirectorySearcher.PropertiesToLoad'/>, and <see cref='System.DirectoryServices.DirectorySearcher.SearchScope'/> set to their default
96         ///    values, and <see cref='System.DirectoryServices.DirectorySearcher.Filter'/> set to the given value.
97         /// </devdoc>
DirectorySearcher(string filter)98         public DirectorySearcher(string filter) : this(null, filter, null, System.DirectoryServices.SearchScope.Subtree)
99         {
100             _scopeSpecified = false;
101         }
102 
103         /// <devdoc>
104         /// Initializes a new instance of the <see cref='System.DirectoryServices.DirectorySearcher'/> class with <see cref='System.DirectoryServices.DirectorySearcher.SearchRoot'/>
105         /// and <see cref='System.DirectoryServices.DirectorySearcher.SearchScope'/> set to their default
106         /// values, and <see cref='System.DirectoryServices.DirectorySearcher.Filter'/> and <see cref='System.DirectoryServices.DirectorySearcher.PropertiesToLoad'/> set to the respective given values.
107         /// </devdoc>
DirectorySearcher(string filter, string[] propertiesToLoad)108         public DirectorySearcher(string filter, string[] propertiesToLoad) : this(null, filter, propertiesToLoad, System.DirectoryServices.SearchScope.Subtree)
109         {
110             _scopeSpecified = false;
111         }
112 
113         /// <devdoc>
114         /// Initializes a new instance of the <see cref='System.DirectoryServices.DirectorySearcher'/> class with <see cref='System.DirectoryServices.DirectorySearcher.SearchRoot'/> set to its default
115         /// value, and <see cref='System.DirectoryServices.DirectorySearcher.Filter'/>, <see cref='System.DirectoryServices.DirectorySearcher.PropertiesToLoad'/>, and <see cref='System.DirectoryServices.DirectorySearcher.SearchScope'/> set to the respective given values.</para>
116         /// </devdoc>
DirectorySearcher(string filter, string[] propertiesToLoad, SearchScope scope)117         public DirectorySearcher(string filter, string[] propertiesToLoad, SearchScope scope) : this(null, filter, propertiesToLoad, scope)
118         {
119         }
120 
121         /// <devdoc>
122         /// Initializes a new instance of the <see cref='System.DirectoryServices.DirectorySearcher'/> class with the <see cref='System.DirectoryServices.DirectorySearcher.SearchRoot'/>, <see cref='System.DirectoryServices.DirectorySearcher.Filter'/>, <see cref='System.DirectoryServices.DirectorySearcher.PropertiesToLoad'/>, and <see cref='System.DirectoryServices.DirectorySearcher.SearchScope'/> properties set to the given
123         /// values.
124         /// </devdoc>
DirectorySearcher(DirectoryEntry searchRoot, string filter, string[] propertiesToLoad, SearchScope scope)125         public DirectorySearcher(DirectoryEntry searchRoot, string filter, string[] propertiesToLoad, SearchScope scope)
126         {
127             _searchRoot = searchRoot;
128             _filter = filter;
129             if (propertiesToLoad != null)
130                 PropertiesToLoad.AddRange(propertiesToLoad);
131             this.SearchScope = scope;
132         }
133 
Dispose(bool disposing)134         protected override void Dispose(bool disposing)
135         {
136             // safe to call while finalizing or disposing
137             //
138             if (!_disposed && disposing)
139             {
140                 if (_rootEntryAllocated)
141                     _searchRoot.Dispose();
142                 _rootEntryAllocated = false;
143                 _disposed = true;
144             }
145             base.Dispose(disposing);
146         }
147 
148         /// <devdoc>
149         /// Gets or sets a value indicating whether the result should be cached on the
150         /// client machine.
151         /// </devdoc>
152         [DefaultValue(true)]
153         public bool CacheResults
154         {
155             get => _cacheResults;
156             set
157             {
158                 // user explicitly set CacheResults to true and also want VLV
159                 if (directoryVirtualListViewSpecified == true && value == true)
160                     throw new ArgumentException(SR.DSBadCacheResultsVLV);
161 
162                 _cacheResults = value;
163 
164                 _cacheResultsSpecified = true;
165             }
166         }
167 
168         /// <devdoc>
169         ///  Gets or sets the maximum amount of time that the client waits for
170         ///  the server to return results. If the server does not respond within this time,
171         ///  the search is aborted, and no results are returned.</para>
172         /// </devdoc>
173         public TimeSpan ClientTimeout
174         {
175             get => _clientTimeout;
176             set
177             {
178                 // prevent integer overflow
179                 if (value.TotalSeconds > Int32.MaxValue)
180                 {
181                     throw new ArgumentException(SR.TimespanExceedMax, "value");
182                 }
183 
184                 _clientTimeout = value;
185             }
186         }
187 
188         /// <devdoc>
189         /// Gets or sets a value indicating whether the search should retrieve only the names of requested
190         /// properties or the names and values of requested properties.</para>
191         /// </devdoc>
192         [DefaultValue(false)]
193         public bool PropertyNamesOnly { get; set; }
194 
195         /// <devdoc>
196         /// Gets or sets the Lightweight Directory Access Protocol (LDAP) filter string format.
197         /// </devdoc>
198         [
199             DefaultValue(defaultFilter),
200             // CoreFXPort - Remove design support
201             // TypeConverter("System.Diagnostics.Design.StringValueConverter, " + AssemblyRef.SystemDesign)
202         ]
203         public string Filter
204         {
205             get => _filter;
206             set
207             {
208                 if (value == null || value.Length == 0)
209                     value = defaultFilter;
210                 _filter = value;
211             }
212         }
213 
214         /// <devdoc>
215         /// Gets or sets the page size in a paged search.
216         /// </devdoc>
217         [DefaultValue(0)]
218         public int PageSize
219         {
220             get => _pageSize;
221             set
222             {
223                 if (value < 0)
224                     throw new ArgumentException(SR.DSBadPageSize);
225 
226                 // specify non-zero pagesize explicitly and also want dirsync
227                 if (directorySynchronizationSpecified == true && value != 0)
228                     throw new ArgumentException(SR.DSBadPageSizeDirsync);
229 
230                 _pageSize = value;
231             }
232         }
233 
234         /// <devdoc>
235         /// Gets the set of properties retrieved during the search. By default, the <see cref='System.DirectoryServices.DirectoryEntry.Path'/>
236         /// and <see cref='System.DirectoryServices.DirectoryEntry.Name'/> properties are retrieved.
237         /// </devdoc>
238         public StringCollection PropertiesToLoad
239         {
240             get
241             {
242                 if (_propertiesToLoad == null)
243                 {
244                     _propertiesToLoad = new StringCollection();
245                 }
246                 return _propertiesToLoad;
247             }
248         }
249 
250         /// <devdoc>
251         /// Gets or sets how referrals are chased.
252         /// </devdoc>
253         [DefaultValue(ReferralChasingOption.External)]
254         public ReferralChasingOption ReferralChasing
255         {
256             get => _referralChasing;
257             set
258             {
259                 if (value != ReferralChasingOption.None &&
260                     value != ReferralChasingOption.Subordinate &&
261                     value != ReferralChasingOption.External &&
262                     value != ReferralChasingOption.All)
263                     throw new InvalidEnumArgumentException("value", (int)value, typeof(ReferralChasingOption));
264 
265                 _referralChasing = value;
266             }
267         }
268 
269         /// <devdoc>
270         /// Gets or sets the scope of the search that should be observed by the server.
271         /// </devdoc>
272         [DefaultValue(SearchScope.Subtree)]
273         public SearchScope SearchScope
274         {
275             get => _scope;
276             set
277             {
278                 if (value < SearchScope.Base || value > SearchScope.Subtree)
279                     throw new InvalidEnumArgumentException("value", (int)value, typeof(SearchScope));
280 
281                 // user explicitly set SearchScope to something other than Base and also want to do ASQ, it is not supported
282                 if (_attributeScopeQuerySpecified == true && value != SearchScope.Base)
283                 {
284                     throw new ArgumentException(SR.DSBadASQSearchScope);
285                 }
286 
287                 _scope = value;
288 
289                 _scopeSpecified = true;
290             }
291         }
292 
293         /// <devdoc>
294         /// Gets or sets the time limit that the server should observe to search a page of results (as
295         /// opposed to the time limit for the entire search).
296         /// </devdoc>
297         public TimeSpan ServerPageTimeLimit
298         {
299             get => _serverPageTimeLimit;
300             set
301             {
302                 // prevent integer overflow
303                 if (value.TotalSeconds > Int32.MaxValue)
304                 {
305                     throw new ArgumentException(SR.TimespanExceedMax, "value");
306                 }
307 
308                 _serverPageTimeLimit = value;
309             }
310         }
311 
312         /// <devdoc>
313         /// Gets or sets the maximum amount of time the server spends searching. If the
314         /// time limit is reached, only entries found up to that point will be returned.
315         /// </devdoc>
316         public TimeSpan ServerTimeLimit
317         {
318             get => _serverTimeLimit;
319             set
320             {
321                 // prevent integer overflow
322                 if (value.TotalSeconds > Int32.MaxValue)
323                 {
324                     throw new ArgumentException(SR.TimespanExceedMax, "value");
325                 }
326 
327                 _serverTimeLimit = value;
328             }
329         }
330 
331         /// <devdoc>
332         ///  Gets or sets the maximum number of objects that the
333         ///  server should return in a search.
334         /// </devdoc>
335         [DefaultValue(0)]
336         public int SizeLimit
337         {
338             get => _sizeLimit;
339             set
340             {
341                 if (value < 0)
342                     throw new ArgumentException(SR.DSBadSizeLimit);
343                 _sizeLimit = value;
344             }
345         }
346 
347         /// <devdoc>
348         /// Gets or sets the node in the Active Directory hierarchy
349         /// at which the search will start.
350         /// </devdoc>
351         [DefaultValue(null)]
352         public DirectoryEntry SearchRoot
353         {
354             get
355             {
356                 if (_searchRoot == null && !DesignMode)
357                 {
358                     // get the default naming context. This should be the default root for the search.
359                     DirectoryEntry rootDSE = new DirectoryEntry("LDAP://RootDSE", true, null, null, AuthenticationTypes.Secure);
360 
361                     //SECREVIEW: Searching the root of the DS will demand browse permissions
362                     //                     on "*" or "LDAP://RootDSE".
363                     string defaultNamingContext = (string)rootDSE.Properties["defaultNamingContext"][0];
364                     rootDSE.Dispose();
365 
366                     _searchRoot = new DirectoryEntry("LDAP://" + defaultNamingContext, true, null, null, AuthenticationTypes.Secure);
367                     _rootEntryAllocated = true;
368                     _assertDefaultNamingContext = "LDAP://" + defaultNamingContext;
369                 }
370                 return _searchRoot;
371             }
372             set
373             {
374                 if (_rootEntryAllocated)
375                     _searchRoot.Dispose();
376                 _rootEntryAllocated = false;
377 
378                 _assertDefaultNamingContext = null;
379                 _searchRoot = value;
380             }
381         }
382 
383         /// <devdoc>
384         /// Gets the property on which the results should be sorted.
385         /// </devdoc>
386         [TypeConverter(typeof(ExpandableObjectConverter))]
387         public SortOption Sort
388         {
389             get => _sort;
390             set => _sort = value ?? throw new ArgumentNullException(nameof(value));
391         }
392 
393         /// <devdoc>
394         /// Gets or sets a value indicating whether searches should be carried out in an asynchronous
395         /// way.
396         /// </devdoc>
397         [DefaultValue(false)]
398         public bool Asynchronous { get; set; }
399 
400         /// <devdoc>
401         /// Gets or sets a value indicating whether the search should also return deleted objects that match the search
402         /// filter.
403         /// </devdoc>
404         [DefaultValue(false)]
405         public bool Tombstone { get; set; }
406 
407         /// <devdoc>
408         /// Gets or sets an attribute name to indicate that an attribute-scoped query search should be
409         /// performed.
410         /// </devdoc>
411         [
412             DefaultValue(""),
413            // CoreFXPort - Remove design support
414            // TypeConverter("System.Diagnostics.Design.StringValueConverter, " + AssemblyRef.SystemDesign)
415         ]
416         public string AttributeScopeQuery
417         {
418             get => _attributeScopeQuery;
419             set
420             {
421                 if (value == null)
422                     value = "";
423 
424                 // user explicitly set AttributeScopeQuery and value is not null or empty string
425                 if (value.Length != 0)
426                 {
427                     if (_scopeSpecified == true && SearchScope != SearchScope.Base)
428                     {
429                         throw new ArgumentException(SR.DSBadASQSearchScope);
430                     }
431 
432                     // if user did not explicitly set search scope
433                     _scope = SearchScope.Base;
434 
435                     _attributeScopeQuerySpecified = true;
436                 }
437                 else
438                 // user explicitly sets the value to default one and doesn't want to do asq
439                 {
440                     _attributeScopeQuerySpecified = false;
441                 }
442 
443                 _attributeScopeQuery = value;
444             }
445         }
446 
447         /// <devdoc>
448         /// Gets or sets a value to indicate how the aliases of found objects are to be
449         /// resolved.
450         /// </devdoc>
451         [DefaultValue(DereferenceAlias.Never)]
452         public DereferenceAlias DerefAlias
453         {
454             get => _derefAlias;
455             set
456             {
457                 if (value < DereferenceAlias.Never || value > DereferenceAlias.Always)
458                     throw new InvalidEnumArgumentException("value", (int)value, typeof(DereferenceAlias));
459 
460                 _derefAlias = value;
461             }
462         }
463 
464         /// <devdoc>
465         /// Gets or sets a value to indicate the search should return security access information for the specified
466         /// attributes.
467         /// </devdoc>
468         [DefaultValue(SecurityMasks.None)]
469         public SecurityMasks SecurityMasks
470         {
471             get => _securityMask;
472             set
473             {
474                 // make sure the behavior is consistent with native ADSI
475                 if (value > (SecurityMasks.None | SecurityMasks.Owner | SecurityMasks.Group | SecurityMasks.Dacl | SecurityMasks.Sacl))
476                     throw new InvalidEnumArgumentException("value", (int)value, typeof(SecurityMasks));
477 
478                 _securityMask = value;
479             }
480         }
481 
482         /// <devdoc>
483         /// Gets or sets a value to return extended DNs according to the requested
484         /// format.
485         /// </devdoc>
486         [DefaultValue(ExtendedDN.None)]
487         public ExtendedDN ExtendedDN
488         {
489             get => _extendedDN;
490             set
491             {
492                 if (value < ExtendedDN.None || value > ExtendedDN.Standard)
493                     throw new InvalidEnumArgumentException("value", (int)value, typeof(ExtendedDN));
494 
495                 _extendedDN = value;
496             }
497         }
498 
499         /// <devdoc>
500         /// Gets or sets a value to indicate a directory synchronization search, which returns all changes since a specified
501         /// state.
502         /// </devdoc>
503         [DefaultValue(null)]
504         public DirectorySynchronization DirectorySynchronization
505         {
506             get
507             {
508                 // if user specifies dirsync search preference and search is executed
509                 if (directorySynchronizationSpecified && searchResult != null)
510                 {
511                     _sync.ResetDirectorySynchronizationCookie(searchResult.DirsyncCookie);
512                 }
513                 return _sync;
514             }
515 
516             set
517             {
518                 // specify non-zero pagesize explicitly and also want dirsync
519                 if (value != null)
520                 {
521                     if (PageSize != 0)
522                         throw new ArgumentException(SR.DSBadPageSizeDirsync);
523 
524                     directorySynchronizationSpecified = true;
525                 }
526                 else
527                 // user explicitly sets the value to default one and doesn't want to do dirsync
528                 {
529                     directorySynchronizationSpecified = false;
530                 }
531 
532                 _sync = value;
533             }
534         }
535 
536         /// <devdoc>
537         /// Gets or sets a value to indicate the search should use the LDAP virtual list view (VLV)
538         /// control.
539         /// </devdoc>
540         [DefaultValue(null)]
541         public DirectoryVirtualListView VirtualListView
542         {
543             get
544             {
545                 // if user specifies dirsync search preference and search is executed
546                 if (directoryVirtualListViewSpecified && searchResult != null)
547                 {
548                     DirectoryVirtualListView tempval = searchResult.VLVResponse;
549                     _vlv.Offset = tempval.Offset;
550                     _vlv.ApproximateTotal = tempval.ApproximateTotal;
551                     _vlv.DirectoryVirtualListViewContext = tempval.DirectoryVirtualListViewContext;
552                     if (_vlv.ApproximateTotal != 0)
553                         _vlv.TargetPercentage = (int)((double)_vlv.Offset / _vlv.ApproximateTotal * 100);
554                     else
555                         _vlv.TargetPercentage = 0;
556                 }
557                 return _vlv;
558             }
559             set
560             {
561                 // if user explicitly set CacheResults to true and also want to set VLV
562                 if (value != null)
563                 {
564                     if (_cacheResultsSpecified == true && CacheResults == true)
565                         throw new ArgumentException(SR.DSBadCacheResultsVLV);
566 
567                     directoryVirtualListViewSpecified = true;
568                     // if user does not explicit specify cache results to true and also do vlv, then cache results is default to false
569                     _cacheResults = false;
570                 }
571                 else
572                 // user explicitly sets the value to default one and doesn't want to do vlv
573                 {
574                     directoryVirtualListViewSpecified = false;
575                 }
576 
577                 _vlv = value;
578             }
579         }
580 
581         /// <devdoc>
582         /// Executes the search and returns only the first entry that is found.
583         /// </devdoc>
FindOne()584         public SearchResult FindOne()
585         {
586             DirectorySynchronization tempsync = null;
587             DirectoryVirtualListView tempvlv = null;
588             SearchResult resultEntry = null;
589 
590             SearchResultCollection results = FindAll(false);
591 
592             try
593             {
594                 foreach (SearchResult entry in results)
595                 {
596                     // need to get the dirsync cookie
597                     if (directorySynchronizationSpecified)
598                         tempsync = DirectorySynchronization;
599 
600                     // need to get the vlv response
601                     if (directoryVirtualListViewSpecified)
602                         tempvlv = VirtualListView;
603 
604                     resultEntry = entry;
605                     break;
606                 }
607             }
608             finally
609             {
610                 searchResult = null;
611 
612                 // still need to properly release the resource
613                 results.Dispose();
614             }
615 
616             return resultEntry;
617         }
618 
619         /// <devdoc>
620         /// Executes the search and returns a collection of the entries that are found.
621         /// </devdoc>
FindAll()622         public SearchResultCollection FindAll() => FindAll(true);
623 
FindAll(bool findMoreThanOne)624         private SearchResultCollection FindAll(bool findMoreThanOne)
625         {
626             searchResult = null;
627 
628             DirectoryEntry clonedRoot = null;
629             if (_assertDefaultNamingContext == null)
630             {
631                 clonedRoot = SearchRoot.CloneBrowsable();
632             }
633             else
634             {
635                 clonedRoot = SearchRoot.CloneBrowsable();
636             }
637 
638             UnsafeNativeMethods.IAds adsObject = clonedRoot.AdsObject;
639             if (!(adsObject is UnsafeNativeMethods.IDirectorySearch))
640                 throw new NotSupportedException(SR.Format(SR.DSSearchUnsupported , SearchRoot.Path));
641 
642             // this is a little bit hacky, but we need to perform a bind here, so we make sure the LDAP connection that we hold has more than
643             // one reference count, one by SearchResultCollection object, one by DirectorySearcher object. In this way, when user calls
644             // Dispose on SearchResultCollection, the connection is still there instead of reference count dropping to zero and being closed.
645             // It is especially important for virtuallistview case, in order to reuse the vlv response, the search must be performed on the same ldap connection
646 
647             // only do it when vlv is used
648             if (directoryVirtualListViewSpecified)
649             {
650                 SearchRoot.Bind(true);
651             }
652 
653             UnsafeNativeMethods.IDirectorySearch adsSearch = (UnsafeNativeMethods.IDirectorySearch)adsObject;
654             SetSearchPreferences(adsSearch, findMoreThanOne);
655 
656             string[] properties = null;
657             if (PropertiesToLoad.Count > 0)
658             {
659                 if (!PropertiesToLoad.Contains("ADsPath"))
660                 {
661                     // if we don't get this property, we won't be able to return a list of DirectoryEntry objects!
662                     PropertiesToLoad.Add("ADsPath");
663                 }
664                 properties = new string[PropertiesToLoad.Count];
665                 PropertiesToLoad.CopyTo(properties, 0);
666             }
667 
668             IntPtr resultsHandle;
669             if (properties != null)
670                 adsSearch.ExecuteSearch(Filter, properties, properties.Length, out resultsHandle);
671             else
672             {
673                 adsSearch.ExecuteSearch(Filter, null, -1, out resultsHandle);
674                 properties = new string[0];
675             }
676 
677             SearchResultCollection result = new SearchResultCollection(clonedRoot, resultsHandle, properties, this);
678             searchResult = result;
679             return result;
680         }
681 
SetSearchPreferences(UnsafeNativeMethods.IDirectorySearch adsSearch, bool findMoreThanOne)682         private unsafe void SetSearchPreferences(UnsafeNativeMethods.IDirectorySearch adsSearch, bool findMoreThanOne)
683         {
684             ArrayList prefList = new ArrayList();
685             AdsSearchPreferenceInfo info;
686 
687             // search scope
688             info = new AdsSearchPreferenceInfo();
689             info.dwSearchPref = (int)AdsSearchPreferences.SEARCH_SCOPE;
690             info.vValue = new AdsValueHelper((int)SearchScope).GetStruct();
691             prefList.Add(info);
692 
693             // size limit
694             if (_sizeLimit != 0 || !findMoreThanOne)
695             {
696                 info = new AdsSearchPreferenceInfo();
697                 info.dwSearchPref = (int)AdsSearchPreferences.SIZE_LIMIT;
698                 info.vValue = new AdsValueHelper(findMoreThanOne ? SizeLimit : 1).GetStruct();
699                 prefList.Add(info);
700             }
701 
702             // time limit
703             if (ServerTimeLimit >= new TimeSpan(0))
704             {
705                 info = new AdsSearchPreferenceInfo();
706                 info.dwSearchPref = (int)AdsSearchPreferences.TIME_LIMIT;
707                 info.vValue = new AdsValueHelper((int)ServerTimeLimit.TotalSeconds).GetStruct();
708                 prefList.Add(info);
709             }
710 
711             // propertyNamesOnly
712             info = new AdsSearchPreferenceInfo();
713             info.dwSearchPref = (int)AdsSearchPreferences.ATTRIBTYPES_ONLY;
714             info.vValue = new AdsValueHelper(PropertyNamesOnly).GetStruct();
715             prefList.Add(info);
716 
717             // Timeout
718             if (ClientTimeout >= new TimeSpan(0))
719             {
720                 info = new AdsSearchPreferenceInfo();
721                 info.dwSearchPref = (int)AdsSearchPreferences.TIMEOUT;
722                 info.vValue = new AdsValueHelper((int)ClientTimeout.TotalSeconds).GetStruct();
723                 prefList.Add(info);
724             }
725 
726             // page size
727             if (PageSize != 0)
728             {
729                 info = new AdsSearchPreferenceInfo();
730                 info.dwSearchPref = (int)AdsSearchPreferences.PAGESIZE;
731                 info.vValue = new AdsValueHelper(PageSize).GetStruct();
732                 prefList.Add(info);
733             }
734 
735             // page time limit
736             if (ServerPageTimeLimit >= new TimeSpan(0))
737             {
738                 info = new AdsSearchPreferenceInfo();
739                 info.dwSearchPref = (int)AdsSearchPreferences.PAGED_TIME_LIMIT;
740                 info.vValue = new AdsValueHelper((int)ServerPageTimeLimit.TotalSeconds).GetStruct();
741                 prefList.Add(info);
742             }
743 
744             // chase referrals
745             info = new AdsSearchPreferenceInfo();
746             info.dwSearchPref = (int)AdsSearchPreferences.CHASE_REFERRALS;
747             info.vValue = new AdsValueHelper((int)ReferralChasing).GetStruct();
748             prefList.Add(info);
749 
750             // asynchronous
751             if (Asynchronous == true)
752             {
753                 info = new AdsSearchPreferenceInfo();
754                 info.dwSearchPref = (int)AdsSearchPreferences.ASYNCHRONOUS;
755                 info.vValue = new AdsValueHelper(Asynchronous).GetStruct();
756                 prefList.Add(info);
757             }
758 
759             // tombstone
760             if (Tombstone == true)
761             {
762                 info = new AdsSearchPreferenceInfo();
763                 info.dwSearchPref = (int)AdsSearchPreferences.TOMBSTONE;
764                 info.vValue = new AdsValueHelper(Tombstone).GetStruct();
765                 prefList.Add(info);
766             }
767 
768             // attributescopequery
769             if (_attributeScopeQuerySpecified)
770             {
771                 info = new AdsSearchPreferenceInfo();
772                 info.dwSearchPref = (int)AdsSearchPreferences.ATTRIBUTE_QUERY;
773                 info.vValue = new AdsValueHelper(AttributeScopeQuery, AdsType.ADSTYPE_CASE_IGNORE_STRING).GetStruct();
774                 prefList.Add(info);
775             }
776 
777             // derefalias
778             if (DerefAlias != DereferenceAlias.Never)
779             {
780                 info = new AdsSearchPreferenceInfo();
781                 info.dwSearchPref = (int)AdsSearchPreferences.DEREF_ALIASES;
782                 info.vValue = new AdsValueHelper((int)DerefAlias).GetStruct();
783                 prefList.Add(info);
784             }
785 
786             // securitymask
787             if (SecurityMasks != SecurityMasks.None)
788             {
789                 info = new AdsSearchPreferenceInfo();
790                 info.dwSearchPref = (int)AdsSearchPreferences.SECURITY_MASK;
791                 info.vValue = new AdsValueHelper((int)SecurityMasks).GetStruct();
792                 prefList.Add(info);
793             }
794 
795             // extendeddn
796             if (ExtendedDN != ExtendedDN.None)
797             {
798                 info = new AdsSearchPreferenceInfo();
799                 info.dwSearchPref = (int)AdsSearchPreferences.EXTENDED_DN;
800                 info.vValue = new AdsValueHelper((int)ExtendedDN).GetStruct();
801                 prefList.Add(info);
802             }
803 
804             // dirsync
805             if (directorySynchronizationSpecified)
806             {
807                 info = new AdsSearchPreferenceInfo();
808                 info.dwSearchPref = (int)AdsSearchPreferences.DIRSYNC;
809                 info.vValue = new AdsValueHelper(DirectorySynchronization.GetDirectorySynchronizationCookie(), AdsType.ADSTYPE_PROV_SPECIFIC).GetStruct();
810                 prefList.Add(info);
811 
812                 if (DirectorySynchronization.Option != DirectorySynchronizationOptions.None)
813                 {
814                     info = new AdsSearchPreferenceInfo();
815                     info.dwSearchPref = (int)AdsSearchPreferences.DIRSYNC_FLAG;
816                     info.vValue = new AdsValueHelper((int)DirectorySynchronization.Option).GetStruct();
817                     prefList.Add(info);
818                 }
819             }
820 
821             IntPtr ptrToFree = (IntPtr)0;
822             IntPtr ptrVLVToFree = (IntPtr)0;
823             IntPtr ptrVLVContexToFree = (IntPtr)0;
824 
825             try
826             {
827                 // sort
828                 if (Sort.PropertyName != null && Sort.PropertyName.Length > 0)
829                 {
830                     info = new AdsSearchPreferenceInfo();
831                     info.dwSearchPref = (int)AdsSearchPreferences.SORT_ON;
832                     AdsSortKey sortKey = new AdsSortKey();
833                     sortKey.pszAttrType = Marshal.StringToCoTaskMemUni(Sort.PropertyName);
834                     ptrToFree = sortKey.pszAttrType; // so we can free it later.
835                     sortKey.pszReserved = (IntPtr)0;
836                     sortKey.fReverseOrder = (Sort.Direction == SortDirection.Descending) ? -1 : 0;
837                     byte[] sortKeyBytes = new byte[Marshal.SizeOf(sortKey)];
838                     Marshal.Copy((INTPTR_INTPTRCAST)(&sortKey), sortKeyBytes, 0, sortKeyBytes.Length);
839                     info.vValue = new AdsValueHelper(sortKeyBytes, AdsType.ADSTYPE_PROV_SPECIFIC).GetStruct();
840                     prefList.Add(info);
841                 }
842 
843                 // vlv
844                 if (directoryVirtualListViewSpecified)
845                 {
846                     info = new AdsSearchPreferenceInfo();
847                     info.dwSearchPref = (int)AdsSearchPreferences.VLV;
848                     AdsVLV vlvValue = new AdsVLV();
849                     vlvValue.beforeCount = _vlv.BeforeCount;
850                     vlvValue.afterCount = _vlv.AfterCount;
851                     vlvValue.offset = _vlv.Offset;
852                     //we need to treat the empty string as null here
853                     if (_vlv.Target.Length != 0)
854                         vlvValue.target = Marshal.StringToCoTaskMemUni(_vlv.Target);
855                     else
856                         vlvValue.target = IntPtr.Zero;
857                     ptrVLVToFree = vlvValue.target;
858                     if (_vlv.DirectoryVirtualListViewContext == null)
859                     {
860                         vlvValue.contextIDlength = 0;
861                         vlvValue.contextID = (IntPtr)0;
862                     }
863                     else
864                     {
865                         vlvValue.contextIDlength = _vlv.DirectoryVirtualListViewContext._context.Length;
866                         vlvValue.contextID = Marshal.AllocCoTaskMem(vlvValue.contextIDlength);
867                         ptrVLVContexToFree = vlvValue.contextID;
868                         Marshal.Copy(_vlv.DirectoryVirtualListViewContext._context, 0, vlvValue.contextID, vlvValue.contextIDlength);
869                     }
870                     IntPtr vlvPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(AdsVLV)));
871                     byte[] vlvBytes = new byte[Marshal.SizeOf(vlvValue)];
872                     try
873                     {
874                         Marshal.StructureToPtr(vlvValue, vlvPtr, false);
875                         Marshal.Copy(vlvPtr, vlvBytes, 0, vlvBytes.Length);
876                     }
877                     finally
878                     {
879                         Marshal.FreeHGlobal(vlvPtr);
880                     }
881                     info.vValue = new AdsValueHelper(vlvBytes, AdsType.ADSTYPE_PROV_SPECIFIC).GetStruct();
882                     prefList.Add(info);
883                 }
884 
885                 // cacheResults
886                 if (_cacheResultsSpecified)
887                 {
888                     info = new AdsSearchPreferenceInfo();
889                     info.dwSearchPref = (int)AdsSearchPreferences.CACHE_RESULTS;
890                     info.vValue = new AdsValueHelper(CacheResults).GetStruct();
891                     prefList.Add(info);
892                 }
893 
894                 //
895                 // now make the call
896                 //
897                 AdsSearchPreferenceInfo[] prefs = new AdsSearchPreferenceInfo[prefList.Count];
898                 for (int i = 0; i < prefList.Count; i++)
899                 {
900                     prefs[i] = (AdsSearchPreferenceInfo)prefList[i];
901                 }
902 
903                 DoSetSearchPrefs(adsSearch, prefs);
904             }
905             finally
906             {
907                 if (ptrToFree != (IntPtr)0)
908                     Marshal.FreeCoTaskMem(ptrToFree);
909 
910                 if (ptrVLVToFree != (IntPtr)0)
911                     Marshal.FreeCoTaskMem(ptrVLVToFree);
912 
913                 if (ptrVLVContexToFree != (IntPtr)0)
914                     Marshal.FreeCoTaskMem(ptrVLVContexToFree);
915             }
916         }
917 
DoSetSearchPrefs(UnsafeNativeMethods.IDirectorySearch adsSearch, AdsSearchPreferenceInfo[] prefs)918         private static void DoSetSearchPrefs(UnsafeNativeMethods.IDirectorySearch adsSearch, AdsSearchPreferenceInfo[] prefs)
919         {
920             int structSize = Marshal.SizeOf(typeof(AdsSearchPreferenceInfo));
921             IntPtr ptr = Marshal.AllocHGlobal((IntPtr)(structSize * prefs.Length));
922             try
923             {
924                 IntPtr tempPtr = ptr;
925                 for (int i = 0; i < prefs.Length; i++)
926                 {
927                     Marshal.StructureToPtr(prefs[i], tempPtr, false);
928                     tempPtr = IntPtr.Add(tempPtr, structSize);
929                 }
930 
931                 adsSearch.SetSearchPreference(ptr, prefs.Length);
932 
933                 // Check for the result status for all preferences
934                 tempPtr = ptr;
935                 for (int i = 0; i < prefs.Length; i++)
936                 {
937                     int status = Marshal.ReadInt32(tempPtr, 32);
938                     if (status != 0)
939                     {
940                         int prefIndex = prefs[i].dwSearchPref;
941                         string property = "";
942                         switch (prefIndex)
943                         {
944                             case (int)AdsSearchPreferences.SEARCH_SCOPE:
945                                 property = "SearchScope";
946                                 break;
947                             case (int)AdsSearchPreferences.SIZE_LIMIT:
948                                 property = "SizeLimit";
949                                 break;
950                             case (int)AdsSearchPreferences.TIME_LIMIT:
951                                 property = "ServerTimeLimit";
952                                 break;
953                             case (int)AdsSearchPreferences.ATTRIBTYPES_ONLY:
954                                 property = "PropertyNamesOnly";
955                                 break;
956                             case (int)AdsSearchPreferences.TIMEOUT:
957                                 property = "ClientTimeout";
958                                 break;
959                             case (int)AdsSearchPreferences.PAGESIZE:
960                                 property = "PageSize";
961                                 break;
962                             case (int)AdsSearchPreferences.PAGED_TIME_LIMIT:
963                                 property = "ServerPageTimeLimit";
964                                 break;
965                             case (int)AdsSearchPreferences.CHASE_REFERRALS:
966                                 property = "ReferralChasing";
967                                 break;
968                             case (int)AdsSearchPreferences.SORT_ON:
969                                 property = "Sort";
970                                 break;
971                             case (int)AdsSearchPreferences.CACHE_RESULTS:
972                                 property = "CacheResults";
973                                 break;
974                             case (int)AdsSearchPreferences.ASYNCHRONOUS:
975                                 property = "Asynchronous";
976                                 break;
977                             case (int)AdsSearchPreferences.TOMBSTONE:
978                                 property = "Tombstone";
979                                 break;
980                             case (int)AdsSearchPreferences.ATTRIBUTE_QUERY:
981                                 property = "AttributeScopeQuery";
982                                 break;
983                             case (int)AdsSearchPreferences.DEREF_ALIASES:
984                                 property = "DerefAlias";
985                                 break;
986                             case (int)AdsSearchPreferences.SECURITY_MASK:
987                                 property = "SecurityMasks";
988                                 break;
989                             case (int)AdsSearchPreferences.EXTENDED_DN:
990                                 property = "ExtendedDn";
991                                 break;
992                             case (int)AdsSearchPreferences.DIRSYNC:
993                                 property = "DirectorySynchronization";
994                                 break;
995                             case (int)AdsSearchPreferences.DIRSYNC_FLAG:
996                                 property = "DirectorySynchronizationFlag";
997                                 break;
998                             case (int)AdsSearchPreferences.VLV:
999                                 property = "VirtualListView";
1000                                 break;
1001                         }
1002                         throw new InvalidOperationException(SR.Format(SR.DSSearchPreferencesNotAccepted , property));
1003                     }
1004 
1005                     tempPtr = IntPtr.Add(tempPtr, structSize);
1006                 }
1007             }
1008             finally
1009             {
1010                 Marshal.FreeHGlobal(ptr);
1011             }
1012         }
1013     }
1014 }
1015