mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
386 lines
15 KiB
C#
386 lines
15 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.DirectoryServices;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
|
|
using Microsoft.PowerShell;
|
|
|
|
#pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings
|
|
|
|
namespace System.Management.Automation
|
|
{
|
|
/// <summary>
|
|
/// Deals with DirectoryEntry objects.
|
|
/// </summary>
|
|
internal class DirectoryEntryAdapter : DotNetAdapter
|
|
{
|
|
#region private data
|
|
// DirectoryEntry(DE) adapter needs dotnet adapter as DE adapter
|
|
// don't know the underlying native adsi object's method metadata.
|
|
// In the MethodInvoke() call, this adapter first calls
|
|
// native adsi object's method, if there is a failure it calls
|
|
// dotnet method (if one available).
|
|
// This ensures dotnet methods are available on the adapted object.
|
|
private static readonly DotNetAdapter s_dotNetAdapter = new DotNetAdapter();
|
|
#endregion
|
|
|
|
#region member
|
|
|
|
internal override bool CanSiteBinderOptimize(MemberTypes typeToOperateOn)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns null if memberName is not a member in the adapter or
|
|
/// the corresponding PSMemberInfo.
|
|
/// </summary>
|
|
/// <param name="obj">Object to retrieve the PSMemberInfo from.</param>
|
|
/// <param name="memberName">Name of the member to be retrieved.</param>
|
|
/// <returns>The PSMemberInfo corresponding to memberName from obj.</returns>
|
|
protected override T GetMember<T>(object obj, string memberName)
|
|
{
|
|
PSProperty property;
|
|
DirectoryEntry entry = (DirectoryEntry)obj;
|
|
|
|
// This line must precede InvokeGet. See the comment below.
|
|
PropertyValueCollection collection = entry.Properties[memberName];
|
|
object valueToTake = collection;
|
|
|
|
#pragma warning disable 56500
|
|
// Even for the cases where propertyName does not exist
|
|
// entry.Properties[propertyName] still returns a PropertyValueCollection.
|
|
// The non schema way to check for a non existing property is to call entry.InvokeGet
|
|
// and catch an eventual exception.
|
|
// Specifically for "LDAP://RootDse" there are some cases where calling
|
|
// InvokeGet will throw COMException for existing properties like defaultNamingContext.
|
|
// Having a call to entry.Properties[propertyName] fixes the RootDse problem.
|
|
// Calling entry.RefreshCache() also fixes the RootDse problem.
|
|
try
|
|
{
|
|
object invokeGetValue = entry.InvokeGet(memberName);
|
|
// if entry.Properties[memberName] returns empty value and invokeGet non-empty
|
|
// value..take invokeGet's value. This will fix bug Windows Bug 121188.
|
|
if ((collection == null) || ((collection.Value == null) && (invokeGetValue != null)))
|
|
{
|
|
valueToTake = invokeGetValue;
|
|
}
|
|
|
|
property = new PSProperty(collection.PropertyName, this, obj, valueToTake);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
property = null;
|
|
}
|
|
#pragma warning restore 56500
|
|
|
|
if (valueToTake == null)
|
|
{
|
|
property = null;
|
|
}
|
|
|
|
if (typeof(T).IsAssignableFrom(typeof(PSProperty)) && property != null)
|
|
{
|
|
return property as T;
|
|
}
|
|
|
|
if (typeof(T).IsAssignableFrom(typeof(PSMethod)))
|
|
{
|
|
if (property == null)
|
|
{
|
|
#region Readme
|
|
// we are unable to find a native adsi object property.
|
|
// The next option is to find method. Unfortunately DirectoryEntry
|
|
// doesn't provide us a way to access underlying native object's method
|
|
// metadata.
|
|
// Adapter engine resolve's members in the following steps:
|
|
// 1. Extended members -> 2. Adapted members -> 3. Dotnet members
|
|
// We cannot say from DirectoryEntryAdapter if a method with name "memberName"
|
|
// is available. So check if a DotNet property with the same name is available
|
|
// If yes, return null from the adapted view and let adapter engine
|
|
// take care of DotNet member resolution. If not, assume memberName method
|
|
// is available on native adsi object.
|
|
// In case of collisions between Dotnet Property and adsi native object methods,
|
|
// Dotnet wins. Looking through IADs com interfaces there doesn't appear
|
|
// to be a collision like this.
|
|
// Powershell Parser will call only GetMember<PSMemberInfo>, so here
|
|
// we cannot distinguish if the caller is looking for a property or a
|
|
// method.
|
|
#endregion
|
|
|
|
if (base.GetDotNetProperty<T>(obj, memberName) == null)
|
|
{
|
|
return new PSMethod(memberName, this, obj, null) as T;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves all the members available in the object.
|
|
/// The adapter implementation is encouraged to cache all properties/methods available
|
|
/// in the first call to GetMember and GetMembers so that subsequent
|
|
/// calls can use the cache.
|
|
/// In the case of the .NET adapter that would be a cache from the .NET type to
|
|
/// the public properties and fields available in that type.
|
|
/// In the case of the DirectoryEntry adapter, this could be a cache of the objectClass
|
|
/// to the properties available in it.
|
|
/// </summary>
|
|
/// <param name="obj">Object to get all the member information from.</param>
|
|
/// <returns>All members in obj.</returns>
|
|
protected override PSMemberInfoInternalCollection<T> GetMembers<T>(object obj)
|
|
{
|
|
DirectoryEntry entry = (DirectoryEntry)obj;
|
|
|
|
PSMemberInfoInternalCollection<T> members = new PSMemberInfoInternalCollection<T>();
|
|
|
|
if (entry.Properties == null || entry.Properties.PropertyNames == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
int countOfProperties = 0;
|
|
|
|
#pragma warning disable 56500
|
|
try
|
|
{
|
|
countOfProperties = entry.Properties.PropertyNames.Count;
|
|
}
|
|
catch (Exception) // swallow all non-severe exceptions
|
|
{
|
|
}
|
|
#pragma warning restore 56500
|
|
|
|
if (countOfProperties > 0)
|
|
{
|
|
foreach (PropertyValueCollection property in entry.Properties)
|
|
{
|
|
members.Add(new PSProperty(property.PropertyName, this, obj, property) as T);
|
|
}
|
|
}
|
|
|
|
return members;
|
|
}
|
|
|
|
#endregion member
|
|
|
|
#region property
|
|
|
|
/// <summary>
|
|
/// Returns the value from a property coming from a previous call to GetMember.
|
|
/// </summary>
|
|
/// <param name="property">PSProperty coming from a previous call to GetMember.</param>
|
|
/// <returns>The value of the property.</returns>
|
|
protected override object PropertyGet(PSProperty property)
|
|
{
|
|
return property.adapterData;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the value of a property coming from a previous call to GetMember.
|
|
/// </summary>
|
|
/// <param name="property">PSProperty coming from a previous call to GetMember.</param>
|
|
/// <param name="setValue">Value to set the property with.</param>
|
|
/// <param name="convertIfPossible">Instructs the adapter to convert before setting, if the adapter supports conversion.</param>
|
|
protected override void PropertySet(PSProperty property, object setValue, bool convertIfPossible)
|
|
{
|
|
PropertyValueCollection values = property.adapterData as PropertyValueCollection;
|
|
|
|
if (values != null)
|
|
{
|
|
// This means GetMember returned PropertyValueCollection
|
|
try
|
|
{
|
|
values.Clear();
|
|
}
|
|
catch (System.Runtime.InteropServices.COMException e)
|
|
{
|
|
if (e.ErrorCode != unchecked((int)0x80004005) || (setValue == null))
|
|
// When clear is called, DirectoryEntry calls PutEx on AD object with Clear option and Null Value
|
|
// WinNT provider throws E_FAIL when null value is specified though actually ADS_PROPERTY_CLEAR option is used,
|
|
// we need to catch this exception here.
|
|
// But at the same time we don't want to catch the exception if user explicitly sets the value to null.
|
|
throw;
|
|
}
|
|
|
|
IEnumerable enumValues = LanguagePrimitives.GetEnumerable(setValue);
|
|
|
|
if (enumValues == null)
|
|
{
|
|
values.Add(setValue);
|
|
}
|
|
else
|
|
{
|
|
foreach (object objValue in enumValues)
|
|
{
|
|
values.Add(objValue);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This means GetMember returned the value from InvokeGet..So set the value using InvokeSet.
|
|
DirectoryEntry entry = (DirectoryEntry)property.baseObject;
|
|
Diagnostics.Assert(entry != null, "Object should be of type DirectoryEntry in DirectoryEntry adapter.");
|
|
|
|
List<object> setValues = new List<object>();
|
|
IEnumerable enumValues = LanguagePrimitives.GetEnumerable(setValue);
|
|
|
|
if (enumValues == null)
|
|
{
|
|
setValues.Add(setValue);
|
|
}
|
|
else
|
|
{
|
|
foreach (object objValue in enumValues)
|
|
{
|
|
setValues.Add(objValue);
|
|
}
|
|
}
|
|
|
|
entry.InvokeSet(property.name, setValues.ToArray());
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the property is settable.
|
|
/// </summary>
|
|
/// <param name="property">Property to check.</param>
|
|
/// <returns>True if the property is settable.</returns>
|
|
protected override bool PropertyIsSettable(PSProperty property)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the property is gettable.
|
|
/// </summary>
|
|
/// <param name="property">Property to check.</param>
|
|
/// <returns>True if the property is gettable.</returns>
|
|
protected override bool PropertyIsGettable(PSProperty property)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the name of the type corresponding to the property's value.
|
|
/// </summary>
|
|
/// <param name="property">PSProperty obtained in a previous GetMember.</param>
|
|
/// <param name="forDisplay">True if the result is for display purposes only.</param>
|
|
/// <returns>The name of the type corresponding to the member.</returns>
|
|
protected override string PropertyType(PSProperty property, bool forDisplay)
|
|
{
|
|
object value = null;
|
|
try
|
|
{
|
|
value = BasePropertyGet(property);
|
|
}
|
|
catch (GetValueException)
|
|
{
|
|
}
|
|
|
|
var type = value == null ? typeof(object) : value.GetType();
|
|
return forDisplay ? ToStringCodeMethods.Type(type) : type.FullName;
|
|
}
|
|
|
|
#endregion property
|
|
|
|
#region method
|
|
|
|
protected override object MethodInvoke(PSMethod method, PSMethodInvocationConstraints invocationConstraints, object[] arguments)
|
|
{
|
|
return this.MethodInvoke(method, arguments);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called after a non null return from GetMember to try to call
|
|
/// the method with the arguments.
|
|
/// </summary>
|
|
/// <param name="method">The non empty return from GetMethods.</param>
|
|
/// <param name="arguments">The arguments to use.</param>
|
|
/// <returns>The return value for the method.</returns>
|
|
protected override object MethodInvoke(PSMethod method, object[] arguments)
|
|
{
|
|
ParameterInformation[] parameters = new ParameterInformation[arguments.Length];
|
|
|
|
for (int i = 0; i < arguments.Length; i++)
|
|
{
|
|
parameters[i] = new ParameterInformation(typeof(object), false, null, false);
|
|
}
|
|
|
|
MethodInformation[] methodInformation = new MethodInformation[1];
|
|
methodInformation[0] = new MethodInformation(false, false, parameters);
|
|
|
|
object[] newArguments;
|
|
GetBestMethodAndArguments(method.Name, methodInformation, arguments, out newArguments);
|
|
|
|
DirectoryEntry entry = (DirectoryEntry)method.baseObject;
|
|
|
|
// First try to invoke method on the native adsi object. If the method
|
|
// call fails, try to invoke dotnet method with same name, if one available.
|
|
// This will ensure dotnet methods are exposed for DE objects.
|
|
// The problem is in GetMember<T>(), DE adapter cannot check if a requested
|
|
// method is available as it doesn't have access to native adsi object's
|
|
// method metadata. So GetMember<T> returns PSMethod assuming a method
|
|
// is available. This behavior will never give a chance to dotnet adapter
|
|
// to resolve method call. So the DE adapter owns calling dotnet method
|
|
// if one available.
|
|
Exception exception;
|
|
try
|
|
{
|
|
return entry.Invoke(method.Name, newArguments);
|
|
}
|
|
catch (DirectoryServicesCOMException dse)
|
|
{
|
|
exception = dse;
|
|
}
|
|
catch (TargetInvocationException tie)
|
|
{
|
|
exception = tie;
|
|
}
|
|
catch (COMException ce)
|
|
{
|
|
exception = ce;
|
|
}
|
|
|
|
// this code is reached only on exception
|
|
// check if there is a dotnet method, invoke the dotnet method if available
|
|
PSMethod dotNetmethod = s_dotNetAdapter.GetDotNetMethod<PSMethod>(method.baseObject, method.name);
|
|
if (dotNetmethod != null)
|
|
{
|
|
return dotNetmethod.Invoke(arguments);
|
|
}
|
|
// else
|
|
throw exception;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the string representation of the method in the object.
|
|
/// </summary>
|
|
/// <returns>The string representation of the method in the object.</returns>
|
|
protected override string MethodToString(PSMethod method)
|
|
{
|
|
StringBuilder returnValue = new StringBuilder();
|
|
foreach (string overload in MethodDefinitions(method))
|
|
{
|
|
returnValue.Append(overload);
|
|
returnValue.Append(", ");
|
|
}
|
|
|
|
returnValue.Remove(returnValue.Length - 2, 2);
|
|
return returnValue.ToString();
|
|
}
|
|
|
|
#endregion method
|
|
}
|
|
}
|