// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using Microsoft.Management.Infrastructure;
#if !UNIX
using System.DirectoryServices;
using System.Management;
#endif
#pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings
#pragma warning disable 56500
namespace System.Management.Automation
{
///
/// Wraps an object providing alternate views of the available members
/// and ways to extend them. Members can be methods, properties,
/// parameterized properties, etc.
///
///
/// It is permitted to subclass
/// but there is no established scenario for doing this, nor has it been tested.
///
[TypeDescriptionProvider(typeof(PSObjectTypeDescriptionProvider))]
public class PSObject : IFormattable, IComparable, ISerializable, IDynamicMetaObjectProvider
{
#region constructors
#region private to the constructors
internal TypeTable GetTypeTable()
{
if (_typeTable != null && _typeTable.TryGetTarget(out TypeTable typeTable))
{
return typeTable;
}
ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();
return context?.TypeTable;
}
internal static T TypeTableGetMemberDelegate(PSObject msjObj, string name) where T : PSMemberInfo
{
TypeTable table = msjObj.GetTypeTable();
return TypeTableGetMemberDelegate(msjObj, table, name);
}
private static T TypeTableGetMemberDelegate(PSObject msjObj, TypeTable typeTableToUse, string name) where T : PSMemberInfo
{
if (typeTableToUse == null)
{
return null;
}
PSMemberInfoInternalCollection allMembers = typeTableToUse.GetMembers(msjObj.InternalTypeNames);
PSMemberInfo member = allMembers[name];
if (member == null)
{
PSObject.MemberResolution.WriteLine("\"{0}\" NOT present in type table.", name);
return null;
}
if (member is T memberAsT)
{
PSObject.MemberResolution.WriteLine("\"{0}\" present in type table.", name);
return memberAsT;
}
PSObject.MemberResolution.WriteLine("\"{0}\" from types table ignored because it has type {1} instead of {2}.",
name, member.GetType(), typeof(T));
return null;
}
internal static PSMemberInfoInternalCollection TypeTableGetMembersDelegate(PSObject msjObj) where T : PSMemberInfo
{
TypeTable table = msjObj.GetTypeTable();
return TypeTableGetMembersDelegate(msjObj, table);
}
internal static PSMemberInfoInternalCollection TypeTableGetMembersDelegate(PSObject msjObj, TypeTable typeTableToUse) where T : PSMemberInfo
{
if (typeTableToUse == null)
{
return new PSMemberInfoInternalCollection();
}
PSMemberInfoInternalCollection members = typeTableToUse.GetMembers(msjObj.InternalTypeNames);
PSObject.MemberResolution.WriteLine("Type table members: {0}.", members.Count);
return members;
}
internal static T TypeTableGetFirstMemberOrDefaultDelegate(PSObject msjObj, MemberNamePredicate predicate) where T : PSMemberInfo
{
TypeTable table = msjObj.GetTypeTable();
return TypeTableGetFirstOrDefaultMemberDelegate(msjObj, table, predicate);
}
internal static T TypeTableGetFirstOrDefaultMemberDelegate(PSObject msjObj, TypeTable typeTableToUse, MemberNamePredicate predicate) where T : PSMemberInfo
{
return typeTableToUse?.GetFirstMemberOrDefault(msjObj.InternalTypeNames, predicate);
}
private static T AdapterGetMemberDelegate(PSObject msjObj, string name) where T : PSMemberInfo
{
if (msjObj.IsDeserialized)
{
if (msjObj.AdaptedMembers == null)
{
return null;
}
T adaptedMember = msjObj.AdaptedMembers[name] as T;
PSObject.MemberResolution.WriteLine("Serialized adapted member: {0}.", adaptedMember == null ? "not found" : adaptedMember.Name);
return adaptedMember;
}
T retValue = msjObj.InternalAdapter.BaseGetMember(msjObj.ImmediateBaseObject, name);
PSObject.MemberResolution.WriteLine("Adapted member: {0}.", retValue == null ? "not found" : retValue.Name);
return retValue;
}
private static T AdapterGetFirstMemberOrDefaultDelegate(PSObject msjObj, MemberNamePredicate predicate) where T : PSMemberInfo
{
if (msjObj.IsDeserialized && typeof(T).IsAssignableFrom(typeof(PSPropertyInfo)))
{
if (msjObj.AdaptedMembers == null)
{
return null;
}
foreach (var adaptedMember in msjObj.AdaptedMembers)
{
if (predicate(adaptedMember.Name))
{
return adaptedMember as T;
}
}
}
T retValue = msjObj.InternalAdapter.BaseGetFirstMemberOrDefault(msjObj._immediateBaseObject, predicate);
return retValue;
}
internal static PSMemberInfoInternalCollection TransformMemberInfoCollection(PSMemberInfoCollection source)
where TSource : PSMemberInfo where TResult : PSMemberInfo
{
if (typeof(TSource) == typeof(TResult))
{
// If the types are the same, don't make a copy, return the cached collection.
return source as PSMemberInfoInternalCollection;
}
PSMemberInfoInternalCollection returnValue = new PSMemberInfoInternalCollection();
foreach (TSource member in source)
{
if (member is TResult result)
{
returnValue.Add(result);
}
}
return returnValue;
}
private static PSMemberInfoInternalCollection AdapterGetMembersDelegate(PSObject msjObj) where T : PSMemberInfo
{
if (msjObj.IsDeserialized)
{
if (msjObj.AdaptedMembers == null)
{
return new PSMemberInfoInternalCollection();
}
PSObject.MemberResolution.WriteLine("Serialized adapted members: {0}.", msjObj.AdaptedMembers.Count);
return TransformMemberInfoCollection(msjObj.AdaptedMembers);
}
PSMemberInfoInternalCollection retValue = msjObj.InternalAdapter.BaseGetMembers(msjObj._immediateBaseObject);
PSObject.MemberResolution.WriteLine("Adapted members: {0}.", retValue.VisibleCount);
return retValue;
}
private static PSMemberInfoInternalCollection DotNetGetMembersDelegate(PSObject msjObj) where T : PSMemberInfo
{
// Don't lookup dotnet members if the object doesn't insist.
if (msjObj.InternalBaseDotNetAdapter != null)
{
PSMemberInfoInternalCollection retValue = msjObj.InternalBaseDotNetAdapter.BaseGetMembers(msjObj._immediateBaseObject);
PSObject.MemberResolution.WriteLine("DotNet members: {0}.", retValue.VisibleCount);
return retValue;
}
return new PSMemberInfoInternalCollection();
}
private static T DotNetGetMemberDelegate(PSObject msjObj, string name) where T : PSMemberInfo
{
// Don't lookup dotnet member if the object doesn't insist.
if (msjObj.InternalBaseDotNetAdapter != null)
{
T retValue = msjObj.InternalBaseDotNetAdapter.BaseGetMember(msjObj._immediateBaseObject, name);
PSObject.MemberResolution.WriteLine("DotNet member: {0}.", retValue == null ? "not found" : retValue.Name);
return retValue;
}
return null;
}
private static T DotNetGetFirstMemberOrDefaultDelegate(PSObject msjObj, MemberNamePredicate predicate) where T : PSMemberInfo
{
// Don't lookup dotnet member if the object doesn't insist.
return msjObj.InternalBaseDotNetAdapter?.BaseGetFirstMemberOrDefault(msjObj._immediateBaseObject, predicate);
}
///
/// A collection of delegates to get Extended/Adapted/Dotnet members based on the
///
///
///
/// A filter to select Extended/Adapted/Dotnet view of the object
///
///
internal static Collection> GetMemberCollection(PSMemberViewTypes viewType)
{
return GetMemberCollection(viewType, null);
}
///
/// A collection of delegates to get Extended/Adapted/Dotnet members based on the
///
///
///
/// A filter to select Extended/Adapted/Dotnet view of the object
///
///
/// Backup type table to use if there is no execution context associated with the current thread
///
///
internal static Collection> GetMemberCollection(
PSMemberViewTypes viewType,
TypeTable backupTypeTable)
{
Collection> returnValue = new Collection>();
if ((viewType & PSMemberViewTypes.Extended) == PSMemberViewTypes.Extended)
{
if (backupTypeTable == null)
{
returnValue.Add(new CollectionEntry(
PSObject.TypeTableGetMembersDelegate,
PSObject.TypeTableGetMemberDelegate,
PSObject.TypeTableGetFirstMemberOrDefaultDelegate,
true, true, "type table members"));
}
else
{
returnValue.Add(new CollectionEntry(
msjObj => TypeTableGetMembersDelegate(msjObj, backupTypeTable),
(msjObj, name) => TypeTableGetMemberDelegate(msjObj, backupTypeTable, name),
(msjObj, predicate) => TypeTableGetFirstOrDefaultMemberDelegate(msjObj, backupTypeTable, predicate),
true, true, "type table members"));
}
}
if ((viewType & PSMemberViewTypes.Adapted) == PSMemberViewTypes.Adapted)
{
returnValue.Add(new CollectionEntry(
PSObject.AdapterGetMembersDelegate,
PSObject.AdapterGetMemberDelegate,
PSObject.AdapterGetFirstMemberOrDefaultDelegate,
shouldReplicateWhenReturning: false,
shouldCloneWhenReturning: false,
collectionNameForTracing: "adapted members"));
}
if ((viewType & PSMemberViewTypes.Base) == PSMemberViewTypes.Base)
{
returnValue.Add(new CollectionEntry(
PSObject.DotNetGetMembersDelegate,
PSObject.DotNetGetMemberDelegate,
PSObject.DotNetGetFirstMemberOrDefaultDelegate,
shouldReplicateWhenReturning: false,
shouldCloneWhenReturning: false,
collectionNameForTracing: "clr members"));
}
return returnValue;
}
private static Collection> GetMethodCollection()
{
Collection> returnValue = new Collection>
{
new CollectionEntry(
PSObject.TypeTableGetMembersDelegate,
PSObject.TypeTableGetMemberDelegate,
PSObject.TypeTableGetFirstMemberOrDefaultDelegate,
shouldReplicateWhenReturning: true,
shouldCloneWhenReturning: true,
collectionNameForTracing: "type table members"),
new CollectionEntry(
PSObject.AdapterGetMembersDelegate,
PSObject.AdapterGetMemberDelegate,
PSObject.AdapterGetFirstMemberOrDefaultDelegate,
shouldReplicateWhenReturning: false,
shouldCloneWhenReturning: false,
collectionNameForTracing: "adapted members"),
new CollectionEntry(
PSObject.DotNetGetMembersDelegate,
PSObject.DotNetGetMemberDelegate,
PSObject.DotNetGetFirstMemberOrDefaultDelegate,
shouldReplicateWhenReturning: false,
shouldCloneWhenReturning: false,
collectionNameForTracing: "clr members")
};
return returnValue;
}
///
/// A collection of delegates to get Extended/Adapted/Dotnet properties based on the
///
///
///
/// A filter to select Extended/Adapted/Dotnet view of the object
///
///
internal static Collection> GetPropertyCollection(
PSMemberViewTypes viewType)
{
return GetPropertyCollection(viewType, null);
}
///
/// A collection of delegates to get Extended/Adapted/Dotnet properties based on the
///
///
///
/// A filter to select Extended/Adapted/Dotnet view of the object
///
///
/// Backup type table to use if there is no execution context associated with the current thread
///
///
internal static Collection> GetPropertyCollection(
PSMemberViewTypes viewType,
TypeTable backupTypeTable)
{
Collection> returnValue = new Collection>();
if ((viewType & PSMemberViewTypes.Extended) == PSMemberViewTypes.Extended)
{
if (backupTypeTable == null)
{
returnValue.Add(new CollectionEntry(
PSObject.TypeTableGetMembersDelegate,
PSObject.TypeTableGetMemberDelegate,
PSObject.TypeTableGetFirstMemberOrDefaultDelegate,
true, true, "type table members"));
}
else
{
returnValue.Add(new CollectionEntry(
msjObj => TypeTableGetMembersDelegate(msjObj, backupTypeTable),
(msjObj, name) => TypeTableGetMemberDelegate(msjObj, backupTypeTable, name),
PSObject.TypeTableGetFirstMemberOrDefaultDelegate,
true, true, "type table members"));
}
}
if ((viewType & PSMemberViewTypes.Adapted) == PSMemberViewTypes.Adapted)
{
returnValue.Add(new CollectionEntry(
PSObject.AdapterGetMembersDelegate,
PSObject.AdapterGetMemberDelegate,
PSObject.AdapterGetFirstMemberOrDefaultDelegate,
false, false, "adapted members"));
}
if ((viewType & PSMemberViewTypes.Base) == PSMemberViewTypes.Base)
{
returnValue.Add(new CollectionEntry(
PSObject.DotNetGetMembersDelegate,
PSObject.DotNetGetMemberDelegate,
PSObject.DotNetGetFirstMemberOrDefaultDelegate,
false, false, "clr members"));
}
return returnValue;
}
private void CommonInitialization(object obj)
{
Diagnostics.Assert(obj != null, "checked by callers");
if (obj is PSCustomObject)
{
this.ImmediateBaseObjectIsEmpty = true;
}
_immediateBaseObject = obj;
var context = LocalPipeline.GetExecutionContextFromTLS();
_typeTable = context?.TypeTableWeakReference;
}
#region Adapter Mappings
private static readonly ConcurrentDictionary s_adapterMapping = new ConcurrentDictionary();
private static readonly List> s_adapterSetMappers = new List>
{
MappedInternalAdapterSet
};
private static AdapterSet MappedInternalAdapterSet(object obj)
{
if (obj is PSMemberSet) { return PSObject.s_mshMemberSetAdapter; }
if (obj is PSObject) { return PSObject.s_mshObjectAdapter; }
if (obj is CimInstance) { return PSObject.s_cimInstanceAdapter; }
#if !UNIX
if (obj is ManagementClass) { return PSObject.s_managementClassAdapter; }
if (obj is ManagementBaseObject) { return PSObject.s_managementObjectAdapter; }
if (obj is DirectoryEntry) { return PSObject.s_directoryEntryAdapter; }
#endif
if (obj is DataRowView) { return PSObject.s_dataRowViewAdapter; }
if (obj is DataRow) { return PSObject.s_dataRowAdapter; }
if (obj is XmlNode) { return PSObject.s_xmlNodeAdapter; }
return null;
}
///
/// Returns the adapter corresponding to obj.GetType()
///
/// The adapter set corresponding to obj.GetType().
internal static AdapterSet GetMappedAdapter(object obj, TypeTable typeTable)
{
Type objectType = obj.GetType();
PSObject.AdapterSet adapter = typeTable?.GetTypeAdapter(objectType);
if (adapter != null)
{
// We don't cache results found via the type table because type tables may differ b/w runspaces,
// our cache is app domain wide, and the key is simply the type.
return adapter;
}
if (s_adapterMapping.TryGetValue(objectType, out AdapterSet result))
{
return result;
}
lock (s_adapterSetMappers)
{
foreach (var mapper in s_adapterSetMappers)
{
result = mapper(obj);
if (result != null)
{
break;
}
}
}
if (result == null)
{
#if !UNIX
if (objectType.IsCOMObject)
{
// All WinRT types are COM types.
// All WinRT types would contain the TypeAttributes flag being set to WindowsRunTime.
if (WinRTHelper.IsWinRTType(objectType))
{
result = PSObject.s_dotNetInstanceAdapterSet;
}
// We are not using IsAssignableFrom because we want to avoid
// using the COM adapters for Primary Interop Assembly types
// and they derive from System.__ComObject.
// We are not using Type.Equals because System.__ComObject is
// not public.
else if (objectType.FullName.Equals("System.__ComObject"))
{
// We don't cache the adapter set for COM objects because the adapter varies depending on the COM object, but often
// (typically), the runtime type is always the same. That's why this if statement is here and returns.
ComTypeInfo info = ComTypeInfo.GetDispatchTypeInfo(obj);
return info != null
? new AdapterSet(new ComAdapter(info), DotNetInstanceAdapter)
: PSObject.s_dotNetInstanceAdapterSet;
}
else
{
ComTypeInfo info = ComTypeInfo.GetDispatchTypeInfo(obj);
result = info != null
? new AdapterSet(new DotNetAdapterWithComTypeName(info), null)
: PSObject.s_dotNetInstanceAdapterSet;
}
}
else
{
result = PSObject.s_dotNetInstanceAdapterSet;
}
#else
result = PSObject.s_dotNetInstanceAdapterSet;
#endif
}
var existingOrNew = s_adapterMapping.GetOrAdd(objectType, result);
Diagnostics.Assert(existingOrNew == result, "There is a logic error in caching adapter sets.");
return result;
}
#endregion
internal static AdapterSet CreateThirdPartyAdapterSet(Type adaptedType, PSPropertyAdapter adapter)
{
return new AdapterSet(new ThirdPartyAdapter(adaptedType, adapter), s_baseAdapterForAdaptedObjects);
}
#endregion private to the constructors
///
/// Initializes a new instance of PSObject with an PSCustomObject BaseObject.
///
public PSObject()
{
CommonInitialization(PSCustomObject.SelfInstance);
}
///
/// Initializes a new instance of PSObject with an PSCustomObject BaseObject
/// with an initial capacity for members.
///
/// The initial capacity for the instance member collection.
public PSObject(int instanceMemberCapacity) : this()
{
_instanceMembers = new PSMemberInfoInternalCollection(instanceMemberCapacity);
}
///
/// Initializes a new instance of PSObject wrapping obj (accessible through BaseObject).
///
/// Object we are wrapping.
/// If is null.
[SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "This is shipped as part of V1. Retaining this for backward compatibility.")]
public PSObject(object obj)
{
if (obj == null)
{
throw PSTraceSource.NewArgumentNullException(nameof(obj));
}
CommonInitialization(obj);
}
///
/// Creates a PSObject from an ISerializable context.
///
/// Serialization information for this instance.
/// The streaming context for this instance.
protected PSObject(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw PSTraceSource.NewArgumentNullException(nameof(info));
}
if (info.GetValue("CliXml", typeof(string)) is not string serializedData)
{
throw PSTraceSource.NewArgumentNullException(nameof(info));
}
PSObject result = PSObject.AsPSObject(PSSerializer.Deserialize(serializedData));
CommonInitialization(result.ImmediateBaseObject);
CopyDeserializerFields(source: result, target: this);
}
internal static PSObject ConstructPSObjectFromSerializationInfo(SerializationInfo info, StreamingContext context)
{
return new PSObject(info, context);
}
#endregion constructors
#region fields
#region instance fields
private readonly object _lockObject = new object();
private ConsolidatedString _typeNames;
///
/// This is the main field in the class representing
/// the System.Object we are encapsulating.
///
private object _immediateBaseObject;
private WeakReference _typeTable;
private AdapterSet _adapterSet;
private PSMemberInfoInternalCollection _instanceMembers;
private PSMemberInfoIntegratingCollection _members;
private PSMemberInfoIntegratingCollection _properties;
private PSMemberInfoIntegratingCollection _methods;
private PSObjectFlags _flags;
#endregion instance fields
private static readonly PSTraceSource s_memberResolution = PSTraceSource.GetTracer("MemberResolution", "Traces the resolution from member name to the member. A member can be a property, method, etc.", false);
private static readonly ConditionalWeakTable