MegaBites-AI/Windows-powershell
0308
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Generic;6using System.DirectoryServices;7using System.Reflection;8using System.Runtime.InteropServices;9using System.Text;10 11using Microsoft.PowerShell;12 13#pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings14 15namespace System.Management.Automation16{17 /// <summary>18 /// Deals with DirectoryEntry objects.19 /// </summary>20 internal class DirectoryEntryAdapter : DotNetAdapter21 {22 #region private data23 // DirectoryEntry(DE) adapter needs dotnet adapter as DE adapter24 // don't know the underlying native adsi object's method metadata.25 // In the MethodInvoke() call, this adapter first calls26 // native adsi object's method, if there is a failure it calls27 // dotnet method (if one available).28 // This ensures dotnet methods are available on the adapted object.29 private static readonly DotNetAdapter s_dotNetAdapter = new DotNetAdapter();30 #endregion31 32 #region member33 34 internal override bool CanSiteBinderOptimize(MemberTypes typeToOperateOn)35 {36 return false;37 }38 39 /// <summary>40 /// Returns null if memberName is not a member in the adapter or41 /// the corresponding PSMemberInfo.42 /// </summary>43 /// <param name="obj">Object to retrieve the PSMemberInfo from.</param>44 /// <param name="memberName">Name of the member to be retrieved.</param>45 /// <returns>The PSMemberInfo corresponding to memberName from obj.</returns>46 protected override T GetMember<T>(object obj, string memberName)47 {48 PSProperty property;49 DirectoryEntry entry = (DirectoryEntry)obj;50 51 // This line must precede InvokeGet. See the comment below.52 PropertyValueCollection collection = entry.Properties[memberName];53 object valueToTake = collection;54 55#pragma warning disable 5650056 // Even for the cases where propertyName does not exist57 // entry.Properties[propertyName] still returns a PropertyValueCollection.58 // The non schema way to check for a non existing property is to call entry.InvokeGet59 // and catch an eventual exception.60 // Specifically for "LDAP://RootDse" there are some cases where calling61 // InvokeGet will throw COMException for existing properties like defaultNamingContext.62 // Having a call to entry.Properties[propertyName] fixes the RootDse problem.63 // Calling entry.RefreshCache() also fixes the RootDse problem.64 try65 {66 object invokeGetValue = entry.InvokeGet(memberName);67 // if entry.Properties[memberName] returns empty value and invokeGet non-empty68 // value..take invokeGet's value. This will fix bug Windows Bug 121188.69 if ((collection == null) || ((collection.Value == null) && (invokeGetValue != null)))70 {71 valueToTake = invokeGetValue;72 }73 74 property = new PSProperty(collection.PropertyName, this, obj, valueToTake);75 }76 catch (Exception)77 {78 property = null;79 }80#pragma warning restore 5650081 82 if (valueToTake == null)83 {84 property = null;85 }86 87 if (typeof(T).IsAssignableFrom(typeof(PSProperty)) && property != null)88 {89 return property as T;90 }91 92 if (typeof(T).IsAssignableFrom(typeof(PSMethod)))93 {94 if (property == null)95 {96 #region Readme97 // we are unable to find a native adsi object property.98 // The next option is to find method. Unfortunately DirectoryEntry99 // doesn't provide us a way to access underlying native object's method100 // metadata.101 // Adapter engine resolve's members in the following steps:102 // 1. Extended members -> 2. Adapted members -> 3. Dotnet members103 // We cannot say from DirectoryEntryAdapter if a method with name "memberName"104 // is available. So check if a DotNet property with the same name is available105 // If yes, return null from the adapted view and let adapter engine106 // take care of DotNet member resolution. If not, assume memberName method107 // is available on native adsi object.108 // In case of collisions between Dotnet Property and adsi native object methods,109 // Dotnet wins. Looking through IADs com interfaces there doesn't appear110 // to be a collision like this.111 // Powershell Parser will call only GetMember<PSMemberInfo>, so here112 // we cannot distinguish if the caller is looking for a property or a113 // method.114 #endregion115 116 if (base.GetDotNetProperty<T>(obj, memberName) == null)117 {118 return new PSMethod(memberName, this, obj, null) as T;119 }120 }121 }122 123 return null;124 }125 126 /// <summary>127 /// Retrieves all the members available in the object.128 /// The adapter implementation is encouraged to cache all properties/methods available129 /// in the first call to GetMember and GetMembers so that subsequent130 /// calls can use the cache.131 /// In the case of the .NET adapter that would be a cache from the .NET type to132 /// the public properties and fields available in that type.133 /// In the case of the DirectoryEntry adapter, this could be a cache of the objectClass134 /// to the properties available in it.135 /// </summary>136 /// <param name="obj">Object to get all the member information from.</param>137 /// <returns>All members in obj.</returns>138 protected override PSMemberInfoInternalCollection<T> GetMembers<T>(object obj)139 {140 DirectoryEntry entry = (DirectoryEntry)obj;141 142 PSMemberInfoInternalCollection<T> members = new PSMemberInfoInternalCollection<T>();143 144 if (entry.Properties == null || entry.Properties.PropertyNames == null)145 {146 return null;147 }148 149 int countOfProperties = 0;150 151#pragma warning disable 56500152 try153 {154 countOfProperties = entry.Properties.PropertyNames.Count;155 }156 catch (Exception) // swallow all non-severe exceptions157 {158 }159#pragma warning restore 56500160 161 if (countOfProperties > 0)162 {163 foreach (PropertyValueCollection property in entry.Properties)164 {165 members.Add(new PSProperty(property.PropertyName, this, obj, property) as T);166 }167 }168 169 return members;170 }171 172 #endregion member173 174 #region property175 176 /// <summary>177 /// Returns the value from a property coming from a previous call to GetMember.178 /// </summary>179 /// <param name="property">PSProperty coming from a previous call to GetMember.</param>180 /// <returns>The value of the property.</returns>181 protected override object PropertyGet(PSProperty property)182 {183 return property.adapterData;184 }185 186 /// <summary>187 /// Sets the value of a property coming from a previous call to GetMember.188 /// </summary>189 /// <param name="property">PSProperty coming from a previous call to GetMember.</param>190 /// <param name="setValue">Value to set the property with.</param>191 /// <param name="convertIfPossible">Instructs the adapter to convert before setting, if the adapter supports conversion.</param>192 protected override void PropertySet(PSProperty property, object setValue, bool convertIfPossible)193 {194 PropertyValueCollection values = property.adapterData as PropertyValueCollection;195 196 if (values != null)197 {198 // This means GetMember returned PropertyValueCollection199 try200 {201 values.Clear();202 }203 catch (System.Runtime.InteropServices.COMException e)204 {205 if (e.ErrorCode != unchecked((int)0x80004005) || (setValue == null))206 // When clear is called, DirectoryEntry calls PutEx on AD object with Clear option and Null Value207 // WinNT provider throws E_FAIL when null value is specified though actually ADS_PROPERTY_CLEAR option is used,208 // we need to catch this exception here.209 // But at the same time we don't want to catch the exception if user explicitly sets the value to null.210 throw;211 }212 213 IEnumerable enumValues = LanguagePrimitives.GetEnumerable(setValue);214 215 if (enumValues == null)216 {217 values.Add(setValue);218 }219 else220 {221 foreach (object objValue in enumValues)222 {223 values.Add(objValue);224 }225 }226 }227 else228 {229 // This means GetMember returned the value from InvokeGet..So set the value using InvokeSet.230 DirectoryEntry entry = (DirectoryEntry)property.baseObject;231 Diagnostics.Assert(entry != null, "Object should be of type DirectoryEntry in DirectoryEntry adapter.");232 233 List<object> setValues = new List<object>();234 IEnumerable enumValues = LanguagePrimitives.GetEnumerable(setValue);235 236 if (enumValues == null)237 {238 setValues.Add(setValue);239 }240 else241 {242 foreach (object objValue in enumValues)243 {244 setValues.Add(objValue);245 }246 }247 248 entry.InvokeSet(property.name, setValues.ToArray());249 }250 251 return;252 }253 254 /// <summary>255 /// Returns true if the property is settable.256 /// </summary>257 /// <param name="property">Property to check.</param>258 /// <returns>True if the property is settable.</returns>259 protected override bool PropertyIsSettable(PSProperty property)260 {261 return true;262 }263 264 /// <summary>265 /// Returns true if the property is gettable.266 /// </summary>267 /// <param name="property">Property to check.</param>268 /// <returns>True if the property is gettable.</returns>269 protected override bool PropertyIsGettable(PSProperty property)270 {271 return true;272 }273 274 /// <summary>275 /// Returns the name of the type corresponding to the property's value.276 /// </summary>277 /// <param name="property">PSProperty obtained in a previous GetMember.</param>278 /// <param name="forDisplay">True if the result is for display purposes only.</param>279 /// <returns>The name of the type corresponding to the member.</returns>280 protected override string PropertyType(PSProperty property, bool forDisplay)281 {282 object value = null;283 try284 {285 value = BasePropertyGet(property);286 }287 catch (GetValueException)288 {289 }290 291 var type = value == null ? typeof(object) : value.GetType();292 return forDisplay ? ToStringCodeMethods.Type(type) : type.FullName;293 }294 295 #endregion property296 297 #region method298 299 protected override object MethodInvoke(PSMethod method, PSMethodInvocationConstraints invocationConstraints, object[] arguments)300 {301 return this.MethodInvoke(method, arguments);302 }303 304 /// <summary>305 /// Called after a non null return from GetMember to try to call306 /// the method with the arguments.307 /// </summary>308 /// <param name="method">The non empty return from GetMethods.</param>309 /// <param name="arguments">The arguments to use.</param>310 /// <returns>The return value for the method.</returns>311 protected override object MethodInvoke(PSMethod method, object[] arguments)312 {313 ParameterInformation[] parameters = new ParameterInformation[arguments.Length];314 315 for (int i = 0; i < arguments.Length; i++)316 {317 parameters[i] = new ParameterInformation(typeof(object), false, null, false);318 }319 320 MethodInformation[] methodInformation = new MethodInformation[1];321 methodInformation[0] = new MethodInformation(false, false, parameters);322 323 object[] newArguments;324 GetBestMethodAndArguments(method.Name, methodInformation, arguments, out newArguments);325 326 DirectoryEntry entry = (DirectoryEntry)method.baseObject;327 328 // First try to invoke method on the native adsi object. If the method329 // call fails, try to invoke dotnet method with same name, if one available.330 // This will ensure dotnet methods are exposed for DE objects.331 // The problem is in GetMember<T>(), DE adapter cannot check if a requested332 // method is available as it doesn't have access to native adsi object's333 // method metadata. So GetMember<T> returns PSMethod assuming a method334 // is available. This behavior will never give a chance to dotnet adapter335 // to resolve method call. So the DE adapter owns calling dotnet method336 // if one available.337 Exception exception;338 try339 {340 return entry.Invoke(method.Name, newArguments);341 }342 catch (DirectoryServicesCOMException dse)343 {344 exception = dse;345 }346 catch (TargetInvocationException tie)347 {348 exception = tie;349 }350 catch (COMException ce)351 {352 exception = ce;353 }354 355 // this code is reached only on exception356 // check if there is a dotnet method, invoke the dotnet method if available357 PSMethod dotNetmethod = s_dotNetAdapter.GetDotNetMethod<PSMethod>(method.baseObject, method.name);358 if (dotNetmethod != null)359 {360 return dotNetmethod.Invoke(arguments);361 }362 // else363 throw exception;364 }365 366 /// <summary>367 /// Returns the string representation of the method in the object.368 /// </summary>369 /// <returns>The string representation of the method in the object.</returns>370 protected override string MethodToString(PSMethod method)371 {372 StringBuilder returnValue = new StringBuilder();373 foreach (string overload in MethodDefinitions(method))374 {375 returnValue.Append(overload);376 returnValue.Append(", ");377 }378 379 returnValue.Remove(returnValue.Length - 2, 2);380 return returnValue.ToString();381 }382 383 #endregion method384 }385}386 