From 42c289fc8eeaeb684cd8f2b5f55d4b1ee869dbfc Mon Sep 17 00:00:00 2001 From: Staffan Gustafsson Date: Thu, 21 Mar 2019 02:43:52 +0100 Subject: [PATCH] Improve formatting performance by having better primitives on `PSObject` (#8785) By not doing excessive amounts of extra work, formatting can be sped up quite significantly (about 8x faster). The main change comes from adding new, more efficient, primitive to query an object for the existence of an instance member. The formatting system has been checking for if an object has properties other than some decorated properties added by PS remoting, and it doesn't this by retrieving all properties which results in heavy allocations and wasted cycles. By adding `GetFirstOrDefault` to `PSObject` and similar primitives to the underlying Adapters, we are able to return early, without having to get all properties back. --- .../commands/utility/CustomSerialization.cs | 8 +- .../commands/utility/GetMember.cs | 2 +- .../commands/utility/WebCmdlet/JsonObject.cs | 2 +- .../host/msh/ConsoleHost.cs | 7 +- .../common/BaseFormattingCommand.cs | 2 +- .../common/FormatViewManager.cs | 74 +-- .../common/FormattingObjects.cs | 35 +- .../FormatAndOutput/common/ILineOutput.cs | 13 - .../common/Utilities/MshObjectUtil.cs | 75 --- .../other/ciminstancetypeadapter.cs | 38 ++ .../engine/COM/ComAdapter.cs | 42 ++ .../CommandCompletion/CompletionCompleters.cs | 2 +- .../engine/CoreAdapter.cs | 400 ++++++++++-- .../engine/ManagementObjectAdapter.cs | 68 ++- .../engine/MshCommandRuntime.cs | 35 +- .../engine/MshMemberInfo.cs | 190 ++++-- .../engine/MshObject.cs | 571 +++++++++++------- .../engine/ParameterBinderBase.cs | 2 +- .../engine/SessionStateDriveAPIs.cs | 2 +- .../engine/ThirdPartyAdapter.cs | 41 ++ .../engine/TypeTable.cs | 9 +- .../engine/parser/Compiler.cs | 4 +- .../engine/parser/TypeInferenceVisitor.cs | 4 +- .../engine/runtime/Binding/Binders.cs | 24 +- .../engine/runtime/Operations/MiscOps.cs | 5 +- .../engine/serialization.cs | 32 +- test/xUnit/csharp/test_PSObject.cs | 127 ++++ test/xUnit/xUnit.tests.csproj | 9 +- 28 files changed, 1227 insertions(+), 596 deletions(-) create mode 100644 test/xUnit/csharp/test_PSObject.cs diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs index 0553ee9dbf..b286d645c2 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs @@ -342,7 +342,7 @@ namespace System.Management.Automation bool sourceHandled = false; PSObject moSource = source as PSObject; - if (moSource != null && !moSource.immediateBaseObjectIsEmpty) + if (moSource != null && !moSource.ImmediateBaseObjectIsEmpty) { // Check if baseObject is primitive known type object baseObject = moSource.ImmediateBaseObject; @@ -367,7 +367,7 @@ namespace System.Management.Automation IDictionary dictionary = null; // If passed in object is PSObject with no baseobject, return false. - if (mshSource != null && mshSource.immediateBaseObjectIsEmpty) + if (mshSource != null && mshSource.ImmediateBaseObjectIsEmpty) { return false; } @@ -411,7 +411,7 @@ namespace System.Management.Automation // We serialize properties of enumerable and on deserialization mark the object // as Deserialized. So if object is marked deserialized, we should write properties. // Note: we do not serialize the properties of IEnumerable if depth is zero. - if (depth != 0 && (ct == ContainerType.Enumerable || (mshSource != null && mshSource.isDeserialized))) + if (depth != 0 && (ct == ContainerType.Enumerable || (mshSource != null && mshSource.IsDeserialized))) { // Note:Depth is the depth for serialization of baseObject. // Depth for serialization of each property is one less. @@ -595,7 +595,7 @@ namespace System.Management.Automation bool isEnum = false; bool isPSObject = false; - if (!source.immediateBaseObjectIsEmpty) + if (!source.ImmediateBaseObjectIsEmpty) { isEnum = source.ImmediateBaseObject is Enum; isPSObject = source.ImmediateBaseObject is PSObject; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetMember.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetMember.cs index 4ba3536118..a40ccf798b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetMember.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetMember.cs @@ -150,7 +150,7 @@ namespace Microsoft.PowerShell.Commands Adapter staticAdapter = null; if (this.Static == true) { - staticAdapter = PSObject.dotNetStaticAdapter; + staticAdapter = PSObject.DotNetStaticAdapter; object baseObject = this.InputObject.BaseObject; baseObjectAsType = baseObject as System.Type ?? baseObject.GetType(); typeName = baseObjectAsType.FullName; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs index 4ac980430b..8e9d63eb34 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs @@ -551,7 +551,7 @@ namespace Microsoft.PowerShell.Commands { if (currentDepth > context.MaxDepth) { - if (pso != null && pso.immediateBaseObjectIsEmpty) + if (pso != null && pso.ImmediateBaseObjectIsEmpty) { // The obj is a pure PSObject, we convert the original PSObject to a string, // instead of its base object in this case diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index c7113f3f1b..7f8ea16553 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -1981,9 +1981,10 @@ namespace Microsoft.PowerShell error = (object)new ErrorRecord(e, "ConsoleHost.ReportException", ErrorCategory.NotSpecified, null); } - PSObject wrappedError = new PSObject(error); - PSNoteProperty note = new PSNoteProperty("writeErrorStream", true); - wrappedError.Properties.Add(note); + PSObject wrappedError = new PSObject(error) + { + WriteStream = WriteStreamType.Error + }; Exception e1 = null; diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs index e95aa8da32..396fb327b8 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs @@ -463,7 +463,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format { Diagnostics.Assert(so != null, "object so cannot be null"); FormatEntryData fed = _viewManager.ViewGenerator.GeneratePayload(so, _enumerationLimit); - fed.SetStreamTypeFromPSObject(so); + fed.writeStream = so.WriteStream; this.WriteObject(fed); List errors = _viewManager.ViewGenerator.ErrorManager.DrainFailedResultList(); diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewManager.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewManager.cs index baed478cc5..9f2de82a3a 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewManager.cs @@ -464,66 +464,20 @@ namespace Microsoft.PowerShell.Commands.Internal.Format /// internal static class OutOfBandFormatViewManager { - internal static bool IsPropertyLessObject(PSObject so) + private static bool IsNotRemotingProperty(string name) { - List allProperties = AssociationManager.ExpandAll(so); - - if (allProperties.Count == 0) - { - return true; - } - - if (allProperties.Count == 3) - { - foreach (MshResolvedExpressionParameterAssociation property in allProperties) - { - if (!property.ResolvedExpression.ToString().Equals(RemotingConstants.ComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.ShowComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.RunspaceIdNoteProperty, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return true; - } - - if (allProperties.Count == 4) - { - foreach (MshResolvedExpressionParameterAssociation property in allProperties) - { - if (!property.ResolvedExpression.ToString().Equals(RemotingConstants.ComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.ShowComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.RunspaceIdNoteProperty, StringComparison.OrdinalIgnoreCase) - && !property.ResolvedExpression.ToString().Equals(RemotingConstants.SourceJobInstanceId, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return true; - } - - if (allProperties.Count == 5) - { - foreach (MshResolvedExpressionParameterAssociation property in allProperties) - { - if (!property.ResolvedExpression.ToString().Equals(RemotingConstants.ComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.ShowComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.RunspaceIdNoteProperty, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.SourceJobInstanceId, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.SourceLength, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return true; - } - - return false; + var isRemotingPropertyName = name.Equals(RemotingConstants.ComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) + || name.Equals(RemotingConstants.ShowComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) + || name.Equals(RemotingConstants.RunspaceIdNoteProperty, StringComparison.OrdinalIgnoreCase) + || name.Equals(RemotingConstants.SourceJobInstanceId, StringComparison.OrdinalIgnoreCase) + || name.Equals(RemotingConstants.SourceLength, StringComparison.OrdinalIgnoreCase); + return !isRemotingPropertyName; } + private static readonly MemberNamePredicate NameIsNotRemotingProperty = IsNotRemotingProperty; + + private static bool HasNonRemotingProperties(PSObject so) => so.GetFirstPropertyOrDefault(NameIsNotRemotingProperty) != null; + internal static FormatEntryData GenerateOutOfBandData(TerminatingErrorContext errorContext, PSPropertyExpressionFactory expressionFactory, TypeInfoDataBase db, PSObject so, int enumerationLimit, bool useToStringFallback, out List errors) { @@ -549,8 +503,8 @@ namespace Microsoft.PowerShell.Commands.Internal.Format } else { - if (DefaultScalarTypes.IsTypeInList(typeNames) || - IsPropertyLessObject(so)) + if (DefaultScalarTypes.IsTypeInList(typeNames) + || !HasNonRemotingProperties(so)) { // we force a ToString() on well known types return GenerateOutOfBandObjectAsToString(so); @@ -575,7 +529,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format FormatEntryData fed = outOfBandViewGenerator.GeneratePayload(so, enumerationLimit); fed.outOfBand = true; - fed.SetStreamTypeFromPSObject(so); + fed.writeStream = so.WriteStream; errors = outOfBandViewGenerator.ErrorManager.DrainFailedResultList(); diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormattingObjects.cs b/src/System.Management.Automation/FormatAndOutput/common/FormattingObjects.cs index 6b611d2255..bc9cc0565a 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormattingObjects.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormattingObjects.cs @@ -23,6 +23,7 @@ // using System.Collections.Generic; +using System.Management.Automation; namespace Microsoft.PowerShell.Commands.Internal.Format { @@ -149,40 +150,6 @@ namespace Microsoft.PowerShell.Commands.Internal.Format public bool outOfBand = false; public WriteStreamType writeStream = WriteStreamType.None; internal bool isHelpObject = false; - - /// - /// Helper method to set the WriteStreamType property - /// based on note properties of a PSObject object. - /// - /// PSObject. - internal void SetStreamTypeFromPSObject( - System.Management.Automation.PSObject so) - { - if (PSObjectHelper.IsWriteErrorStream(so)) - { - writeStream = WriteStreamType.Error; - } - else if (PSObjectHelper.IsWriteWarningStream(so)) - { - writeStream = WriteStreamType.Warning; - } - else if (PSObjectHelper.IsWriteVerboseStream(so)) - { - writeStream = WriteStreamType.Verbose; - } - else if (PSObjectHelper.IsWriteDebugStream(so)) - { - writeStream = WriteStreamType.Debug; - } - else if (PSObjectHelper.IsWriteInformationStream(so)) - { - writeStream = WriteStreamType.Information; - } - else - { - writeStream = WriteStreamType.None; - } - } } #endregion diff --git a/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs b/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs index d4534a18fd..b8db88610c 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs @@ -109,19 +109,6 @@ namespace Microsoft.PowerShell.Commands.Internal.Format } - /// - /// Specifies special stream write processing. - /// - internal enum WriteStreamType - { - None, - Error, - Warning, - Verbose, - Debug, - Information - } - /// /// Base class providing information about the screen device capabilities /// and used to write the output strings to the text output device. diff --git a/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshObjectUtil.cs b/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshObjectUtil.cs index e201f2479c..1877677e58 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshObjectUtil.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshObjectUtil.cs @@ -35,81 +35,6 @@ namespace Microsoft.PowerShell.Commands.Internal.Format return string.Equals(typeNames[1], "System.Enum", StringComparison.Ordinal); } - /// - /// WriteError adds a note property called WriteErrorStream to the error - /// record wrapped in an PSObject and set its value to true. When F and O detects - /// this note exists and its value is set to true, WriteErrorLine will be used - /// to emit the error; otherwise, F and O actions are regular. - /// - /// - /// - internal static bool IsWriteErrorStream(PSObject so) - { - return IsStreamType(so, "WriteErrorStream"); - } - - /// - /// Checks for WriteWarningStream property on object, indicating that - /// it is a warning stream. Used by F and O. - /// - /// - /// - internal static bool IsWriteWarningStream(PSObject so) - { - return IsStreamType(so, "WriteWarningStream"); - } - - /// - /// Checks for WriteVerboseStream property on object, indicating that - /// it is a verbose stream. Used by F and O. - /// - /// - /// - internal static bool IsWriteVerboseStream(PSObject so) - { - return IsStreamType(so, "WriteVerboseStream"); - } - - /// - /// Checks for WriteDebugStream property on object, indicating that - /// it is a debug stream. Used by F and O. - /// - /// - /// - internal static bool IsWriteDebugStream(PSObject so) - { - return IsStreamType(so, "WriteDebugStream"); - } - - /// - /// Checks for WriteInformationStream property on object, indicating that - /// it is an informational stream. Used by F and O. - /// - /// - /// - internal static bool IsWriteInformationStream(PSObject so) - { - return IsStreamType(so, "WriteInformationStream"); - } - - internal static bool IsStreamType(PSObject so, string streamFlag) - { - try - { - PSPropertyInfo streamProperty = so.Properties[streamFlag]; - if (streamProperty != null && streamProperty.Value is bool) - { - return (bool)streamProperty.Value; - } - - return false; - } - catch (ExtendedTypeSystemException) - { - return false; - } - } - /// /// Retrieve the display name. It looks for a well known property and, /// if not found, it uses some heuristics to get a "close" match. diff --git a/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs b/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs index bc41b2d56c..0fe0569261 100644 --- a/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs +++ b/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs @@ -133,6 +133,44 @@ namespace Microsoft.PowerShell.Cim return null; } + /// + public override PSAdaptedProperty GetFirstPropertyOrDefault(object baseObject, MemberNamePredicate predicate) + { + if (predicate == null) + { + throw new PSArgumentNullException(nameof(predicate)); + } + + // baseObject should never be null + CimInstance cimInstance = baseObject as CimInstance; + if (cimInstance == null) + { + string msg = string.Format( + CultureInfo.InvariantCulture, + CimInstanceTypeAdapterResources.BaseObjectNotCimInstance, + "baseObject", + typeof(CimInstance).ToString()); + throw new PSInvalidOperationException(msg); + } + + if (predicate(RemotingConstants.ComputerNameNoteProperty)) + { + PSAdaptedProperty prop = GetPSComputerNameAdapter(cimInstance); + return prop; + } + + foreach (CimProperty cimProperty in cimInstance.CimInstanceProperties) + { + if (cimProperty != null && predicate(cimProperty.Name)) + { + PSAdaptedProperty prop = GetCimPropertyAdapter(cimProperty, baseObject, cimProperty.Name); + return prop; + } + } + + return null; + } + internal static string CimTypeToTypeNameDisplayString(CimType cimType) { switch (cimType) diff --git a/src/System.Management.Automation/engine/COM/ComAdapter.cs b/src/System.Management.Automation/engine/COM/ComAdapter.cs index afee12b539..b4548246fb 100644 --- a/src/System.Management.Automation/engine/COM/ComAdapter.cs +++ b/src/System.Management.Automation/engine/COM/ComAdapter.cs @@ -84,6 +84,48 @@ namespace System.Management.Automation return null; } + /// + /// Returns the first PSMemberInfo whose name matches the specified . + /// + protected override T GetFirstMemberOrDefault(object obj, MemberNamePredicate predicate) + { + bool lookingForProperties = typeof(T).IsAssignableFrom(typeof(PSProperty)); + bool lookingForParameterizedProperties = typeof(T).IsAssignableFrom(typeof(PSParameterizedProperty)); + if (lookingForProperties || lookingForParameterizedProperties) + { + foreach (ComProperty prop in _comTypeInfo.Properties.Values) + { + if (prop.IsParameterized + && lookingForParameterizedProperties + && predicate(prop.Name)) + { + return new PSParameterizedProperty(prop.Name, this, obj, prop) as T; + } + + if (lookingForProperties && predicate(prop.Name)) + { + return new PSProperty(prop.Name, this, obj, prop) as T; + } + } + } + + bool lookingForMethods = typeof(T).IsAssignableFrom(typeof(PSMethod)); + + if (lookingForMethods) + { + foreach (ComMethod method in _comTypeInfo.Methods.Values) + { + if (predicate(method.Name)) + { + var mshMethod = new PSMethod(method.Name, this, obj, method); + return mshMethod as T; + } + } + } + + return null; + } + /// /// Retrieves all the members available in the object. /// The adapter implementation is encouraged to cache all properties/methods available diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index b340f8ac12..d117e09428 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -6564,7 +6564,7 @@ namespace System.Management.Automation return; } - members = PSObject.dotNetStaticAdapter.BaseGetMembers(type); + members = PSObject.DotNetStaticAdapter.BaseGetMembers(type); } else { diff --git a/src/System.Management.Automation/engine/CoreAdapter.cs b/src/System.Management.Automation/engine/CoreAdapter.cs index f2e745e4b4..119557dd79 100644 --- a/src/System.Management.Automation/engine/CoreAdapter.cs +++ b/src/System.Management.Automation/engine/CoreAdapter.cs @@ -89,6 +89,15 @@ namespace System.Management.Automation /// The PSMemberInfo corresponding to memberName from obj. protected abstract T GetMember(object obj, string memberName) where T : PSMemberInfo; + /// + /// Returns the first PSMemberInfo whose name matches the specified . + /// Otherwise, return null. + /// + /// Object to retrieve the PSMemberInfo from. + /// The predicate to find the matching member. + /// The PSMemberInfo corresponding to the predicate match. + protected abstract T GetFirstMemberOrDefault(object obj, MemberNamePredicate predicate) where T : PSMemberInfo; + /// /// Retrieves all the members available in the object. /// The adapter implementation is encouraged to cache all properties/methods available @@ -212,6 +221,7 @@ namespace System.Management.Automation #endregion method #region parameterized property + /// /// Returns the name of the type corresponding to the property's value. /// @@ -326,8 +336,12 @@ namespace System.Management.Automation #region private - private static Exception NewException(Exception e, string errorId, - string targetErrorId, string resourceString, params object[] parameters) + private static Exception NewException( + Exception e, + string errorId, + string targetErrorId, + string resourceString, + params object[] parameters) { object[] newParameters = new object[parameters.Length + 1]; for (int i = 0; i < parameters.Length; i++) @@ -356,6 +370,7 @@ namespace System.Management.Automation #endregion private #region member + internal ConsolidatedString BaseGetTypeNameHierarchy(object obj) { try @@ -384,6 +399,20 @@ namespace System.Management.Automation } } + internal T BaseGetFirstMemberOrDefault(object obj, MemberNamePredicate predicate) where T : PSMemberInfo + { + try + { + return this.GetFirstMemberOrDefault(obj, predicate); + } + catch (ExtendedTypeSystemException) { throw; } + catch (Exception e) + { + throw NewException(e, "CatchFromBaseGetMember", "CatchFromBaseGetMemberTI", + ExtendedTypeSystem.ExceptionGettingMember, nameof(predicate)); + } + } + internal PSMemberInfoInternalCollection BaseGetMembers(object obj) where T : PSMemberInfo { try @@ -1537,7 +1566,7 @@ namespace System.Management.Automation internal static void SetReferences(object[] arguments, MethodInformation methodInformation, object[] originalArguments) { - using (PSObject.memberResolution.TraceScope("Checking for possible references.")) + using (PSObject.MemberResolution.TraceScope("Checking for possible references.")) { ParameterInformation[] parameters = methodInformation.parameters; for (int i = 0; (i < originalArguments.Length) && (i < parameters.Length) && (i < arguments.Length); i++) @@ -1567,7 +1596,7 @@ namespace System.Management.Automation } object argument = arguments[i]; - PSObject.memberResolution.WriteLine("Argument '{0}' was a reference so it will be set to \"{1}\".", i + 1, argument); + PSObject.MemberResolution.WriteLine("Argument '{0}' was a reference so it will be set to \"{1}\".", i + 1, argument); originalArgumentReference.Value = argument; } } @@ -1741,7 +1770,7 @@ namespace System.Management.Automation bool isParameterByRef, int parameterIndex, Type resultType, IFormatProvider formatProvider) { - using (PSObject.memberResolution.TraceScope("Method argument conversion.")) + using (PSObject.MemberResolution.TraceScope("Method argument conversion.")) { if (resultType == null) { @@ -1772,7 +1801,7 @@ namespace System.Management.Automation PSReference reference = obj as PSReference; if (reference != null) { - PSObject.memberResolution.WriteLine("Parameter was a reference."); + PSObject.MemberResolution.WriteLine("Parameter was a reference."); isArgumentByRef = true; return reference.Value; } @@ -1785,7 +1814,7 @@ namespace System.Management.Automation if (reference != null) { - PSObject.memberResolution.WriteLine("Parameter was an PSObject containing a reference."); + PSObject.MemberResolution.WriteLine("Parameter was an PSObject containing a reference."); isArgumentByRef = true; return reference.Value; } @@ -1796,7 +1825,7 @@ namespace System.Management.Automation internal static object PropertySetAndMethodArgumentConvertTo(object valueToConvert, Type resultType, IFormatProvider formatProvider) { - using (PSObject.memberResolution.TraceScope("Converting parameter \"{0}\" to \"{1}\".", valueToConvert, resultType)) + using (PSObject.MemberResolution.TraceScope("Converting parameter \"{0}\" to \"{1}\".", valueToConvert, resultType)) { if (resultType == null) { @@ -1808,7 +1837,7 @@ namespace System.Management.Automation { if (resultType == typeof(object)) { - PSObject.memberResolution.WriteLine("Parameter was an PSObject and will be converted to System.Object."); + PSObject.MemberResolution.WriteLine("Parameter was an PSObject and will be converted to System.Object."); // we use PSObject.Base so we don't return // PSCustomObject return PSObject.Base(mshObj); @@ -1904,6 +1933,19 @@ namespace System.Management.Automation return this.memberCollection[indexObj]; } } + + internal object GetFirstOrDefault(MemberNamePredicate predicate) + { + foreach (var entry in _indexes) + { + if (predicate(entry.Key)) + { + return this.memberCollection[entry.Value]; + } + } + + return null; + } } /// @@ -3433,7 +3475,7 @@ namespace System.Management.Automation return t == typeof(PSMemberInfo) || t == typeof(PSParameterizedProperty); } - internal T GetDotNetProperty(object obj, string propertyName) where T : PSMemberInfo + private T GetDotNetPropertyImpl(object obj, string propertyName, MemberNamePredicate predicate) where T : PSMemberInfo { bool lookingForProperties = typeof(T).IsAssignableFrom(typeof(PSProperty)); bool lookingForParameterizedProperties = IsTypeParameterizedProperty(typeof(T)); @@ -3443,36 +3485,31 @@ namespace System.Management.Automation } CacheTable typeTable = _isStatic - ? GetStaticPropertyReflectionTable((Type)obj) - : GetInstancePropertyReflectionTable(obj.GetType()); + ? GetStaticPropertyReflectionTable((Type)obj) + : GetInstancePropertyReflectionTable(obj.GetType()); - object entry = typeTable[propertyName]; - if (entry == null) + object entry = predicate != null + ? typeTable.GetFirstOrDefault(predicate) + : typeTable[propertyName]; + switch (entry) { - return null; - } + case null: return null; + case PropertyCacheEntry cacheEntry when lookingForProperties: + { + var isHidden = cacheEntry.member.GetCustomAttributes(typeof(HiddenAttribute), false).Any(); + return new PSProperty(cacheEntry.member.Name, this, obj, cacheEntry) { IsHidden = isHidden } as T; + } + case ParameterizedPropertyCacheEntry paramCacheEntry when lookingForParameterizedProperties: - var propertyEntry = entry as PropertyCacheEntry; - if (propertyEntry != null && lookingForProperties) - { - var isHidden = propertyEntry.member.GetCustomAttributes(typeof(HiddenAttribute), false).Any(); - return new PSProperty(propertyEntry.member.Name, this, obj, propertyEntry) { IsHidden = isHidden } as T; + // TODO: check for HiddenAttribute + // We can't currently write a parameterized property in a PowerShell class so this isn't too important, + // but if someone added the attribute to their C#, it'd be good to set isHidden correctly here. + return new PSParameterizedProperty(paramCacheEntry.propertyName, this, obj, paramCacheEntry) as T; + default: return null; } - - var parameterizedPropertyEntry = entry as ParameterizedPropertyCacheEntry; - if (parameterizedPropertyEntry != null && lookingForParameterizedProperties) - { - // TODO: check for HiddenAttribute - // We can't currently write a parameterized property in a PowerShell class so this isn't too important, - // but if someone added the attribute to their C#, it'd be good to set isHidden correctly here. - return new PSParameterizedProperty(parameterizedPropertyEntry.propertyName, - this, obj, parameterizedPropertyEntry) as T; - } - - return null; } - internal T GetDotNetMethod(object obj, string methodName) where T : PSMemberInfo + private T GetDotNetMethodImpl(object obj, string methodName, MemberNamePredicate predicate) where T : PSMemberInfo { if (!typeof(T).IsAssignableFrom(typeof(PSMethod))) { @@ -3480,10 +3517,13 @@ namespace System.Management.Automation } CacheTable typeTable = _isStatic - ? GetStaticMethodReflectionTable((Type)obj) - : GetInstanceMethodReflectionTable(obj.GetType()); + ? GetStaticMethodReflectionTable((Type)obj) + : GetInstanceMethodReflectionTable(obj.GetType()); + + var methods = predicate != null + ? (MethodCacheEntry)typeTable.GetFirstOrDefault(predicate) + : (MethodCacheEntry)typeTable[methodName]; - var methods = (MethodCacheEntry)typeTable[methodName]; if (methods == null) { return null; @@ -3504,6 +3544,72 @@ namespace System.Management.Automation return PSMethod.Create(methods[0].method.Name, this, obj, methods, isSpecial, isHidden) as T; } + internal T GetDotNetProperty(object obj, string propertyName) where T : PSMemberInfo + { + return GetDotNetPropertyImpl(obj, propertyName, predicate: null); + } + + internal T GetDotNetMethod(object obj, string methodName) where T : PSMemberInfo + { + return GetDotNetMethodImpl(obj, methodName, predicate: null); + } + + protected T GetFirstDotNetPropertyOrDefault(object obj, MemberNamePredicate predicate) where T : PSMemberInfo + { + return GetDotNetPropertyImpl(obj, propertyName: null, predicate); + } + + protected T GetFirstDotNetMethodOrDefault(object obj, MemberNamePredicate predicate) where T : PSMemberInfo + { + return GetDotNetMethodImpl(obj, methodName: null, predicate); + } + + protected T GetFirstDotNetEventOrDefault(object obj, MemberNamePredicate predicate) where T : PSMemberInfo + { + if (!typeof(T).IsAssignableFrom(typeof(PSEvent))) + { + return null; + } + + var table = _isStatic + ? GetStaticEventReflectionTable((Type)obj) + : GetInstanceEventReflectionTable(obj.GetType()); + + foreach (var psEvent in table.Values) + { + if (predicate(psEvent.events[0].Name)) + { + return new PSEvent(psEvent.events[0]) as T; + } + } + + return null; + } + + protected T GetFirstDynamicMemberOrDefault(object obj, MemberNamePredicate predicate) where T : PSMemberInfo + { + var idmop = obj as IDynamicMetaObjectProvider; + if (idmop == null || obj is PSObject) + { + return null; + } + + if (!typeof(T).IsAssignableFrom(typeof(PSDynamicMember))) + { + return null; + } + + foreach (var name in idmop.GetMetaObject(Expression.Variable(idmop.GetType())).GetDynamicMemberNames()) + { + if (predicate(name)) + { + return new PSDynamicMember(name) as T; + } + } + + return null; + } + internal void AddAllProperties(object obj, PSMemberInfoInternalCollection members, bool ignoreDuplicates) where T : PSMemberInfo { bool lookingForProperties = typeof(T).IsAssignableFrom(typeof(PSProperty)); @@ -3519,8 +3625,7 @@ namespace System.Management.Automation for (int i = 0; i < table.memberCollection.Count; i++) { - var propertyEntry = table.memberCollection[i] as PropertyCacheEntry; - if (propertyEntry != null) + if (table.memberCollection[i] is PropertyCacheEntry propertyEntry) { if (lookingForProperties) { @@ -3676,6 +3781,15 @@ namespace System.Management.Automation return GetDotNetMethod(obj, memberName); } + /// + /// Get the first .NET member whose name matches the specified . + /// + protected override T GetFirstMemberOrDefault(object obj, MemberNamePredicate predicate) + { + return GetFirstDotNetPropertyOrDefault(obj, predicate) ?? GetFirstDotNetMethodOrDefault(obj, predicate) ?? + GetFirstDotNetEventOrDefault(obj, predicate) ?? GetFirstDynamicMemberOrDefault(obj, predicate); + } + /// /// Retrieves all the members available in the object. /// The adapter implementation is encouraged to cache all properties/methods available @@ -4061,7 +4175,7 @@ namespace System.Management.Automation string methodDefinition = bestMethod.methodDefinition; ScriptTrace.Trace(1, "TraceMethodCall", ParserStrings.TraceMethodCall, methodDefinition); - PSObject.memberResolution.WriteLine("Calling Method: {0}", methodDefinition); + PSObject.MemberResolution.WriteLine("Calling Method: {0}", methodDefinition); return AuxiliaryMethodInvoke(target, newArguments, bestMethod, arguments); } @@ -4084,9 +4198,9 @@ namespace System.Management.Automation private static object InvokeResolvedConstructor(MethodInformation bestMethod, object[] newArguments, object[] arguments) { - if ((PSObject.memberResolution.Options & PSTraceSourceOptions.WriteLine) != 0) + if ((PSObject.MemberResolution.Options & PSTraceSourceOptions.WriteLine) != 0) { - PSObject.memberResolution.WriteLine("Calling Constructor: {0}", DotNetAdapter.GetMethodInfoOverloadDefinition(null, + PSObject.MemberResolution.WriteLine("Calling Constructor: {0}", DotNetAdapter.GetMethodInfoOverloadDefinition(null, bestMethod.method, 0)); } @@ -4107,7 +4221,7 @@ namespace System.Management.Automation // of all parameters but the last one object[] newArguments; MethodInformation bestMethod = GetBestMethodAndArguments(propertyName, methodInformation, arguments, out newArguments); - PSObject.memberResolution.WriteLine("Calling Set Method: {0}", bestMethod.methodDefinition); + PSObject.MemberResolution.WriteLine("Calling Set Method: {0}", bestMethod.methodDefinition); ParameterInfo[] bestMethodParameters = bestMethod.method.GetParameters(); Type propertyType = bestMethodParameters[bestMethodParameters.Length - 1].ParameterType; @@ -4378,6 +4492,7 @@ namespace System.Management.Automation } #region DotNetAdapterWithOnlyPropertyLookup + /// /// This is used by PSObject to support dotnet member lookup for the adapted /// objects. @@ -4391,9 +4506,6 @@ namespace System.Management.Automation /// Return a collection representing the object's /// members as returned by CLR reflection. /// - /// - /// - /// protected override PSMemberInfoInternalCollection GetMembers(object obj) { PSMemberInfoInternalCollection returnValue = new PSMemberInfoInternalCollection(); @@ -4407,10 +4519,6 @@ namespace System.Management.Automation /// /// Returns a member representing the as given by CLR reflection. /// - /// - /// - /// - /// protected override T GetMember(object obj, string memberName) { PSProperty property = base.GetDotNetProperty(obj, memberName); @@ -4447,6 +4555,49 @@ namespace System.Management.Automation return null; } + + /// + /// Returns the first reflection member whose name matches the specified . + /// Otherwise, return null. + /// + protected override T GetFirstMemberOrDefault(object obj, MemberNamePredicate predicate) + { + PSProperty property = base.GetFirstDotNetPropertyOrDefault(obj, predicate); + if (typeof(T).IsAssignableFrom(typeof(PSProperty)) && property != null) + { + return property as T; + } + + // In order to not break v1..base dotnet adapter should not return methods + // when accessed with T as PSMethod.. accessing method with PSMemberInfo + // is ok as property always gets precedence over methods and duplicates + // are ignored. + if (typeof(T) == typeof(PSMemberInfo)) + { + T returnValue = base.GetFirstDotNetMethodOrDefault(obj, predicate); + + // We only return a method if there is no property by the same name + // to match the behavior we have in GetMembers + if (returnValue != null && property == null) + { + return returnValue; + } + } + + if (IsTypeParameterizedProperty(typeof(T))) + { + var parameterizedProperty = base.GetFirstDotNetPropertyOrDefault(obj, predicate); + + // We only return a parameterized property if there is no property by the same name + // to match the behavior we have in GetMembers + if (parameterizedProperty != null && property == null) + { + return parameterizedProperty as T; + } + } + + return null; + } } #endregion @@ -4628,6 +4779,15 @@ namespace System.Management.Automation return ((PSObject)obj).Members[memberName] as T; } + /// + /// Returns the first PSMemberInfo whose name matches the specified . + /// Otherwise, return null. + /// + protected override T GetFirstMemberOrDefault(object obj, MemberNamePredicate predicate) + { + return ((PSObject)obj).GetFirstPropertyOrDefault(predicate) as T; + } + /// /// Retrieves all the members available in the object. /// The adapter implementation is encouraged to cache all properties/methods available @@ -4687,6 +4847,23 @@ namespace System.Management.Automation return ((PSMemberSet)obj).Members[memberName] as T; } + /// + /// Returns the first PSMemberInfo whose name matches the specified . + /// Otherwise, return null. + /// + protected override T GetFirstMemberOrDefault(object obj, MemberNamePredicate predicate) + { + foreach (var member in ((PSMemberSet)obj).Members) + { + if (predicate(member.Name)) + { + return member as T; + } + } + + return null; + } + /// /// Retrieves all the members available in the object. /// The adapter implementation is encouraged to cache all properties/methods available @@ -4756,6 +4933,15 @@ namespace System.Management.Automation /// The PSProperty corresponding to propertyName from obj. protected abstract PSProperty DoGetProperty(object obj, string propertyName); + /// + /// Returns the first PSProperty whose name matches the specified . + /// Otherwise, return null. + /// + /// Object to retrieve the PSProperty from. + /// The predicate to find the matching member. + /// The first PSProperty whose name matches the . + protected abstract PSProperty DoGetFirstPropertyOrDefault(object obj, MemberNamePredicate predicate); + /// /// Retrieves all the properties available in the object. /// @@ -4804,6 +4990,50 @@ namespace System.Management.Automation return null; } + /// + /// Returns the first PSMemberInfo whose name matches the specified . + /// Otherwise, return null. + /// + /// A subtype of . + /// Object to retrieve the PSMemberInfo from. + /// A name matching predicate. + /// The PSMemberInfo corresponding to the predicate match. + protected override T GetFirstMemberOrDefault(object obj, MemberNamePredicate predicate) + { + PSProperty property = DoGetFirstPropertyOrDefault(obj, predicate); + + if (typeof(T).IsAssignableFrom(typeof(PSProperty))) + { + return property as T; + } + + if (typeof(T).IsAssignableFrom(typeof(PSMethod))) + { + T returnValue = base.GetFirstDotNetMethodOrDefault(obj, predicate); + + // We only return a method if there is no property by the same name + // to match the behavior we have in GetMembers + if (returnValue != null && property == null) + { + return returnValue; + } + } + + if (IsTypeParameterizedProperty(typeof(T))) + { + var parameterizedProperty = base.GetFirstDotNetPropertyOrDefault(obj, predicate); + + // We only return a parameterized property if there is no property by the same name + // to match the behavior we have in GetMembers + if (parameterizedProperty != null && property == null) + { + return parameterizedProperty as T; + } + } + + return null; + } + /// /// Retrieves all the members available in the object. /// The adapter implementation is encouraged to cache all properties/methods available @@ -4959,6 +5189,12 @@ namespace System.Management.Automation return new PSProperty(nodes[0].LocalName, this, obj, nodes); } + protected override PSProperty DoGetFirstPropertyOrDefault(object obj, MemberNamePredicate predicate) + { + XmlNode node = FindNode(obj, predicate); + return node == null ? null : new PSProperty(node.LocalName, this, obj, node); + } + /// /// Returns true if the property is settable. /// @@ -5200,6 +5436,38 @@ namespace System.Management.Automation return retValue.ToArray(); } + + private static XmlNode FindNode(object obj, MemberNamePredicate predicate) + { + var node = (XmlNode)obj; + + if (node.Attributes != null) + { + foreach (XmlNode attribute in node.Attributes) + { + if (predicate(attribute.LocalName)) + { + return attribute; + } + } + } + + foreach (XmlNode childNode in node.ChildNodes) + { + if (childNode is XmlWhitespace) + { + // Win8: 437544 ignore whitespace + continue; + } + + if (predicate(childNode.LocalName)) + { + return childNode; + } + } + + return null; + } } /// @@ -5208,6 +5476,7 @@ namespace System.Management.Automation internal class DataRowAdapter : PropertyOnlyAdapter { #region virtual + /// /// Retrieves all the properties available in the object. /// @@ -5249,6 +5518,21 @@ namespace System.Management.Automation return new PSProperty(columnName, this, obj, columnName); } + protected override PSProperty DoGetFirstPropertyOrDefault(object obj, MemberNamePredicate predicate) + { + DataRow dataRow = (DataRow)obj; + + foreach (DataColumn property in dataRow.Table.Columns) + { + if (predicate(property.ColumnName)) + { + return new PSProperty(property.ColumnName, this, obj, property.ColumnName); + } + } + + return null; + } + /// /// Returns the name of the type corresponding to the property. /// @@ -5356,6 +5640,22 @@ namespace System.Management.Automation return new PSProperty(columnName, this, obj, columnName); } + protected override PSProperty DoGetFirstPropertyOrDefault(object obj, MemberNamePredicate predicate) + { + DataRowView dataRowView = (DataRowView)obj; + + foreach (DataColumn column in dataRowView.Row.Table.Columns) + { + string columnName = column.ColumnName; + if (predicate(columnName)) + { + return new PSProperty(columnName, this, obj, columnName); + } + } + + return null; + } + /// /// Returns the name of the type corresponding to the property. /// diff --git a/src/System.Management.Automation/engine/ManagementObjectAdapter.cs b/src/System.Management.Automation/engine/ManagementObjectAdapter.cs index b07f85c313..d191ce5a9b 100644 --- a/src/System.Management.Automation/engine/ManagementObjectAdapter.cs +++ b/src/System.Management.Automation/engine/ManagementObjectAdapter.cs @@ -198,6 +198,17 @@ namespace System.Management.Automation return null; } + protected override T GetFirstMemberOrDefault(object obj, MemberNamePredicate predicate) + { + if (obj is ManagementBaseObject wmiObject) + { + return GetFirstOrDefaultProperty(wmiObject, predicate) + ?? GetFirstOrDefaultMethod(wmiObject, predicate); + } + + return null; + } + /// /// Retrieves all the members available in the object. /// The adapter implementation is encouraged to cache all properties/methods available @@ -918,6 +929,16 @@ namespace System.Management.Automation protected abstract PSProperty DoGetProperty(ManagementBaseObject wmiObject, string propertyName); + /// + /// Returns the first property whose name matches the specified + /// + protected abstract T GetFirstOrDefaultProperty(ManagementBaseObject wmiObject, MemberNamePredicate predicate) where T : PSMemberInfo; + + /// + /// Returns the first method whose name matches the specified + /// + protected abstract T GetFirstOrDefaultMethod(ManagementBaseObject wmiObject, MemberNamePredicate predicate) where T : PSMemberInfo; + #endregion #region Private Data @@ -1037,11 +1058,8 @@ namespace System.Management.Automation return null; } - CacheTable typeTable; - WMIMethodCacheEntry method; - - typeTable = GetInstanceMethodTable(wmiObject, true); - method = (WMIMethodCacheEntry)typeTable[methodName]; + CacheTable typeTable = GetInstanceMethodTable(wmiObject, true); + WMIMethodCacheEntry method = (WMIMethodCacheEntry)typeTable[methodName]; if (method == null) { @@ -1050,6 +1068,46 @@ namespace System.Management.Automation return new PSMethod(method.Name, this, wmiObject, method) as T; } + + protected override T GetFirstOrDefaultProperty(ManagementBaseObject wmiObject, MemberNamePredicate predicate) + { + if (!typeof(T).IsAssignableFrom(typeof(PSProperty))) + { + return null; + } + + if (wmiObject.SystemProperties != null) + { + foreach (PropertyData property in wmiObject.SystemProperties) + { + if (predicate(property.Name)) + { + return new PSProperty(property.Name, this, wmiObject, property) as T; + } + } + } + + return null; + } + + protected override T GetFirstOrDefaultMethod(ManagementBaseObject wmiObject, MemberNamePredicate predicate) + { + if (!typeof(T).IsAssignableFrom(typeof(PSMethod))) + { + return null; + } + + CacheTable table = GetInstanceMethodTable(wmiObject, true); + foreach (WMIMethodCacheEntry methodEntry in table.memberCollection) + { + if (predicate(methodEntry.Name)) + { + return new PSMethod(methodEntry.Name, this, wmiObject, methodEntry) as T; + } + } + + return null; + } } /// diff --git a/src/System.Management.Automation/engine/MshCommandRuntime.cs b/src/System.Management.Automation/engine/MshCommandRuntime.cs index ef1cd5fbaa..a169a629cc 100644 --- a/src/System.Management.Automation/engine/MshCommandRuntime.cs +++ b/src/System.Management.Automation/engine/MshCommandRuntime.cs @@ -486,12 +486,9 @@ namespace System.Management.Automation CBhost.InternalUI.WriteDebugInfoBuffers(record); } - // Add note property so that the debug output is formatted correctly. + // Set WriteStream so that the debug output is formatted correctly. PSObject debugWrap = PSObject.AsPSObject(record); - if (debugWrap.Members["WriteDebugStream"] == null) - { - debugWrap.Properties.Add(new PSNoteProperty("WriteDebugStream", true)); - } + debugWrap.WriteStream = WriteStreamType.Debug; DebugOutputPipe.Add(debugWrap); } @@ -577,12 +574,9 @@ namespace System.Management.Automation CBhost.InternalUI.WriteVerboseInfoBuffers(record); } - // Add note property so that the verbose output is formatted correctly. + // Add WriteStream so that the verbose output is formatted correctly. PSObject verboseWrap = PSObject.AsPSObject(record); - if (verboseWrap.Members["WriteVerboseStream"] == null) - { - verboseWrap.Properties.Add(new PSNoteProperty("WriteVerboseStream", true)); - } + verboseWrap.WriteStream = WriteStreamType.Verbose; VerboseOutputPipe.Add(verboseWrap); } @@ -668,12 +662,9 @@ namespace System.Management.Automation CBhost.InternalUI.WriteWarningInfoBuffers(record); } - // Add note property so that the warning output is formatted correctly. + // Add WriteStream so that the warning output is formatted correctly. PSObject warningWrap = PSObject.AsPSObject(record); - if (warningWrap.Members["WriteWarningStream"] == null) - { - warningWrap.Properties.Add(new PSNoteProperty("WriteWarningStream", true)); - } + warningWrap.WriteStream = WriteStreamType.Warning; WarningOutputPipe.AddWithoutAppendingOutVarList(warningWrap); } @@ -733,12 +724,9 @@ namespace System.Management.Automation CBhost.InternalUI.WriteInformationInfoBuffers(record); } - // Add note property so that the information output is formatted correctly. + // Add WriteStream so that the information output is formatted correctly. PSObject informationWrap = PSObject.AsPSObject(record); - if (informationWrap.Members["WriteInformationStream"] == null) - { - informationWrap.Properties.Add(new PSNoteProperty("WriteInformationStream", true)); - } + informationWrap.WriteStream = WriteStreamType.Information; InformationOutputPipe.Add(informationWrap); } @@ -2845,10 +2833,9 @@ namespace System.Management.Automation // when tracing), so don't add the member again. // We don't add a note property on messages that comes from stderr stream. - if (!isNativeError && errorWrap.Members["writeErrorStream"] == null) + if (!isNativeError) { - PSNoteProperty note = new PSNoteProperty("writeErrorStream", true); - errorWrap.Properties.Add(note); + errorWrap.WriteStream = WriteStreamType.Error; } // 2003/11/19-JonN Previously, PSObject instances in ErrorOutputPipe @@ -3566,7 +3553,7 @@ namespace System.Management.Automation CBhost.InternalUI.TranscribeResult(inquireCaption); CBhost.InternalUI.TranscribeResult(inquireMessage); - System.Text.StringBuilder textChoices = new System.Text.StringBuilder(); + Text.StringBuilder textChoices = new Text.StringBuilder(); foreach (ChoiceDescription choice in choices) { if (textChoices.Length > 0) diff --git a/src/System.Management.Automation/engine/MshMemberInfo.cs b/src/System.Management.Automation/engine/MshMemberInfo.cs index aff5e7635a..d6dfd19b68 100644 --- a/src/System.Management.Automation/engine/MshMemberInfo.cs +++ b/src/System.Management.Automation/engine/MshMemberInfo.cs @@ -3115,6 +3115,7 @@ namespace System.Management.Automation returnValue.Add(new CollectionEntry( PSObject.TypeTableGetMembersDelegate, PSObject.TypeTableGetMemberDelegate, + PSObject.TypeTableGetFirstMemberOrDefaultDelegate, true, true, "type table members")); return returnValue; } @@ -3125,6 +3126,7 @@ namespace System.Management.Automation returnValue.Add(new CollectionEntry( PSObject.TypeTableGetMembersDelegate, PSObject.TypeTableGetMemberDelegate, + PSObject.TypeTableGetFirstMemberOrDefaultDelegate, true, true, "type table members")); return returnValue; } @@ -3135,6 +3137,7 @@ namespace System.Management.Automation returnValue.Add(new CollectionEntry( PSObject.TypeTableGetMembersDelegate, PSObject.TypeTableGetMemberDelegate, + PSObject.TypeTableGetFirstMemberOrDefaultDelegate, true, true, "type table members")); return returnValue; } @@ -3329,11 +3332,11 @@ namespace System.Management.Automation private void GenerateInternalMembersFromBase() { - if (_psObject.isDeserialized) + if (_psObject.IsDeserialized) { - if (_psObject.clrMembers != null) + if (_psObject.ClrMembers != null) { - foreach (PSMemberInfo member in _psObject.clrMembers) + foreach (PSMemberInfo member in _psObject.ClrMembers) { internalMembers.Add(member.Copy()); } @@ -3342,7 +3345,7 @@ namespace System.Management.Automation else { foreach (PSMemberInfo member in - PSObject.dotNetInstanceAdapter.BaseGetMembers(_psObject.ImmediateBaseObject)) + PSObject.DotNetInstanceAdapter.BaseGetMembers(_psObject.ImmediateBaseObject)) { internalMembers.Add(member.Copy()); } @@ -3353,11 +3356,11 @@ namespace System.Management.Automation { PSMemberInfoInternalCollection retVal = new PSMemberInfoInternalCollection(); - if (_psObject.isDeserialized) + if (_psObject.IsDeserialized) { - if (_psObject.adaptedMembers != null) + if (_psObject.AdaptedMembers != null) { - foreach (PSMemberInfo member in _psObject.adaptedMembers) + foreach (PSMemberInfo member in _psObject.AdaptedMembers) { retVal.Add(member.Copy()); } @@ -3377,7 +3380,7 @@ namespace System.Management.Automation private void GenerateInternalMembersFromPSObject() { - PSMemberInfoCollection members = PSObject.dotNetInstanceAdapter.BaseGetMembers( + PSMemberInfoCollection members = PSObject.DotNetInstanceAdapter.BaseGetMembers( _psObject); foreach (PSMemberInfo member in members) { @@ -3682,6 +3685,13 @@ namespace System.Management.Automation } } + /// + /// A Predicate that determine if a member name matches a criterion. + /// + /// + /// true if the matches the predicate, otherwise false. + public delegate bool MemberNamePredicate(string memberName); + /// /// Serves as the collection of members in an PSObject or MemberSet. /// @@ -3812,6 +3822,8 @@ namespace System.Management.Automation public abstract IEnumerator GetEnumerator(); #endregion IEnumerable + + internal abstract T FirstOrDefault(MemberNamePredicate predicate); } /// @@ -4261,6 +4273,25 @@ namespace System.Management.Automation return _members.Values.OfType().ToList().GetEnumerator(); } } + + /// + /// Returns the first member that matches the specified . + /// + internal override T FirstOrDefault(MemberNamePredicate predicate) + { + lock (_members) + { + foreach (DictionaryEntry entry in _members) + { + if (predicate((string)entry.Key)) + { + return entry.Value as T; + } + } + } + + return null; + } } #region CollectionEntry @@ -4271,13 +4302,21 @@ namespace System.Management.Automation internal delegate T GetMemberDelegate(PSObject obj, string name); - internal CollectionEntry(GetMembersDelegate getMembers, GetMemberDelegate getMember, - bool shouldReplicateWhenReturning, bool shouldCloneWhenReturning, string collectionNameForTracing) + internal delegate T GetFirstOrDefaultDelegate(PSObject obj, MemberNamePredicate predicate); + + internal CollectionEntry( + GetMembersDelegate getMembers, + GetMemberDelegate getMember, + GetFirstOrDefaultDelegate getFirstOrDefault, + bool shouldReplicateWhenReturning, + bool shouldCloneWhenReturning, + string collectionNameForTracing) { GetMembers = getMembers; GetMember = getMember; - ShouldReplicateWhenReturning = shouldReplicateWhenReturning; - ShouldCloneWhenReturning = shouldCloneWhenReturning; + GetFirstOrDefault = getFirstOrDefault; + _shouldReplicateWhenReturning = shouldReplicateWhenReturning; + _shouldCloneWhenReturning = shouldCloneWhenReturning; CollectionNameForTracing = collectionNameForTracing; } @@ -4285,11 +4324,28 @@ namespace System.Management.Automation internal GetMemberDelegate GetMember { get; } - internal bool ShouldReplicateWhenReturning { get; } - - internal bool ShouldCloneWhenReturning { get; } + internal GetFirstOrDefaultDelegate GetFirstOrDefault { get; } internal string CollectionNameForTracing { get; } + + private readonly bool _shouldReplicateWhenReturning; + + private readonly bool _shouldCloneWhenReturning; + + internal T CloneOrReplicateObject(object owner, T member) + { + if (_shouldCloneWhenReturning) + { + member = (T)member.Copy(); + } + + if (_shouldReplicateWhenReturning) + { + member.ReplicateInstance(owner); + } + + return member; + } } #endregion CollectionEntry @@ -4382,9 +4438,9 @@ namespace System.Management.Automation private void GenerateAllReservedMembers() { - if (!_mshOwner.hasGeneratedReservedMembers) + if (!_mshOwner.HasGeneratedReservedMembers) { - _mshOwner.hasGeneratedReservedMembers = true; + _mshOwner.HasGeneratedReservedMembers = true; ReservedNameMembers.GeneratePSExtendedMemberSet(_mshOwner); ReservedNameMembers.GeneratePSBaseMemberSet(_mshOwner); ReservedNameMembers.GeneratePSObjectMemberSet(_mshOwner); @@ -4676,7 +4732,7 @@ namespace System.Management.Automation { get { - using (PSObject.memberResolution.TraceScope("Lookup")) + using (PSObject.MemberResolution.TraceScope("Lookup")) { if (string.IsNullOrEmpty(name)) { @@ -4697,7 +4753,7 @@ namespace System.Management.Automation member = instanceMembers[name]; if (member is T memberAsT) { - PSObject.memberResolution.WriteLine("Found PSObject instance member: {0}.", name); + PSObject.MemberResolution.WriteLine("Found PSObject instance member: {0}.", name); return memberAsT; } } @@ -4711,7 +4767,7 @@ namespace System.Management.Automation // In membersets we cannot replicate the instance when adding // since the memberset might not yet have an associated PSObject. // We replicate the instance when returning the members of the memberset. - PSObject.memberResolution.WriteLine("Found PSMemberSet member: {0}.", name); + PSObject.MemberResolution.WriteLine("Found PSMemberSet member: {0}.", name); member.ReplicateInstance(delegateOwner); return memberAsT; } @@ -4727,17 +4783,7 @@ namespace System.Management.Automation T memberAsT = collection.GetMember((PSObject) delegateOwner, name); if (memberAsT != null) { - if (collection.ShouldCloneWhenReturning) - { - memberAsT = (T) memberAsT.Copy(); - } - - if (collection.ShouldReplicateWhenReturning) - { - memberAsT.ReplicateInstance(delegateOwner); - } - - return memberAsT; + return collection.CloneOrReplicateObject(delegateOwner, memberAsT); } } @@ -4748,7 +4794,7 @@ namespace System.Management.Automation private PSMemberInfoInternalCollection GetIntegratedMembers(MshMemberMatchOptions matchOptions) { - using (PSObject.memberResolution.TraceScope("Generating the total list of members")) + using (PSObject.MemberResolution.TraceScope("Generating the total list of members")) { PSMemberInfoInternalCollection returnValue = new PSMemberInfoInternalCollection(); object delegateOwner; @@ -4794,32 +4840,18 @@ namespace System.Management.Automation PSMemberInfo previousMember = returnValue[member.Name]; if (previousMember != null) { - PSObject.memberResolution.WriteLine("Member \"{0}\" of type \"{1}\" has been ignored because a member with the same name and type \"{2}\" is already present.", + PSObject.MemberResolution.WriteLine("Member \"{0}\" of type \"{1}\" has been ignored because a member with the same name and type \"{2}\" is already present.", member.Name, member.MemberType, previousMember.MemberType); continue; } if (!member.MatchesOptions(matchOptions)) { - PSObject.memberResolution.WriteLine("Skipping hidden member \"{0}\".", member.Name); + PSObject.MemberResolution.WriteLine("Skipping hidden member \"{0}\".", member.Name); continue; } - T memberToAdd; - if (collection.ShouldCloneWhenReturning) - { - memberToAdd = (T) member.Copy(); - } - else - { - memberToAdd = member; - } - - if (collection.ShouldReplicateWhenReturning) - { - memberToAdd.ReplicateInstance(delegateOwner); - } - + T memberToAdd = collection.CloneOrReplicateObject(delegateOwner, member); returnValue.Add(memberToAdd); } } @@ -4871,7 +4903,7 @@ namespace System.Management.Automation /// For invalid arguments. internal override ReadOnlyPSMemberInfoCollection Match(string name, PSMemberTypes memberTypes, MshMemberMatchOptions matchOptions) { - using (PSObject.memberResolution.TraceScope("Matching \"{0}\"", name)) + using (PSObject.MemberResolution.TraceScope("Matching \"{0}\"", name)) { if (string.IsNullOrEmpty(name)) { @@ -4886,7 +4918,7 @@ namespace System.Management.Automation WildcardPattern nameMatch = MemberMatch.GetNamePattern(name); PSMemberInfoInternalCollection allMembers = GetIntegratedMembers(matchOptions); ReadOnlyPSMemberInfoCollection returnValue = new ReadOnlyPSMemberInfoCollection(MemberMatch.Match(allMembers, name, nameMatch, memberTypes)); - PSObject.memberResolution.WriteLine("{0} total matches.", returnValue.Count); + PSObject.MemberResolution.WriteLine("{0} total matches.", returnValue.Count); return returnValue; } } @@ -4902,6 +4934,52 @@ namespace System.Management.Automation return new Enumerator(this); } + internal override T FirstOrDefault(MemberNamePredicate predicate) + { + object delegateOwner; + if (_mshOwner != null) + { + delegateOwner = _mshOwner; + foreach (PSMemberInfo member in _mshOwner.InstanceMembers) + { + if (member is T memberAsT && predicate(memberAsT.Name)) + { + return memberAsT; + } + } + } + else + { + delegateOwner = _memberSetOwner.instance; + foreach (PSMemberInfo member in _memberSetOwner.InternalMembers) + { + if (member is T memberAsT && predicate(memberAsT.Name)) + { + memberAsT.ReplicateInstance(delegateOwner); + return memberAsT; + } + } + } + + if (delegateOwner == null) + { + return null; + } + + var ownerAsPSObj = PSObject.AsPSObject(delegateOwner); + for (int i = 0; i < Collections.Count; i++) + { + var collectionEntry = Collections[i]; + var member = collectionEntry.GetFirstOrDefault(ownerAsPSObj, predicate); + if (member != null) + { + return collectionEntry.CloneOrReplicateObject(ownerAsPSObj, member); + } + } + + return null; + } + #endregion overrides /// @@ -4919,7 +4997,7 @@ namespace System.Management.Automation /// Members we are enumerating. internal Enumerator(PSMemberInfoIntegratingCollection integratingCollection) { - using (PSObject.memberResolution.TraceScope("Enumeration Start")) + using (PSObject.MemberResolution.TraceScope("Enumeration Start")) { _currentIndex = -1; _current = null; @@ -4927,13 +5005,13 @@ namespace System.Management.Automation if (integratingCollection._mshOwner != null) { integratingCollection.GenerateAllReservedMembers(); - PSObject.memberResolution.WriteLine("Enumerating PSObject with type \"{0}\".", integratingCollection._mshOwner.ImmediateBaseObject.GetType().FullName); - PSObject.memberResolution.WriteLine("PSObject instance members: {0}", _allMembers.VisibleCount); + PSObject.MemberResolution.WriteLine("Enumerating PSObject with type \"{0}\".", integratingCollection._mshOwner.ImmediateBaseObject.GetType().FullName); + PSObject.MemberResolution.WriteLine("PSObject instance members: {0}", _allMembers.VisibleCount); } else { - PSObject.memberResolution.WriteLine("Enumerating PSMemberSet \"{0}\".", integratingCollection._memberSetOwner.Name); - PSObject.memberResolution.WriteLine("MemberSet instance members: {0}", _allMembers.VisibleCount); + PSObject.MemberResolution.WriteLine("Enumerating PSMemberSet \"{0}\".", integratingCollection._memberSetOwner.Name); + PSObject.MemberResolution.WriteLine("MemberSet instance members: {0}", _allMembers.VisibleCount); } } } diff --git a/src/System.Management.Automation/engine/MshObject.cs b/src/System.Management.Automation/engine/MshObject.cs index b3ae3378e4..edf41c7e34 100644 --- a/src/System.Management.Automation/engine/MshObject.cs +++ b/src/System.Management.Automation/engine/MshObject.cs @@ -78,17 +78,17 @@ namespace System.Management.Automation PSMemberInfo member = allMembers[name]; if (member == null) { - PSObject.memberResolution.WriteLine("\"{0}\" NOT present in type table.", name); + 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); + 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}.", + PSObject.MemberResolution.WriteLine("\"{0}\" from types table ignored because it has type {1} instead of {2}.", name, member.GetType(), typeof(T)); return null; } @@ -107,26 +107,59 @@ namespace System.Management.Automation } PSMemberInfoInternalCollection members = typeTableToUse.GetMembers(msjObj.InternalTypeNames); - PSObject.memberResolution.WriteLine("Type table members: {0}.", members.Count); + 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.IsDeserialized) { - if (msjObj.adaptedMembers == null) + 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); + 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); + 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; } @@ -152,19 +185,19 @@ namespace System.Management.Automation private static PSMemberInfoInternalCollection AdapterGetMembersDelegate(PSObject msjObj) where T : PSMemberInfo { - if (msjObj.isDeserialized) + if (msjObj.IsDeserialized) { - if (msjObj.adaptedMembers == null) + if (msjObj.AdaptedMembers == null) { return new PSMemberInfoInternalCollection(); } - PSObject.memberResolution.WriteLine("Serialized adapted members: {0}.", msjObj.adaptedMembers.Count); - return TransformMemberInfoCollection(msjObj.adaptedMembers); + 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); + PSObject.MemberResolution.WriteLine("Adapted members: {0}.", retValue.VisibleCount); return retValue; } @@ -174,7 +207,7 @@ namespace System.Management.Automation if (msjObj.InternalBaseDotNetAdapter != null) { PSMemberInfoInternalCollection retValue = msjObj.InternalBaseDotNetAdapter.BaseGetMembers(msjObj._immediateBaseObject); - PSObject.memberResolution.WriteLine("DotNet members: {0}.", retValue.VisibleCount); + PSObject.MemberResolution.WriteLine("DotNet members: {0}.", retValue.VisibleCount); return retValue; } @@ -187,16 +220,18 @@ namespace System.Management.Automation if (msjObj.InternalBaseDotNetAdapter != null) { T retValue = msjObj.InternalBaseDotNetAdapter.BaseGetMember(msjObj._immediateBaseObject, name); - PSObject.memberResolution.WriteLine("DotNet member: {0}.", retValue == null ? "not found" : retValue.Name); + PSObject.MemberResolution.WriteLine("DotNet member: {0}.", retValue == null ? "not found" : retValue.Name); return retValue; } return null; } - private static readonly Collection> s_memberCollection = GetMemberCollection(PSMemberViewTypes.All); - private static readonly Collection> s_methodCollection = GetMethodCollection(); - private static readonly Collection> s_propertyCollection = GetPropertyCollection(PSMemberViewTypes.All); + 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 @@ -234,6 +269,7 @@ namespace System.Management.Automation returnValue.Add(new CollectionEntry( PSObject.TypeTableGetMembersDelegate, PSObject.TypeTableGetMemberDelegate, + PSObject.TypeTableGetFirstMemberOrDefaultDelegate, true, true, "type table members")); } else @@ -241,6 +277,7 @@ namespace System.Management.Automation 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")); } } @@ -250,6 +287,7 @@ namespace System.Management.Automation returnValue.Add(new CollectionEntry( PSObject.AdapterGetMembersDelegate, PSObject.AdapterGetMemberDelegate, + PSObject.AdapterGetFirstMemberOrDefaultDelegate, shouldReplicateWhenReturning: false, shouldCloneWhenReturning: false, collectionNameForTracing: "adapted members")); @@ -260,6 +298,7 @@ namespace System.Management.Automation returnValue.Add(new CollectionEntry( PSObject.DotNetGetMembersDelegate, PSObject.DotNetGetMemberDelegate, + PSObject.DotNetGetFirstMemberOrDefaultDelegate, shouldReplicateWhenReturning: false, shouldCloneWhenReturning: false, collectionNameForTracing: "clr members")); @@ -275,18 +314,21 @@ namespace System.Management.Automation 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") @@ -331,6 +373,7 @@ namespace System.Management.Automation returnValue.Add(new CollectionEntry( PSObject.TypeTableGetMembersDelegate, PSObject.TypeTableGetMemberDelegate, + PSObject.TypeTableGetFirstMemberOrDefaultDelegate, true, true, "type table members")); } else @@ -338,6 +381,7 @@ namespace System.Management.Automation returnValue.Add(new CollectionEntry( msjObj => TypeTableGetMembersDelegate(msjObj, backupTypeTable), (msjObj, name) => TypeTableGetMemberDelegate(msjObj, backupTypeTable, name), + PSObject.TypeTableGetFirstMemberOrDefaultDelegate, true, true, "type table members")); } } @@ -347,6 +391,7 @@ namespace System.Management.Automation returnValue.Add(new CollectionEntry( PSObject.AdapterGetMembersDelegate, PSObject.AdapterGetMemberDelegate, + PSObject.AdapterGetFirstMemberOrDefaultDelegate, false, false, "adapted members")); } @@ -355,6 +400,7 @@ namespace System.Management.Automation returnValue.Add(new CollectionEntry( PSObject.DotNetGetMembersDelegate, PSObject.DotNetGetMemberDelegate, + PSObject.DotNetGetFirstMemberOrDefaultDelegate, false, false, "clr members")); } @@ -366,7 +412,7 @@ namespace System.Management.Automation Diagnostics.Assert(obj != null, "checked by callers"); if (obj is PSCustomObject) { - this.immediateBaseObjectIsEmpty = true; + this.ImmediateBaseObjectIsEmpty = true; } _immediateBaseObject = obj; @@ -374,26 +420,6 @@ namespace System.Management.Automation _typeTable = context?.TypeTableWeakReference; } - internal static readonly DotNetAdapter dotNetInstanceAdapter = new DotNetAdapter(); - private static readonly DotNetAdapter s_baseAdapterForAdaptedObjects = new BaseDotNetAdapterForAdaptedObjects(); - internal static readonly DotNetAdapter dotNetStaticAdapter = new DotNetAdapter(true); - - private static readonly AdapterSet s_dotNetInstanceAdapterSet = new AdapterSet(dotNetInstanceAdapter, null); - private static readonly AdapterSet s_mshMemberSetAdapter = new AdapterSet(new PSMemberSetAdapter(), null); - private static readonly AdapterSet s_mshObjectAdapter = new AdapterSet(new PSObjectAdapter(), null); - private static readonly PSObject.AdapterSet s_cimInstanceAdapter = - new PSObject.AdapterSet(new ThirdPartyAdapter(typeof(Microsoft.Management.Infrastructure.CimInstance), - new Microsoft.PowerShell.Cim.CimInstanceAdapter()), - PSObject.dotNetInstanceAdapter); -#if !UNIX - private static readonly AdapterSet s_managementObjectAdapter = new AdapterSet(new ManagementObjectAdapter(), dotNetInstanceAdapter); - private static readonly AdapterSet s_managementClassAdapter = new AdapterSet(new ManagementClassApdapter(), dotNetInstanceAdapter); - private static readonly AdapterSet s_directoryEntryAdapter = new AdapterSet(new DirectoryEntryAdapter(), dotNetInstanceAdapter); -#endif - private static readonly AdapterSet s_dataRowViewAdapter = new AdapterSet(new DataRowViewAdapter(), s_baseAdapterForAdaptedObjects); - private static readonly AdapterSet s_dataRowAdapter = new AdapterSet(new DataRowAdapter(), s_baseAdapterForAdaptedObjects); - private static readonly AdapterSet s_xmlNodeAdapter = new AdapterSet(new XmlNodeAdapter(), s_baseAdapterForAdaptedObjects); - #region Adapter Mappings private static readonly ConcurrentDictionary s_adapterMapping = new ConcurrentDictionary(); @@ -482,7 +508,7 @@ namespace System.Management.Automation ComTypeInfo info = ComTypeInfo.GetDispatchTypeInfo(obj); return info != null - ? new AdapterSet(new ComAdapter(info), dotNetInstanceAdapter) + ? new AdapterSet(new ComAdapter(info), DotNetInstanceAdapter) : PSObject.s_dotNetInstanceAdapterSet; } else @@ -583,12 +609,10 @@ namespace System.Management.Automation #region fields #region instance fields - private readonly object _lockObject = new Object(); - /// - /// If this is non-null return this string as the ToString() for this wrapped object. - /// - internal string TokenText; + private readonly object _lockObject = new object(); + + private ConsolidatedString _typeNames; /// /// This is the main field in the class representing @@ -597,15 +621,76 @@ namespace System.Management.Automation 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 s_typeNamesResurrectionTable = new ConditionalWeakTable(); + + private static readonly Collection> s_memberCollection = GetMemberCollection(PSMemberViewTypes.All); + private static readonly Collection> s_methodCollection = GetMethodCollection(); + private static readonly Collection> s_propertyCollection = GetPropertyCollection(PSMemberViewTypes.All); + + private static readonly DotNetAdapter s_dotNetInstanceAdapter = new DotNetAdapter(); + private static readonly DotNetAdapter s_baseAdapterForAdaptedObjects = new BaseDotNetAdapterForAdaptedObjects(); + private static readonly DotNetAdapter s_dotNetStaticAdapter = new DotNetAdapter(true); + + private static readonly AdapterSet s_dotNetInstanceAdapterSet = new AdapterSet(DotNetInstanceAdapter, null); + private static readonly AdapterSet s_mshMemberSetAdapter = new AdapterSet(new PSMemberSetAdapter(), null); + private static readonly AdapterSet s_mshObjectAdapter = new AdapterSet(new PSObjectAdapter(), null); + private static readonly PSObject.AdapterSet s_cimInstanceAdapter = + new PSObject.AdapterSet(new ThirdPartyAdapter(typeof(Microsoft.Management.Infrastructure.CimInstance), + new Microsoft.PowerShell.Cim.CimInstanceAdapter()), + PSObject.DotNetInstanceAdapter); +#if !UNIX + private static readonly AdapterSet s_managementObjectAdapter = new AdapterSet(new ManagementObjectAdapter(), DotNetInstanceAdapter); + private static readonly AdapterSet s_managementClassAdapter = new AdapterSet(new ManagementClassApdapter(), DotNetInstanceAdapter); + private static readonly AdapterSet s_directoryEntryAdapter = new AdapterSet(new DirectoryEntryAdapter(), DotNetInstanceAdapter); +#endif + private static readonly AdapterSet s_dataRowViewAdapter = new AdapterSet(new DataRowViewAdapter(), s_baseAdapterForAdaptedObjects); + private static readonly AdapterSet s_dataRowAdapter = new AdapterSet(new DataRowAdapter(), s_baseAdapterForAdaptedObjects); + private static readonly AdapterSet s_xmlNodeAdapter = new AdapterSet(new XmlNodeAdapter(), s_baseAdapterForAdaptedObjects); + + #endregion fields + + #region properties + + internal PSMemberInfoInternalCollection InstanceMembers + { + get + { + if (_instanceMembers == null) + { + lock (_lockObject) + { + if (_instanceMembers == null) + { + _instanceMembers = + s_instanceMembersResurrectionTable.GetValue( + GetKeyForResurrectionTables(this), + _ => new PSMemberInfoInternalCollection()); + } + } + } + + return _instanceMembers; + } + + set => _instanceMembers = value; + } /// /// This is the adapter that will depend on the type of baseObject. /// - internal Adapter InternalAdapter - { - get => InternalAdapterSet.OriginalAdapter; - set => InternalAdapterSet.OriginalAdapter = value; - } + internal Adapter InternalAdapter => InternalAdapterSet.OriginalAdapter; /// /// This is the adapter that is used to resolve the base dotnet members for an @@ -644,97 +729,6 @@ namespace System.Management.Automation } } - private AdapterSet _adapterSet; - - internal bool hasGeneratedReservedMembers; - - internal PSMemberInfoInternalCollection InstanceMembers - { - get - { - if (_instanceMembers == null) - { - lock (_lockObject) - { - if (_instanceMembers == null) - { - _instanceMembers = - s_instanceMembersResurrectionTable.GetValue( - GetKeyForResurrectionTables(this), - _ => new PSMemberInfoInternalCollection()); - } - } - } - - return _instanceMembers; - } - - set => _instanceMembers = value; - } - - private PSMemberInfoInternalCollection _instanceMembers; - - internal static bool HasInstanceMembers(object obj, out PSMemberInfoInternalCollection instanceMembers) - { - if (obj is PSObject psobj) - { - lock (psobj) - { - if (psobj._instanceMembers == null) - { - s_instanceMembersResurrectionTable.TryGetValue(GetKeyForResurrectionTables(psobj), - out psobj._instanceMembers); - } - } - - instanceMembers = psobj._instanceMembers; - } - else if (obj != null) - { - s_instanceMembersResurrectionTable.TryGetValue(GetKeyForResurrectionTables(obj), out instanceMembers); - } - else - { - instanceMembers = null; - } - - return instanceMembers != null && instanceMembers.Count > 0; - } - - private static readonly ConditionalWeakTable> s_instanceMembersResurrectionTable = - new ConditionalWeakTable>(); - - /// - /// Indicate whether we store the instance members and type names locally - /// for this PSObject instance. - /// - private bool _storeTypeNameAndInstanceMembersLocally; - - /// - /// Members from the adapter of the object before it was serialized - /// Null for live objects but not null for deserialized objects. - /// - internal PSMemberInfoInternalCollection adaptedMembers; - - /// - /// Members from the adapter of the object before it was serialized - /// Null for live objects but not null for deserialized objects. - /// - internal PSMemberInfoInternalCollection clrMembers; - - /// - /// Set to true when the BaseObject is PSCustomObject. - /// - internal bool immediateBaseObjectIsEmpty; - - internal static PSTraceSource memberResolution = PSTraceSource.GetTracer("MemberResolution", "Traces the resolution from member name to the member. A member can be a property, method, etc.", false); - - #endregion instance fields - - #endregion fields - - #region properties - /// /// Gets the member collection. /// @@ -757,8 +751,6 @@ namespace System.Management.Automation } } - private PSMemberInfoIntegratingCollection _members; - /// /// Gets the Property collection, or the members that are actually properties. /// @@ -781,7 +773,6 @@ namespace System.Management.Automation } } - private PSMemberInfoIntegratingCollection _properties; /// /// Gets the Method collection, or the members that are actually methods. @@ -805,8 +796,6 @@ namespace System.Management.Automation } } - private PSMemberInfoIntegratingCollection _methods; - /// /// Gets the object we are directly wrapping. /// @@ -917,12 +906,37 @@ namespace System.Management.Automation return s_typeNamesResurrectionTable.TryGetValue(GetKeyForResurrectionTables(obj), out result); } - private ConsolidatedString _typeNames; - private static readonly ConditionalWeakTable s_typeNamesResurrectionTable = new ConditionalWeakTable(); - #endregion properties #region static methods + internal static bool HasInstanceMembers(object obj, out PSMemberInfoInternalCollection instanceMembers) + { + if (obj is PSObject psobj) + { + lock (psobj) + { + if (psobj._instanceMembers == null) + { + s_instanceMembersResurrectionTable.TryGetValue(GetKeyForResurrectionTables(psobj), out psobj._instanceMembers); + } + } + + instanceMembers = psobj._instanceMembers; + } + else if (obj != null) + { + s_instanceMembersResurrectionTable.TryGetValue(GetKeyForResurrectionTables(obj), out instanceMembers); + } + else + { + instanceMembers = null; + } + + return instanceMembers != null && instanceMembers.Count > 0; + } + + private static readonly ConditionalWeakTable> s_instanceMembersResurrectionTable = + new ConditionalWeakTable>(); /// /// @@ -980,7 +994,7 @@ namespace System.Management.Automation if (mshObj == AutomationNull.Value) return null; - if (mshObj.immediateBaseObjectIsEmpty) + if (mshObj.ImmediateBaseObjectIsEmpty) { return obj; } @@ -990,7 +1004,7 @@ namespace System.Management.Automation { returnValue = mshObj._immediateBaseObject; mshObj = returnValue as PSObject; - } while ((mshObj != null) && (!mshObj.immediateBaseObjectIsEmpty)); + } while ((mshObj != null) && (!mshObj.ImmediateBaseObjectIsEmpty)); return returnValue; } @@ -1004,7 +1018,7 @@ namespace System.Management.Automation } var objType = obj as Type ?? obj.GetType(); - return dotNetStaticAdapter.BaseGetMember(objType, methodName); + return DotNetStaticAdapter.BaseGetMember(objType, methodName); } /// @@ -1044,7 +1058,7 @@ namespace System.Management.Automation return so; } - return new PSObject(obj) { _storeTypeNameAndInstanceMembersLocally = storeTypeNameAndInstanceMembersLocally }; + return new PSObject(obj) { StoreTypeNameAndInstanceMembersLocally = storeTypeNameAndInstanceMembersLocally }; } /// @@ -1071,7 +1085,7 @@ namespace System.Management.Automation if (psObjectAboveBase.ImmediateBaseObject is PSCustomObject || psObjectAboveBase.ImmediateBaseObject is string - || pso._storeTypeNameAndInstanceMembersLocally) + || pso.StoreTypeNameAndInstanceMembersLocally) { return psObjectAboveBase; } @@ -1412,7 +1426,7 @@ namespace System.Management.Automation // Since we don't have a brokered ToString, we check for the need to enumerate the object or its properties if (recurse) { - if (mshObj.immediateBaseObjectIsEmpty) + if (mshObj.ImmediateBaseObjectIsEmpty) { try { @@ -1495,9 +1509,9 @@ namespace System.Management.Automation { // If ToString value from deserialization is available, // simply return it. - if (_toStringFromDeserialization != null) + if (ToStringFromDeserialization != null) { - return _toStringFromDeserialization; + return ToStringFromDeserialization; } return PSObject.ToString(null, this, null, null, null, true, false); @@ -1516,9 +1530,9 @@ namespace System.Management.Automation { // If ToString value from deserialization is available, // simply return it. - if (_toStringFromDeserialization != null) + if (ToStringFromDeserialization != null) { - return _toStringFromDeserialization; + return ToStringFromDeserialization; } return PSObject.ToString(null, this, null, format, formatProvider, true, false); @@ -1555,12 +1569,12 @@ namespace System.Management.Automation if (this.BaseObject is PSCustomObject) { returnValue._immediateBaseObject = PSCustomObject.SelfInstance; - returnValue.immediateBaseObjectIsEmpty = true; + returnValue.ImmediateBaseObjectIsEmpty = true; } else { returnValue._immediateBaseObject = _immediateBaseObject; - returnValue.immediateBaseObjectIsEmpty = false; + returnValue.ImmediateBaseObjectIsEmpty = false; } // Instance members will be recovered as necessary through the resurrection table. @@ -1607,7 +1621,8 @@ namespace System.Management.Automation } } - returnValue.hasGeneratedReservedMembers = false; + returnValue.WriteStream = WriteStream; + returnValue.HasGeneratedReservedMembers = false; return returnValue; } @@ -1788,7 +1803,7 @@ namespace System.Management.Automation // We create a wrapper PSObject, so that we can successfully deserialize it string serializedContent; - if (this.immediateBaseObjectIsEmpty) + if (this.ImmediateBaseObjectIsEmpty) { PSObject serializeTarget = new PSObject(this); serializedContent = PSSerializer.Serialize(serializeTarget); @@ -1974,22 +1989,22 @@ namespace System.Management.Automation internal bool ShouldSerializeAdapter() { - if (this.isDeserialized) + if (this.IsDeserialized) { - return this.adaptedMembers != null; + return this.AdaptedMembers != null; } - return !this.immediateBaseObjectIsEmpty; + return !this.ImmediateBaseObjectIsEmpty; } internal PSMemberInfoInternalCollection GetAdaptedProperties() { - return GetProperties(this.adaptedMembers, this.InternalAdapter); + return GetProperties(this.AdaptedMembers, this.InternalAdapter); } private PSMemberInfoInternalCollection GetProperties(PSMemberInfoInternalCollection serializedMembers, Adapter particularAdapter) { - if (this.isDeserialized) + if (this.IsDeserialized) { return serializedMembers; } @@ -2004,23 +2019,18 @@ namespace System.Management.Automation return returnValue; } - /// - /// This flag is set to true in deserialized shellobject. - /// - internal bool isDeserialized; - internal static void CopyDeserializerFields(PSObject source, PSObject target) { - if (!target.isDeserialized) + if (!target.IsDeserialized) { - target.isDeserialized = source.isDeserialized; - target.adaptedMembers = source.adaptedMembers; - target.clrMembers = source.clrMembers; + target.IsDeserialized = source.IsDeserialized; + target.AdaptedMembers = source.AdaptedMembers; + target.ClrMembers = source.ClrMembers; } - if (target._toStringFromDeserialization == null) + if (target.ToStringFromDeserialization == null) { - target._toStringFromDeserialization = source._toStringFromDeserialization; + target.ToStringFromDeserialization = source.ToStringFromDeserialization; target.TokenText = source.TokenText; } } @@ -2033,9 +2043,9 @@ namespace System.Management.Automation ///This method is to be used only by Serialization code internal void SetCoreOnDeserialization(object value, bool overrideTypeInfo) { - Diagnostics.Assert(this.immediateBaseObjectIsEmpty, "BaseObject should be PSCustomObject for deserialized objects"); + Diagnostics.Assert(this.ImmediateBaseObjectIsEmpty, "BaseObject should be PSCustomObject for deserialized objects"); Diagnostics.Assert(value != null, "known objects are never null"); - this.immediateBaseObjectIsEmpty = false; + this.ImmediateBaseObjectIsEmpty = false; _immediateBaseObject = value; _adapterSet = GetMappedAdapter(_immediateBaseObject, GetTypeTable()); if (overrideTypeInfo) @@ -2044,42 +2054,6 @@ namespace System.Management.Automation } } - // This is toString value set on deserialization - private string _toStringFromDeserialization = null; - - internal bool preserveToString = false; - internal bool preserveToStringSet = false; - - internal bool PreserveToString - { - get - { - if (preserveToStringSet) - { - return preserveToString; - } - - preserveToStringSet = true; - if (InternalTypeNames.Count == 0) - { - return false; - } - - preserveToString = false; - - return preserveToString; - } - } - - /// - /// Sets the to string value on deserialization. - /// - internal string ToStringFromDeserialization - { - get => _toStringFromDeserialization; - set => _toStringFromDeserialization = value; - } - #endregion serialization /// @@ -2302,17 +2276,164 @@ namespace System.Management.Automation #endregion - #region Help formatting + internal bool IsDeserialized + { + get => _flags.HasFlag(PSObjectFlags.IsDeserialized); + set + { + if (value) + { + _flags |= PSObjectFlags.IsDeserialized; + } + else + { + _flags &= ~PSObjectFlags.IsDeserialized; + } + } + } + + private bool StoreTypeNameAndInstanceMembersLocally + { + get => _flags.HasFlag(PSObjectFlags.StoreTypeNameAndInstanceMembersLocally); + set + { + if (value) + { + _flags |= PSObjectFlags.StoreTypeNameAndInstanceMembersLocally; + } + else + { + _flags &= ~PSObjectFlags.StoreTypeNameAndInstanceMembersLocally; + } + } + } internal bool IsHelpObject { - get => _isHelpObject; - set => _isHelpObject = value; + get => _flags.HasFlag(PSObjectFlags.IsHelpObject); + set + { + if (value) + { + _flags |= PSObjectFlags.IsHelpObject; + } + else + { + _flags &= ~PSObjectFlags.IsHelpObject; + } + } } - private bool _isHelpObject = false; + internal bool HasGeneratedReservedMembers + { + get => _flags.HasFlag(PSObjectFlags.HasGeneratedReservedMembers); + set + { + if (value) + { + _flags |= PSObjectFlags.HasGeneratedReservedMembers; + } + else + { + _flags &= ~PSObjectFlags.HasGeneratedReservedMembers; + } + } + } - #endregion + internal bool ImmediateBaseObjectIsEmpty + { + get => _flags.HasFlag(PSObjectFlags.ImmediateBaseObjectIsEmpty); + set + { + if (value) + { + _flags |= PSObjectFlags.ImmediateBaseObjectIsEmpty; + } + else + { + _flags &= ~PSObjectFlags.ImmediateBaseObjectIsEmpty; + } + } + } + + /// + /// If 'this' is non-null, return this string as the ToString() for this wrapped object. + /// + internal string TokenText { get; set; } + + /// + /// Sets the 'ToString' value on deserialization. + /// + internal string ToStringFromDeserialization { get; set; } + + /// + /// This property contains a stream type used by the formatting system. + /// + internal WriteStreamType WriteStream { get; set; } + + /// + /// Members from the adapter of the object before it was serialized + /// Null for live objects but not null for deserialized objects. + /// + internal PSMemberInfoInternalCollection AdaptedMembers { get; set; } + + internal static DotNetAdapter DotNetStaticAdapter => s_dotNetStaticAdapter; + + internal static PSTraceSource MemberResolution => s_memberResolution; + + /// + /// Members from the adapter of the object before it was serialized + /// Null for live objects but not null for deserialized objects. + /// + internal PSMemberInfoInternalCollection ClrMembers { get; set; } + + internal static DotNetAdapter DotNetInstanceAdapter => s_dotNetInstanceAdapter; + + /// + /// Gets an instance member if it's name matches the predicate. Otherwise null. + /// + internal PSPropertyInfo GetFirstPropertyOrDefault(MemberNamePredicate predicate) + { + return Properties.FirstOrDefault(predicate); + } + + [Flags] + private enum PSObjectFlags : byte + { + None = 0, + + /// + /// This flag is set in deserialized shellobject. + /// + IsDeserialized = 0b00000001, + + /// + /// Set to true when the BaseObject is PSCustomObject. + /// + HasGeneratedReservedMembers = 0b00000010, + ImmediateBaseObjectIsEmpty = 0b00000100, + IsHelpObject = 0b00001000, + + /// + /// Indicate whether we store the instance members and type names locally + /// for this PSObject instance. + /// + StoreTypeNameAndInstanceMembersLocally = 0b00010000, + } + } + + /// + /// Specifies special stream write processing. + /// + internal enum WriteStreamType : byte + { + None, + Output, + Error, + Warning, + Verbose, + Debug, + Information } /// @@ -2347,7 +2468,7 @@ namespace System.Management.Automation AllPublicProperties = 0, String = 1, SpecificProperties = 2 - }; + } } #pragma warning restore 56500 diff --git a/src/System.Management.Automation/engine/ParameterBinderBase.cs b/src/System.Management.Automation/engine/ParameterBinderBase.cs index 39b08596ec..e25d6df506 100644 --- a/src/System.Management.Automation/engine/ParameterBinderBase.cs +++ b/src/System.Management.Automation/engine/ParameterBinderBase.cs @@ -850,7 +850,7 @@ namespace System.Management.Automation } var psobj = parameterValue as PSObject; - if (psobj != null && !psobj.immediateBaseObjectIsEmpty) + if (psobj != null && !psobj.ImmediateBaseObjectIsEmpty) { // See if the base object is of the same type or // as subclass of the parameter diff --git a/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs b/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs index b2eed9aeb5..b6af867457 100644 --- a/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs @@ -88,7 +88,7 @@ namespace System.Management.Automation // set the return value to the first drive (should only be one). - if (!successObjects[0].immediateBaseObjectIsEmpty) + if (!successObjects[0].ImmediateBaseObjectIsEmpty) { result = (PSDriveInfo)successObjects[0].BaseObject; } diff --git a/src/System.Management.Automation/engine/ThirdPartyAdapter.cs b/src/System.Management.Automation/engine/ThirdPartyAdapter.cs index 75cfbd6020..8c2d485546 100644 --- a/src/System.Management.Automation/engine/ThirdPartyAdapter.cs +++ b/src/System.Management.Automation/engine/ThirdPartyAdapter.cs @@ -128,6 +128,30 @@ namespace System.Management.Automation return property; } + protected override PSProperty DoGetFirstPropertyOrDefault(object obj, MemberNamePredicate predicate) + { + PSAdaptedProperty property = null; + + try + { + property = _externalAdapter.GetFirstPropertyOrDefault(obj, predicate); + } + catch (Exception exception) + { + throw new ExtendedTypeSystemException( + "PSPropertyAdapter.GetProperty", + exception, + ExtendedTypeSystem.GetProperty, nameof(predicate), obj.ToString()); + } + + if (property != null) + { + InitializeProperty(property, obj); + } + + return property; + } + /// /// Ensures that the adapter and base object are set in the given PSAdaptedProperty. /// @@ -323,5 +347,22 @@ namespace System.Management.Automation /// Returns the type for a given property. /// public abstract string GetPropertyTypeName(PSAdaptedProperty adaptedProperty); + + /// + /// Returns a property if it's name matches the specified , otherwise null. + /// + /// An adapted property if the predicate matches, or null. + public virtual PSAdaptedProperty GetFirstPropertyOrDefault(object baseObject, MemberNamePredicate predicate) + { + foreach (var property in GetProperties(baseObject)) + { + if (predicate(property.Name)) + { + return property; + } + } + + return null; + } } } diff --git a/src/System.Management.Automation/engine/TypeTable.cs b/src/System.Management.Automation/engine/TypeTable.cs index 437cf401e6..d807885caf 100644 --- a/src/System.Management.Automation/engine/TypeTable.cs +++ b/src/System.Management.Automation/engine/TypeTable.cs @@ -3633,6 +3633,11 @@ namespace System.Management.Automation.Runspaces return PSObject.TransformMemberInfoCollection(GetMembers(types)); } + internal T GetFirstMemberOrDefault(ConsolidatedString types, MemberNamePredicate predicate) where T : PSMemberInfo + { + return GetMembers(types).FirstOrDefault(member => member is T && predicate(member.Name)) as T; + } + internal PSMemberInfoInternalCollection GetMembers(ConsolidatedString types) { if ((types == null) || string.IsNullOrEmpty(types.Key)) @@ -3640,8 +3645,7 @@ namespace System.Management.Automation.Runspaces return new PSMemberInfoInternalCollection(); } - PSMemberInfoInternalCollection result = _consolidatedMembers.GetOrAdd(types.Key, _memberFactoryFunc, types); - return result; + return _consolidatedMembers.GetOrAdd(types.Key, _memberFactoryFunc, types); } private PSMemberInfoInternalCollection MemberFactory(string k, ConsolidatedString types) @@ -4527,5 +4531,6 @@ namespace System.Management.Automation.Runspaces } #endregion internal methods + } } diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 72ede0a445..38be189389 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -339,8 +339,8 @@ namespace System.Management.Automation.Language typeof(PSObject).GetMethod(nameof(PSObject.Base), staticFlags); internal static readonly PropertyInfo PSObject_BaseObject = typeof(PSObject).GetProperty(nameof(PSObject.BaseObject)); - internal static readonly FieldInfo PSObject_isDeserialized = - typeof(PSObject).GetField(nameof(PSObject.isDeserialized), instanceFlags); + internal static readonly PropertyInfo PSObject_IsDeserialized = + typeof(PSObject).GetProperty(nameof(PSObject.IsDeserialized), instanceFlags); internal static readonly MethodInfo PSObject_ToStringParser = typeof(PSObject).GetMethod(nameof(PSObject.ToStringParser), staticFlags, null, new[] { typeof(ExecutionContext), typeof(object) }, null); diff --git a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs index a3c095b493..6e0d02d1c1 100644 --- a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs +++ b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs @@ -249,8 +249,8 @@ namespace System.Management.Automation } var members = isStatic - ? PSObject.dotNetStaticAdapter.BaseGetMembers(type) - : PSObject.dotNetInstanceAdapter.GetPropertiesAndMethods(type, false); + ? PSObject.DotNetStaticAdapter.BaseGetMembers(type) + : PSObject.DotNetInstanceAdapter.GetPropertiesAndMethods(type, false); if (filterToCall != null) { diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index 18d9449a54..94e6fd4e68 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -5386,7 +5386,7 @@ namespace System.Management.Automation.Language // If the target value is actually a deserialized PSObject, we should use the original value var psobj = value as PSObject; - if (psobj != null && psobj != AutomationNull.Value && !psobj.isDeserialized) + if (psobj != null && psobj != AutomationNull.Value && !psobj.IsDeserialized) { expr = Expression.Call(CachedReflectionInfo.PSObject_Base, expr); value = PSObject.Base(value); @@ -5650,7 +5650,7 @@ namespace System.Management.Automation.Language // // - If not, we want to use the base object, so that we might generate optimized code. var psobj = target.Value as PSObject; - bool isTargetDeserializedObject = (psobj != null) && (psobj.isDeserialized); + bool isTargetDeserializedObject = (psobj != null) && (psobj.IsDeserialized); object value = isTargetDeserializedObject ? target.Value : PSObject.Base(target.Value); var adapterSet = PSObject.GetMappedAdapter(value, typeTable); @@ -5703,7 +5703,7 @@ namespace System.Management.Automation.Language } } - if (_classScope != null && (target.LimitType == _classScope || target.LimitType.IsSubclassOf(_classScope)) && adapterSet.OriginalAdapter == PSObject.dotNetInstanceAdapter) + if (_classScope != null && (target.LimitType == _classScope || target.LimitType.IsSubclassOf(_classScope)) && adapterSet.OriginalAdapter == PSObject.DotNetInstanceAdapter) { List candidateMethods = null; foreach (var member in _classScope.GetMembers(BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic)) @@ -5719,7 +5719,7 @@ namespace System.Management.Automation.Language if ((getMethod == null || getMethod.IsFamily || getMethod.IsPublic) && (setMethod == null || setMethod.IsFamily || setMethod.IsPublic)) { - memberInfo = new PSProperty(this.Name, PSObject.dotNetInstanceAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(propertyInfo)); + memberInfo = new PSProperty(this.Name, PSObject.DotNetInstanceAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(propertyInfo)); } } else @@ -5729,7 +5729,7 @@ namespace System.Management.Automation.Language { if (fieldInfo.IsFamily) { - memberInfo = new PSProperty(this.Name, PSObject.dotNetInstanceAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(fieldInfo)); + memberInfo = new PSProperty(this.Name, PSObject.DotNetInstanceAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(fieldInfo)); } } else @@ -5767,7 +5767,7 @@ namespace System.Management.Automation.Language else { DotNetAdapter.MethodCacheEntry method = new DotNetAdapter.MethodCacheEntry(candidateMethods.ToArray()); - memberInfo = PSMethod.Create(this.Name, PSObject.dotNetInstanceAdapter, null, method); + memberInfo = PSMethod.Create(this.Name, PSObject.DotNetInstanceAdapter, null, method); } } } @@ -5788,7 +5788,7 @@ namespace System.Management.Automation.Language if (isTargetDeserializedObject) { restrictions = restrictions.Merge(BindingRestrictions.GetExpressionRestriction( - Expression.Field(target.Expression.Cast(typeof(PSObject)), CachedReflectionInfo.PSObject_isDeserialized))); + Expression.Property(target.Expression.Cast(typeof(PSObject)), CachedReflectionInfo.PSObject_IsDeserialized))); } if (hasTypeTableMember) @@ -6744,8 +6744,8 @@ namespace System.Management.Automation.Language // If the target value is a PSObject and its base object happens to be a Hashtable or ArrayList, // we might have three interesting cases here: // (1) the target value could be a regular PSObject that wraps the Hashtable/ArrayList, i.e. $target = [PSObject]::AsPSObject($hash) - // (2) the target value could be a deserialized object (PSObject) with the 'isDeserialized' field to be false, i.e. deserialized Hashtable/ArrayList/Dictionary[string, string] - // (3) the target value could be a deserialized object (PSObject) with the 'isDeserialized' field to be true, i.e. deserialized XmlElement + // (2) the target value could be a deserialized object (PSObject) with the 'IsDeserialized' property to be false, i.e. deserialized Hashtable/ArrayList/Dictionary[string, string] + // (3) the target value could be a deserialized object (PSObject) with the 'IsDeserialized' property to be true, i.e. deserialized XmlElement // For the first two cases, it's OK to call a .NET method from the base object, such as $target.Add(). // For the third case, calling a .NET method from the base object is incorrect, because the original type of the deserialized object doesn't have the method. // example: XmlElement derives from IEnumerable, so it's treated as a container object when powershell does the serialization -- using an ArrayList to hold @@ -6761,11 +6761,11 @@ namespace System.Management.Automation.Language // If we get here, then the target value should have 'isDeserialized == false', otherwise we cannot get a .NET methodInfo // from _getMemberBinder.GetPSMemberInfo(). This is because when 'isDeserialized' is true, we use the PSObject to find the // corresponding Adapter -- PSObjectAdapter, which cannot be optimized. - Diagnostics.Assert(psObj.isDeserialized == false, + Diagnostics.Assert(psObj.IsDeserialized == false, "isDeserialized should be false, because if not, we cannot get a .NET method/parameterizedProperty from GetPSMemberInfo"); restrictions = restrictions.Merge(BindingRestrictions.GetExpressionRestriction( - Expression.Not(Expression.Field(target.Expression.Cast(typeof(PSObject)), CachedReflectionInfo.PSObject_isDeserialized)))); + Expression.Not(Expression.Property(target.Expression.Cast(typeof(PSObject)), CachedReflectionInfo.PSObject_IsDeserialized)))); } } @@ -6976,7 +6976,7 @@ namespace System.Management.Automation.Language { MethodInfo result = null; - var psMethod = PSObject.dotNetInstanceAdapter.GetDotNetMethod(PSObject.Base(target.Value), methodName); + var psMethod = PSObject.DotNetInstanceAdapter.GetDotNetMethod(PSObject.Base(target.Value), methodName); if (psMethod != null) { var data = (DotNetAdapter.MethodCacheEntry)psMethod.adapterData; diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index 4dbed59c71..d4c6f8f10a 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -659,7 +659,7 @@ namespace System.Management.Automation // GetSteppablePipeline() is called on an arbitrary script block with the intention // of invoking it. So the trustworthiness is defined by the trustworthiness of the // script block's language mode. - bool isTrusted = (scriptBlock.LanguageMode == PSLanguageMode.FullLanguage); + bool isTrusted = scriptBlock.LanguageMode == PSLanguageMode.FullLanguage; foreach (var commandAst in pipelineAst.PipelineElements.Cast()) { @@ -1888,8 +1888,7 @@ namespace System.Management.Automation rte.ErrorRecord.SetInvocationInfo(new InvocationInfo(null, extent, context)); PSObject errorWrap = PSObject.AsPSObject(new ErrorRecord(rte.ErrorRecord, rte)); - PSNoteProperty note = new PSNoteProperty("writeErrorStream", true); - errorWrap.Properties.Add(note); + errorWrap.WriteStream = WriteStreamType.Error; // If this is an error pipe for a hosting application (i.e.: no downstream cmdlet), // and we are logging, then create a temporary PowerShell to log the error. diff --git a/src/System.Management.Automation/engine/serialization.cs b/src/System.Management.Automation/engine/serialization.cs index b69f2434f4..048a7491d7 100644 --- a/src/System.Management.Automation/engine/serialization.cs +++ b/src/System.Management.Automation/engine/serialization.cs @@ -1100,7 +1100,7 @@ namespace System.Management.Automation moSource = source as PSObject; } - if (moSource != null && !moSource.immediateBaseObjectIsEmpty) + if (moSource != null && !moSource.ImmediateBaseObjectIsEmpty) { // check if source is of type secure string secureString = moSource.ImmediateBaseObject as SecureString; @@ -1172,7 +1172,7 @@ namespace System.Management.Automation bool sourceHandled = false; PSObject moSource = source as PSObject; - if (moSource != null && !moSource.immediateBaseObjectIsEmpty) + if (moSource != null && !moSource.ImmediateBaseObjectIsEmpty) { // Check if baseObject is primitive known type object baseObject = moSource.ImmediateBaseObject; @@ -1203,7 +1203,7 @@ namespace System.Management.Automation IDictionary dictionary = null; // If passed in object is PSObject with no baseobject, return false. - if (mshSource != null && mshSource.immediateBaseObjectIsEmpty) + if (mshSource != null && mshSource.ImmediateBaseObjectIsEmpty) { return false; } @@ -1252,7 +1252,7 @@ namespace System.Management.Automation // So on roundtrip it will show up as List. // We serialize properties of enumerable and on deserialization mark the object as Deserialized. // So if object is marked deserialized, we should write properties. - if (ct == ContainerType.Enumerable || (mshSource != null && mshSource.isDeserialized)) + if (ct == ContainerType.Enumerable || (mshSource != null && mshSource.IsDeserialized)) { PSObject sourceAsPSObject = PSObject.AsPSObject(source); PSMemberInfoInternalCollection specificPropertiesToSerialize = SerializationUtilities.GetSpecificPropertiesToSerialize(sourceAsPSObject, AllPropertiesCollection, _typeTable); @@ -1460,7 +1460,7 @@ namespace System.Management.Automation bool isPSObject = false; bool isCimInstance = false; - if (!mshSource.immediateBaseObjectIsEmpty) + if (!mshSource.ImmediateBaseObjectIsEmpty) { do // false loop { @@ -1495,7 +1495,7 @@ namespace System.Management.Automation bool writeToString = true; if (mshSource.ToStringFromDeserialization == null) // continue to write ToString from deserialized objects, but... { - if (mshSource.immediateBaseObjectIsEmpty) // ... don't write ToString for property bags + if (mshSource.ImmediateBaseObjectIsEmpty) // ... don't write ToString for property bags { writeToString = false; } @@ -2327,7 +2327,7 @@ namespace System.Management.Automation if (0 != (_context.options & SerializationOptions.PreserveSerializationSettingOfOriginal)) { - if ((pso.isDeserialized) && (depth <= 0)) + if ((pso.IsDeserialized) && (depth <= 0)) { return 1; } @@ -3463,9 +3463,9 @@ namespace System.Management.Automation } // process properties that were originally "adapted" properties - if (deserializedObject.adaptedMembers != null) + if (deserializedObject.AdaptedMembers != null) { - foreach (PSMemberInfo deserializedMemberInfo in deserializedObject.adaptedMembers) + foreach (PSMemberInfo deserializedMemberInfo in deserializedObject.AdaptedMembers) { PSPropertyInfo deserializedProperty = deserializedMemberInfo as PSPropertyInfo; if (deserializedProperty == null) @@ -3495,7 +3495,7 @@ namespace System.Management.Automation } // skip adapted properties - if ((deserializedObject.adaptedMembers != null) && (deserializedObject.adaptedMembers[deserializedProperty.Name] != null)) + if ((deserializedObject.AdaptedMembers != null) && (deserializedObject.AdaptedMembers[deserializedProperty.Name] != null)) { continue; } @@ -3643,7 +3643,7 @@ namespace System.Management.Automation else if (IsNextElement(SerializationStrings.ToStringElementTag)) { dso.ToStringFromDeserialization = ReadDecodedElementString(SerializationStrings.ToStringElementTag); - dso.InstanceMembers.Add(PSObject.dotNetInstanceAdapter.GetDotNetMethod(dso, "ToString")); + dso.InstanceMembers.Add(PSObject.DotNetInstanceAdapter.GetDotNetMethod(dso, "ToString")); PSGetMemberBinder.SetHasInstanceMember("ToString"); // Fix for Win8:75437 // The TokenText property is used in type conversion and it is not being populated during deserialization @@ -3822,15 +3822,15 @@ namespace System.Management.Automation // Since we are adding baseobject properties as propertybag, // mark the object as deserialized. - dso.isDeserialized = true; - dso.adaptedMembers = new PSMemberInfoInternalCollection(); + dso.IsDeserialized = true; + dso.AdaptedMembers = new PSMemberInfoInternalCollection(); // Add the GetType method to the instance members, so that it works on deserialized psobjects - dso.InstanceMembers.Add(PSObject.dotNetInstanceAdapter.GetDotNetMethod(dso, "GetType")); + dso.InstanceMembers.Add(PSObject.DotNetInstanceAdapter.GetDotNetMethod(dso, "GetType")); PSGetMemberBinder.SetHasInstanceMember("GetType"); // Set Clr members to a collection which is empty - dso.clrMembers = new PSMemberInfoInternalCollection(); + dso.ClrMembers = new PSMemberInfoInternalCollection(); if (ReadStartElementAndHandleEmpty(SerializationStrings.AdapterProperties)) { @@ -3840,7 +3840,7 @@ namespace System.Management.Automation string property = ReadNameAttribute(); object value = ReadOneObject(); PSProperty prop = new PSProperty(property, value); - dso.adaptedMembers.Add(prop); + dso.AdaptedMembers.Add(prop); } ReadEndElement(); diff --git a/test/xUnit/csharp/test_PSObject.cs b/test/xUnit/csharp/test_PSObject.cs new file mode 100644 index 0000000000..46497065fd --- /dev/null +++ b/test/xUnit/csharp/test_PSObject.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Management.Automation.Language; +using Xunit; + +namespace PSTests.Parallel +{ + using System.Linq; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + using System.Xml; + + using Microsoft.Management.Infrastructure; + + public static class PSObjectTests + { + [Fact] + public static void TestEmptyObjectHasNoProperty() + { + var psObject = new PSObject(); + var actual = psObject.GetFirstPropertyOrDefault(name => true); + Assert.Null(actual); + } + + [Fact] + public static void TestWrappedDateTimeHasReflectedMember() + { + var psObject = new PSObject(DateTime.Now); + var member = psObject.GetFirstPropertyOrDefault(name => name == "DayOfWeek"); + Assert.NotNull(member); + Assert.Equal("DayOfWeek", member.Name); + } + + [Fact] + public static void TestAdaptedMember() + { + var psObject = new PSObject(DateTime.Now); + psObject.Members.Add(new PSNoteProperty("NewMember", "AValue")); + var member = psObject.GetFirstPropertyOrDefault(name => name == "NewMember"); + Assert.NotNull(member); + Assert.Equal("NewMember", member.Name); + } + + [Fact] + public static void TestShadowedMember() + { + var psObject = new PSObject(DateTime.Now); + psObject.Members.Add(new PSNoteProperty("DayOfWeek", "AValue")); + var member = psObject.GetFirstPropertyOrDefault(name => name == "DayOfWeek"); + Assert.NotNull(member); + Assert.Equal("DayOfWeek", member.Name); + Assert.Equal("AValue", member.Value); + } + + [Fact] + public static void TestMemberSetIsNotProperty() + { + var psObject = new PSObject(DateTime.Now); + var psNoteProperty = new PSNoteProperty("NewMember", "AValue"); + psObject.Members.Add(psNoteProperty); + psObject.Members.Add(new PSMemberSet("NewMemberSet", new[] { psNoteProperty })); + + var member = psObject.GetFirstPropertyOrDefault(name => name == "NewMemberSet"); + Assert.Null(member); + } + + [Fact] + public static void TestMemberSet() + { + var psObject = new PSObject(DateTime.Now); + var psNoteProperty = new PSNoteProperty("NewMember", "AValue"); + psObject.Members.Add(psNoteProperty); + psObject.Members.Add(new PSMemberSet("NewMemberSet", new[] { psNoteProperty })); + + var member = psObject.Members.FirstOrDefault(name => name == "NewMemberSet"); + Assert.NotNull(member); + Assert.Equal("NewMemberSet", member.Name); + } + + [Fact] + public static void TextXmlElementMember() + { + var doc = new XmlDocument(); + var root = doc.CreateElement("root"); + doc.AppendChild(root); + var firstChild = doc.CreateElement("elem1"); + root.AppendChild(firstChild); + root.InsertAfter(doc.CreateElement("elem2"), firstChild); + + var psObject = new PSObject(root); + var member = psObject.GetFirstPropertyOrDefault(name => name.StartsWith("elem")); + Assert.Equal("elem1", member.Name); + } + + [Fact] + public static void TextXmlAttributeMember() + { + var doc = new XmlDocument(); + var root = doc.CreateElement("root"); + doc.AppendChild(root); + root.SetAttribute("attr", "value"); + root.AppendChild(doc.CreateElement("elem")); + + var psObject = new PSObject(root); + var member = psObject.GetFirstPropertyOrDefault(name => name.StartsWith("attr")); + Assert.Equal("attr", member.Name); + } + + [SkippableFact] + public static void TestCimInstanceProperty() + { + Skip.IfNot(Platform.IsWindows); + var iss = InitialSessionState.CreateDefault2(); + iss.Commands.Add(new SessionStateCmdletEntry("Get-CimInstance", typeof(Microsoft.Management.Infrastructure.CimCmdlets.GetCimInstanceCommand), null)); + using (var ps = PowerShell.Create(iss)) + { + ps.AddCommand("Get-CimInstance").AddParameter("ClassName", "Win32_BIOS"); + var res = ps.Invoke().FirstOrDefault(); + Assert.NotNull(res); + var member = res.GetFirstPropertyOrDefault(name => name == "Name"); + Assert.NotNull(member); + } + } + } +} diff --git a/test/xUnit/xUnit.tests.csproj b/test/xUnit/xUnit.tests.csproj index 227405930e..ff75f65925 100644 --- a/test/xUnit/xUnit.tests.csproj +++ b/test/xUnit/xUnit.tests.csproj @@ -1,6 +1,6 @@  - + PowerShell xUnit Tests @@ -16,9 +16,10 @@ - - - + + + +