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.DirectoryServices.Interop; 8 using System.Globalization; 9 10 namespace System.DirectoryServices 11 { 12 /// <devdoc> 13 /// Contains the properties on a <see cref='System.DirectoryServices.DirectoryEntry'/>. 14 /// </devdoc> 15 public class PropertyCollection : IDictionary 16 { 17 private readonly DirectoryEntry _entry; 18 internal readonly Hashtable valueTable = null; 19 PropertyCollection(DirectoryEntry entry)20 internal PropertyCollection(DirectoryEntry entry) 21 { 22 _entry = entry; 23 Hashtable tempTable = new Hashtable(); 24 valueTable = Hashtable.Synchronized(tempTable); 25 } 26 27 /// <devdoc> 28 /// Gets the property with the given name. 29 /// </devdoc> 30 public PropertyValueCollection this[string propertyName] 31 { 32 get 33 { 34 if (propertyName == null) 35 throw new ArgumentNullException("propertyName"); 36 37 string name = propertyName.ToLower(CultureInfo.InvariantCulture); 38 if (valueTable.Contains(name)) 39 return (PropertyValueCollection)valueTable[name]; 40 else 41 { 42 PropertyValueCollection value = new PropertyValueCollection(_entry, propertyName); 43 valueTable.Add(name, value); 44 return value; 45 } 46 } 47 } 48 49 /// <devdoc> 50 /// Gets the number of properties available on this entry. 51 /// </devdoc> 52 public int Count 53 { 54 get 55 { 56 if (!(_entry.AdsObject is UnsafeNativeMethods.IAdsPropertyList)) 57 throw new NotSupportedException(SR.DSCannotCount); 58 59 _entry.FillCache(""); 60 61 UnsafeNativeMethods.IAdsPropertyList propList = (UnsafeNativeMethods.IAdsPropertyList)_entry.AdsObject; 62 63 return propList.PropertyCount; 64 } 65 } 66 67 /// </devdoc> 68 public ICollection PropertyNames => new KeysCollection(this); 69 70 public ICollection Values => new ValuesCollection(this); 71 Contains(string propertyName)72 public bool Contains(string propertyName) 73 { 74 object var; 75 int unmanagedResult = _entry.AdsObject.GetEx(propertyName, out var); 76 if (unmanagedResult != 0) 77 { 78 // property not found (IIS provider returns 0x80005006, other provides return 0x8000500D). 79 if ((unmanagedResult == unchecked((int)0x8000500D)) || (unmanagedResult == unchecked((int)0x80005006))) 80 { 81 return false; 82 } 83 else 84 { 85 throw COMExceptionHelper.CreateFormattedComException(unmanagedResult); 86 } 87 } 88 89 return true; 90 } 91 92 /// <devdoc> 93 /// Copies the elements of this instance into an <see cref='System.Array'/>, starting at a particular index into the array. 94 /// </devdoc> CopyTo(PropertyValueCollection[] array, int index)95 public void CopyTo(PropertyValueCollection[] array, int index) 96 { 97 ((ICollection)this).CopyTo((Array)array, index); 98 } 99 100 /// <devdoc> 101 /// Returns an enumerator, which can be used to iterate through the collection. 102 /// </devdoc> GetEnumerator()103 public IDictionaryEnumerator GetEnumerator() 104 { 105 if (!(_entry.AdsObject is UnsafeNativeMethods.IAdsPropertyList)) 106 throw new NotSupportedException(SR.DSCannotEmunerate); 107 108 // Once an object has been used for an enumerator once, it can't be used again, because it only 109 // maintains a single cursor. Re-bind to the ADSI object to get a new instance. 110 // That's why we must clone entry here. It will be automatically disposed inside Enumerator. 111 DirectoryEntry entryToUse = _entry.CloneBrowsable(); 112 entryToUse.FillCache(""); 113 114 UnsafeNativeMethods.IAdsPropertyList propList = (UnsafeNativeMethods.IAdsPropertyList)entryToUse.AdsObject; 115 116 entryToUse.propertiesAlreadyEnumerated = true; 117 return new PropertyEnumerator(_entry, entryToUse); 118 } 119 120 object IDictionary.this[object key] 121 { 122 get => this[(string)key]; 123 set => throw new NotSupportedException(SR.DSPropertySetSupported); 124 } 125 126 bool IDictionary.IsFixedSize => true; 127 128 bool IDictionary.IsReadOnly => true; 129 130 ICollection IDictionary.Keys => new KeysCollection(this); 131 IDictionary.Add(object key, object value)132 void IDictionary.Add(object key, object value) 133 { 134 throw new NotSupportedException(SR.DSAddNotSupported); 135 } 136 IDictionary.Clear()137 void IDictionary.Clear() 138 { 139 throw new NotSupportedException(SR.DSClearNotSupported); 140 } 141 142 bool IDictionary.Contains(object value) => Contains((string)value); 143 IDictionary.Remove(object key)144 void IDictionary.Remove(object key) 145 { 146 throw new NotSupportedException(SR.DSRemoveNotSupported); 147 } 148 IEnumerable.GetEnumerator()149 IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 150 151 bool ICollection.IsSynchronized => false; 152 153 object ICollection.SyncRoot => this; 154 ICollection.CopyTo(Array array, Int32 index)155 void ICollection.CopyTo(Array array, Int32 index) 156 { 157 if (array == null) 158 throw new ArgumentNullException("array"); 159 160 if (array.Rank != 1) 161 throw new ArgumentException(SR.OnlyAllowSingleDimension, "array"); 162 163 if (index < 0) 164 throw new ArgumentOutOfRangeException(SR.LessThanZero, "index"); 165 166 if (((index + Count) > array.Length) || ((index + Count) < index)) 167 throw new ArgumentException(SR.DestinationArrayNotLargeEnough); 168 169 foreach (PropertyValueCollection value in this) 170 { 171 array.SetValue(value, index); 172 index++; 173 } 174 } 175 176 private class PropertyEnumerator : IDictionaryEnumerator, IDisposable 177 { 178 private DirectoryEntry _entry; // clone (to be disposed) 179 private DirectoryEntry _parentEntry; // original entry to pass to PropertyValueCollection 180 private string _currentPropName = null; 181 PropertyEnumerator(DirectoryEntry parent, DirectoryEntry clone)182 public PropertyEnumerator(DirectoryEntry parent, DirectoryEntry clone) 183 { 184 _entry = clone; 185 _parentEntry = parent; 186 } 187 ~PropertyEnumerator()188 ~PropertyEnumerator() => Dispose(true); 189 Dispose()190 public void Dispose() 191 { 192 Dispose(true); 193 GC.SuppressFinalize(this); 194 } 195 Dispose(bool disposing)196 protected virtual void Dispose(bool disposing) 197 { 198 if (disposing) 199 { 200 _entry.Dispose(); 201 } 202 } 203 204 public object Current => Entry.Value; 205 206 public DictionaryEntry Entry 207 { 208 get 209 { 210 if (_currentPropName == null) 211 throw new InvalidOperationException(SR.DSNoCurrentProperty); 212 213 return new DictionaryEntry(_currentPropName, new PropertyValueCollection(_parentEntry, _currentPropName)); 214 } 215 } 216 217 public object Key => Entry.Key; 218 219 public object Value => Entry.Value; 220 MoveNext()221 public bool MoveNext() 222 { 223 object prop; 224 int hr = 0; 225 try 226 { 227 hr = ((UnsafeNativeMethods.IAdsPropertyList)_entry.AdsObject).Next(out prop); 228 } 229 catch (COMException e) 230 { 231 hr = e.ErrorCode; 232 prop = null; 233 } 234 if (hr == 0) 235 { 236 if (prop != null) 237 _currentPropName = ((UnsafeNativeMethods.IAdsPropertyEntry)prop).Name; 238 else 239 _currentPropName = null; 240 241 return true; 242 } 243 else 244 { 245 _currentPropName = null; 246 return false; 247 } 248 } 249 Reset()250 public void Reset() 251 { 252 ((UnsafeNativeMethods.IAdsPropertyList)_entry.AdsObject).Reset(); 253 _currentPropName = null; 254 } 255 } 256 257 private class ValuesCollection : ICollection 258 { 259 protected PropertyCollection props; 260 ValuesCollection(PropertyCollection props)261 public ValuesCollection(PropertyCollection props) 262 { 263 this.props = props; 264 } 265 266 public int Count => props.Count; 267 268 public bool IsReadOnly => true; 269 270 public bool IsSynchronized => false; 271 272 public object SyncRoot => ((ICollection)props).SyncRoot; 273 CopyTo(Array array, int index)274 public void CopyTo(Array array, int index) 275 { 276 foreach (object value in this) 277 array.SetValue(value, index++); 278 } 279 GetEnumerator()280 public virtual IEnumerator GetEnumerator() => new ValuesEnumerator(props); 281 } 282 283 private class KeysCollection : ValuesCollection 284 { KeysCollection(PropertyCollection props)285 public KeysCollection(PropertyCollection props) : base(props) 286 { 287 } 288 GetEnumerator()289 public override IEnumerator GetEnumerator() 290 { 291 props._entry.FillCache(""); 292 return new KeysEnumerator(props); 293 } 294 } 295 296 private class ValuesEnumerator : IEnumerator 297 { 298 private int _currentIndex = -1; 299 protected PropertyCollection propCollection; 300 ValuesEnumerator(PropertyCollection propCollection)301 public ValuesEnumerator(PropertyCollection propCollection) 302 { 303 this.propCollection = propCollection; 304 } 305 306 protected int CurrentIndex 307 { 308 get 309 { 310 if (_currentIndex == -1) 311 throw new InvalidOperationException(SR.DSNoCurrentValue); 312 return _currentIndex; 313 } 314 } 315 316 public virtual object Current 317 { 318 get 319 { 320 UnsafeNativeMethods.IAdsPropertyList propList = (UnsafeNativeMethods.IAdsPropertyList)propCollection._entry.AdsObject; 321 return propCollection[((UnsafeNativeMethods.IAdsPropertyEntry)propList.Item(CurrentIndex)).Name]; 322 } 323 } 324 MoveNext()325 public bool MoveNext() 326 { 327 _currentIndex++; 328 if (_currentIndex >= propCollection.Count) 329 { 330 _currentIndex = -1; 331 return false; 332 } 333 else 334 return true; 335 } 336 Reset()337 public void Reset() => _currentIndex = -1; 338 } 339 340 private class KeysEnumerator : ValuesEnumerator 341 { KeysEnumerator(PropertyCollection collection)342 public KeysEnumerator(PropertyCollection collection) : base(collection) 343 { 344 } 345 346 public override object Current 347 { 348 get 349 { 350 UnsafeNativeMethods.IAdsPropertyList propList = (UnsafeNativeMethods.IAdsPropertyList)propCollection._entry.AdsObject; 351 352 return ((UnsafeNativeMethods.IAdsPropertyEntry)propList.Item(CurrentIndex)).Name; 353 } 354 } 355 } 356 } 357 } 358