PowerShell/src/System.Management.Automation/engine/CommandInfo.cs
xtqqczze 883ca98dd7
Seal private classes (#15725)
* Seal private classes

* Fix CS0509

* Fix CS0628
2021-07-19 14:09:12 +05:00

1004 lines
36 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Text;
using Microsoft.PowerShell.Commands;
namespace System.Management.Automation
{
/// <summary>
/// Defines the types of commands that MSH can execute.
/// </summary>
[Flags]
public enum CommandTypes
{
/// <summary>
/// Aliases create a name that refers to other command types.
/// </summary>
/// <remarks>
/// Aliases are only persisted within the execution of a single engine.
/// </remarks>
Alias = 0x0001,
/// <summary>
/// Script functions that are defined by a script block.
/// </summary>
/// <remarks>
/// Functions are only persisted within the execution of a single engine.
/// </remarks>
Function = 0x0002,
/// <summary>
/// Script filters that are defined by a script block.
/// </summary>
/// <remarks>
/// Filters are only persisted within the execution of a single engine.
/// </remarks>
Filter = 0x0004,
/// <summary>
/// A cmdlet.
/// </summary>
Cmdlet = 0x0008,
/// <summary>
/// An MSH script (*.ps1 file)
/// </summary>
ExternalScript = 0x0010,
/// <summary>
/// Any existing application (can be console or GUI).
/// </summary>
/// <remarks>
/// An application can have any extension that can be executed either directly through CreateProcess
/// or indirectly through ShellExecute.
/// </remarks>
Application = 0x0020,
/// <summary>
/// A script that is built into the runspace configuration.
/// </summary>
Script = 0x0040,
/// <summary>
/// A Configuration.
/// </summary>
Configuration = 0x0100,
/// <summary>
/// All possible command types.
/// </summary>
/// <remarks>
/// Note, a CommandInfo instance will never specify
/// All as its CommandType but All can be used when filtering the CommandTypes.
/// </remarks>
All = Alias | Function | Filter | Cmdlet | Script | ExternalScript | Application | Configuration,
}
/// <summary>
/// The base class for the information about commands. Contains the basic information about
/// the command, like name and type.
/// </summary>
public abstract class CommandInfo : IHasSessionStateEntryVisibility
{
#region ctor
/// <summary>
/// Creates an instance of the CommandInfo class with the specified name and type.
/// </summary>
/// <param name="name">
/// The name of the command.
/// </param>
/// <param name="type">
/// The type of the command.
/// </param>
/// <exception cref="ArgumentNullException">
/// If <paramref name="name"/> is null.
/// </exception>
internal CommandInfo(string name, CommandTypes type)
{
// The name can be empty for functions and filters but it
// can't be null
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
Name = name;
CommandType = type;
}
/// <summary>
/// Creates an instance of the CommandInfo class with the specified name and type.
/// </summary>
/// <param name="name">
/// The name of the command.
/// </param>
/// <param name="type">
/// The type of the command.
/// </param>
/// <param name="context">
/// The execution context for the command.
/// </param>
/// <exception cref="ArgumentNullException">
/// If <paramref name="name"/> is null.
/// </exception>
internal CommandInfo(string name, CommandTypes type, ExecutionContext context)
: this(name, type)
{
this.Context = context;
}
/// <summary>
/// This is a copy constructor, used primarily for get-command.
/// </summary>
internal CommandInfo(CommandInfo other)
{
// Computed fields not copied:
// this._externalCommandMetadata = other._externalCommandMetadata;
// this._moduleName = other._moduleName;
// this.parameterSets = other.parameterSets;
this.Module = other.Module;
_visibility = other._visibility;
Arguments = other.Arguments;
this.Context = other.Context;
Name = other.Name;
CommandType = other.CommandType;
CopiedCommand = other;
this.DefiningLanguageMode = other.DefiningLanguageMode;
}
/// <summary>
/// This is a copy constructor, used primarily for get-command.
/// </summary>
internal CommandInfo(string name, CommandInfo other)
: this(other)
{
Name = name;
}
#endregion ctor
/// <summary>
/// Gets the name of the command.
/// </summary>
public string Name { get; private set; } = string.Empty;
// Name
/// <summary>
/// Gets the type of the command.
/// </summary>
public CommandTypes CommandType { get; private set; } = CommandTypes.Application;
// CommandType
/// <summary>
/// Gets the source of the command (shown by default in Get-Command)
/// </summary>
public virtual string Source { get { return this.ModuleName; } }
/// <summary>
/// Gets the source version (shown by default in Get-Command)
/// </summary>
public virtual Version Version
{
get
{
if (_version == null)
{
if (Module != null)
{
if (Module.Version.Equals(new Version(0, 0)))
{
if (Module.Path.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase))
{
// Manifest module (.psd1)
Module.SetVersion(ModuleIntrinsics.GetManifestModuleVersion(Module.Path));
}
else if (Module.Path.EndsWith(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) ||
Module.Path.EndsWith(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase))
{
// Binary module (.dll or .exe)
Module.SetVersion(AssemblyName.GetAssemblyName(Module.Path).Version);
}
}
_version = Module.Version;
}
}
return _version;
}
}
private Version _version;
/// <summary>
/// The execution context this command will run in.
/// </summary>
internal ExecutionContext Context
{
get
{
return _context;
}
set
{
_context = value;
if ((value != null) && !this.DefiningLanguageMode.HasValue)
{
this.DefiningLanguageMode = value.LanguageMode;
}
}
}
private ExecutionContext _context;
/// <summary>
/// The language mode that was in effect when this alias was defined.
/// </summary>
internal PSLanguageMode? DefiningLanguageMode { get; set; }
internal virtual HelpCategory HelpCategory
{
get { return HelpCategory.None; }
}
internal CommandInfo CopiedCommand { get; set; }
/// <summary>
/// Internal interface to change the type of a CommandInfo object.
/// </summary>
/// <param name="newType"></param>
internal void SetCommandType(CommandTypes newType)
{
CommandType = newType;
}
/// <summary>
/// A string representing the definition of the command.
/// </summary>
/// <remarks>
/// This is overridden by derived classes to return specific
/// information for the command type.
/// </remarks>
public abstract string Definition { get; }
/// <summary>
/// This is required for renaming aliases, functions, and filters.
/// </summary>
/// <param name="newName">
/// The new name for the command.
/// </param>
/// <exception cref="ArgumentException">
/// If <paramref name="newName"/> is null or empty.
/// </exception>
internal void Rename(string newName)
{
if (string.IsNullOrEmpty(newName))
{
throw new ArgumentNullException(nameof(newName));
}
Name = newName;
}
/// <summary>
/// For diagnostic purposes.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return ModuleCmdletBase.AddPrefixToCommandName(Name, Prefix);
}
/// <summary>
/// Indicates if the command is to be allowed to be executed by a request
/// external to the runspace.
/// </summary>
public virtual SessionStateEntryVisibility Visibility
{
get
{
return CopiedCommand == null ? _visibility : CopiedCommand.Visibility;
}
set
{
if (CopiedCommand == null)
{
_visibility = value;
}
else
{
CopiedCommand.Visibility = value;
}
if (value == SessionStateEntryVisibility.Private && Module != null)
{
Module.ModuleHasPrivateMembers = true;
}
}
}
private SessionStateEntryVisibility _visibility = SessionStateEntryVisibility.Public;
/// <summary>
/// Return a CommandMetadata instance that is never exposed publicly.
/// </summary>
internal virtual CommandMetadata CommandMetadata
{
get
{
throw new InvalidOperationException();
}
}
/// <summary>
/// Returns the syntax of a command.
/// </summary>
internal virtual string Syntax
{
get { return Definition; }
}
/// <summary>
/// The module name of this command. It will be empty for commands
/// not imported from either a module or snapin.
/// </summary>
public string ModuleName
{
get
{
string moduleName = null;
if (Module != null && !string.IsNullOrEmpty(Module.Name))
{
moduleName = Module.Name;
}
else
{
CmdletInfo cmdlet = this as CmdletInfo;
if (cmdlet != null && cmdlet.PSSnapIn != null)
{
moduleName = cmdlet.PSSnapInName;
}
}
if (moduleName == null)
return string.Empty;
return moduleName;
}
}
/// <summary>
/// The module that defines this cmdlet. This will be null for commands
/// that are not defined in the context of a module.
/// </summary>
public PSModuleInfo Module { get; internal set; }
/// <summary>
/// The remoting capabilities of this cmdlet, when exposed in a context
/// with ambient remoting.
/// </summary>
public RemotingCapability RemotingCapability
{
get
{
try
{
return ExternalCommandMetadata.RemotingCapability;
}
catch (PSNotSupportedException)
{
// Thrown on an alias that hasn't been resolved yet (i.e.: in a module that
// hasn't been loaded.) Assume the default.
return RemotingCapability.PowerShell;
}
}
}
/// <summary>
/// True if the command has dynamic parameters, false otherwise.
/// </summary>
internal virtual bool ImplementsDynamicParameters
{
get { return false; }
}
/// <summary>
/// Constructs the MergedCommandParameterMetadata, using any arguments that
/// may have been specified so that dynamic parameters can be determined, if any.
/// </summary>
/// <returns></returns>
private MergedCommandParameterMetadata GetMergedCommandParameterMetadataSafely()
{
if (_context == null)
return null;
MergedCommandParameterMetadata result;
if (_context != LocalPipeline.GetExecutionContextFromTLS())
{
// In the normal case, _context is from the thread we're on, and we won't get here.
// But, if it's not, we can't safely get the parameter metadata without running on
// on the correct thread, because that thread may be busy doing something else.
// One of the things we do here is change the current scope in execution context,
// that can mess up the runspace our CommandInfo object came from.
var runspace = (RunspaceBase)_context.CurrentRunspace;
if (runspace.CanRunActionInCurrentPipeline())
{
GetMergedCommandParameterMetadata(out result);
}
else
{
_context.Events.SubscribeEvent(
source: null,
eventName: PSEngineEvent.GetCommandInfoParameterMetadata,
sourceIdentifier: PSEngineEvent.GetCommandInfoParameterMetadata,
data: null,
handlerDelegate: new PSEventReceivedEventHandler(OnGetMergedCommandParameterMetadataSafelyEventHandler),
supportEvent: true,
forwardEvent: false,
shouldQueueAndProcessInExecutionThread: true,
maxTriggerCount: 1);
var eventArgs = new GetMergedCommandParameterMetadataSafelyEventArgs();
_context.Events.GenerateEvent(
sourceIdentifier: PSEngineEvent.GetCommandInfoParameterMetadata,
sender: null,
args: new[] { eventArgs },
extraData: null,
processInCurrentThread: true,
waitForCompletionInCurrentThread: true);
if (eventArgs.Exception != null)
{
// An exception happened on a different thread, rethrow it here on the correct thread.
eventArgs.Exception.Throw();
}
return eventArgs.Result;
}
}
GetMergedCommandParameterMetadata(out result);
return result;
}
private sealed class GetMergedCommandParameterMetadataSafelyEventArgs : EventArgs
{
public MergedCommandParameterMetadata Result;
public ExceptionDispatchInfo Exception;
}
private void OnGetMergedCommandParameterMetadataSafelyEventHandler(object sender, PSEventArgs args)
{
var eventArgs = args.SourceEventArgs as GetMergedCommandParameterMetadataSafelyEventArgs;
if (eventArgs != null)
{
try
{
// Save the result in our event args as the return value.
GetMergedCommandParameterMetadata(out eventArgs.Result);
}
catch (Exception e)
{
// Save the exception so we can throw it on the correct thread.
eventArgs.Exception = ExceptionDispatchInfo.Capture(e);
}
}
}
private void GetMergedCommandParameterMetadata(out MergedCommandParameterMetadata result)
{
// MSFT:652277 - When invoking cmdlets or advanced functions, MyInvocation.MyCommand.Parameters do not contain the dynamic parameters
// When trying to get parameter metadata for a CommandInfo that has dynamic parameters, a new CommandProcessor will be
// created out of this CommandInfo and the parameter binding algorithm will be invoked. However, when this happens via
// 'MyInvocation.MyCommand.Parameter', it's actually retrieving the parameter metadata of the same cmdlet that is currently
// running. In this case, information about the specified parameters are not kept around in 'MyInvocation.MyCommand', so
// going through the binding algorithm again won't give us the metadata about the dynamic parameters that should have been
// discovered already.
// The fix is to check if the CommandInfo is actually representing the currently running cmdlet. If so, the retrieval of parameter
// metadata actually stems from the running of the same cmdlet. In this case, we can just use the current CommandProcessor to
// retrieve all bindable parameters, which should include the dynamic parameters that have been discovered already.
CommandProcessor processor;
if (Context.CurrentCommandProcessor != null && Context.CurrentCommandProcessor.CommandInfo == this)
{
// Accessing the parameters within the invocation of the same cmdlet/advanced function.
processor = (CommandProcessor)Context.CurrentCommandProcessor;
}
else
{
IScriptCommandInfo scriptCommand = this as IScriptCommandInfo;
processor = scriptCommand != null
? new CommandProcessor(scriptCommand, _context, useLocalScope: true, fromScriptFile: false,
sessionState: scriptCommand.ScriptBlock.SessionStateInternal ?? Context.EngineSessionState)
: new CommandProcessor((CmdletInfo)this, _context) { UseLocalScope = true };
ParameterBinderController.AddArgumentsToCommandProcessor(processor, Arguments);
CommandProcessorBase oldCurrentCommandProcessor = Context.CurrentCommandProcessor;
try
{
Context.CurrentCommandProcessor = processor;
processor.SetCurrentScopeToExecutionScope();
processor.CmdletParameterBinderController.BindCommandLineParametersNoValidation(processor.arguments);
}
catch (ParameterBindingException)
{
// Ignore the binding exception if no argument is specified
if (processor.arguments.Count > 0)
{
throw;
}
}
finally
{
Context.CurrentCommandProcessor = oldCurrentCommandProcessor;
processor.RestorePreviousScope();
}
}
result = processor.CmdletParameterBinderController.BindableParameters;
}
/// <summary>
/// Return the parameters for this command.
/// </summary>
public virtual Dictionary<string, ParameterMetadata> Parameters
{
get
{
Dictionary<string, ParameterMetadata> result = new Dictionary<string, ParameterMetadata>(StringComparer.OrdinalIgnoreCase);
if (ImplementsDynamicParameters && Context != null)
{
MergedCommandParameterMetadata merged = GetMergedCommandParameterMetadataSafely();
foreach (KeyValuePair<string, MergedCompiledCommandParameter> pair in merged.BindableParameters)
{
result.Add(pair.Key, new ParameterMetadata(pair.Value.Parameter));
}
// Don't cache this data...
return result;
}
return ExternalCommandMetadata.Parameters;
}
}
internal CommandMetadata ExternalCommandMetadata
{
get { return _externalCommandMetadata ??= new CommandMetadata(this, true); }
set { _externalCommandMetadata = value; }
}
private CommandMetadata _externalCommandMetadata;
/// <summary>
/// Resolves a full, shortened, or aliased parameter name to the actual
/// cmdlet parameter name, using PowerShell's standard parameter resolution
/// algorithm.
/// </summary>
/// <param name="name">The name of the parameter to resolve.</param>
/// <returns>The parameter that matches this name.</returns>
public ParameterMetadata ResolveParameter(string name)
{
MergedCommandParameterMetadata merged = GetMergedCommandParameterMetadataSafely();
MergedCompiledCommandParameter result = merged.GetMatchingParameter(name, true, true, null);
return this.Parameters[result.Parameter.Name];
}
/// <summary>
/// Gets the information about the parameters and parameter sets for
/// this command.
/// </summary>
public ReadOnlyCollection<CommandParameterSetInfo> ParameterSets
{
get
{
if (_parameterSets == null)
{
Collection<CommandParameterSetInfo> parameterSetInfo =
GenerateCommandParameterSetInfo();
_parameterSets = new ReadOnlyCollection<CommandParameterSetInfo>(parameterSetInfo);
}
return _parameterSets;
}
}
internal ReadOnlyCollection<CommandParameterSetInfo> _parameterSets;
/// <summary>
/// A possibly incomplete or even incorrect list of types the command could return.
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
public abstract ReadOnlyCollection<PSTypeName> OutputType { get; }
/// <summary>
/// Specifies whether this command was imported from a module or not.
/// This is used in Get-Command to figure out which of the commands in module session state were imported.
/// </summary>
internal bool IsImported { get; set; } = false;
/// <summary>
/// The prefix that was used when importing this command.
/// </summary>
internal string Prefix { get; set; } = string.Empty;
/// <summary>
/// Create a copy of commandInfo for GetCommandCommand so that we can generate parameter
/// sets based on an argument list (so we can get the dynamic parameters.)
/// </summary>
internal virtual CommandInfo CreateGetCommandCopy(object[] argumentList)
{
throw new InvalidOperationException();
}
/// <summary>
/// Generates the parameter and parameter set info from the cmdlet metadata.
/// </summary>
/// <returns>
/// A collection of CommandParameterSetInfo representing the cmdlet metadata.
/// </returns>
/// <exception cref="ArgumentException">
/// The type name is invalid or the length of the type name
/// exceeds 1024 characters.
/// </exception>
/// <exception cref="System.Security.SecurityException">
/// The caller does not have the required permission to load the assembly
/// or create the type.
/// </exception>
/// <exception cref="ParsingMetadataException">
/// If more than int.MaxValue parameter-sets are defined for the command.
/// </exception>
/// <exception cref="MetadataException">
/// If a parameter defines the same parameter-set name multiple times.
/// If the attributes could not be read from a property or field.
/// </exception>
internal Collection<CommandParameterSetInfo> GenerateCommandParameterSetInfo()
{
Collection<CommandParameterSetInfo> result;
if (IsGetCommandCopy && ImplementsDynamicParameters)
{
result = GetParameterMetadata(CommandMetadata, GetMergedCommandParameterMetadataSafely());
}
else
{
result = GetCacheableMetadata(CommandMetadata);
}
return result;
}
/// <summary>
/// Gets or sets whether this CmdletInfo instance is a copy used for get-command.
/// If true, and the cmdlet supports dynamic parameters, it means that the dynamic
/// parameter metadata will be merged into the parameter set information.
/// </summary>
internal bool IsGetCommandCopy { get; set; }
/// <summary>
/// Gets or sets the command line arguments/parameters that were specified
/// which will allow for the dynamic parameters to be retrieved and their
/// metadata merged into the parameter set information.
/// </summary>
internal object[] Arguments { get; set; }
internal static Collection<CommandParameterSetInfo> GetCacheableMetadata(CommandMetadata metadata)
{
return GetParameterMetadata(metadata, metadata.StaticCommandParameterMetadata);
}
internal static Collection<CommandParameterSetInfo> GetParameterMetadata(CommandMetadata metadata, MergedCommandParameterMetadata parameterMetadata)
{
Collection<CommandParameterSetInfo> result = new Collection<CommandParameterSetInfo>();
if (parameterMetadata != null)
{
if (parameterMetadata.ParameterSetCount == 0)
{
const string parameterSetName = ParameterAttribute.AllParameterSets;
result.Add(
new CommandParameterSetInfo(
parameterSetName,
false,
uint.MaxValue,
parameterMetadata));
}
else
{
int parameterSetCount = parameterMetadata.ParameterSetCount;
for (int index = 0; index < parameterSetCount; ++index)
{
uint currentFlagPosition = (uint)0x1 << index;
// Get the parameter set name
string parameterSetName = parameterMetadata.GetParameterSetName(currentFlagPosition);
// Is the parameter set the default?
bool isDefaultParameterSet = (currentFlagPosition & metadata.DefaultParameterSetFlag) != 0;
result.Add(
new CommandParameterSetInfo(
parameterSetName,
isDefaultParameterSet,
currentFlagPosition,
parameterMetadata));
}
}
}
return result;
}
}
/// <summary>
/// Represents <see cref="System.Type"/>, but can be used where a real type
/// might not be available, in which case the name of the type can be used.
/// </summary>
public class PSTypeName
{
/// <summary>
/// This constructor is used when the type exists and is currently loaded.
/// </summary>
/// <param name="type">The type.</param>
public PSTypeName(Type type)
{
_type = type;
if (_type != null)
{
Name = _type.FullName;
}
}
/// <summary>
/// This constructor is used when the type may not exist, or is not loaded.
/// </summary>
/// <param name="name">The name of the type.</param>
public PSTypeName(string name)
{
Name = name;
_type = null;
}
/// <summary>
/// This constructor is used when the creating a PSObject with a custom typename.
/// </summary>
/// <param name="name">The name of the type.</param>
/// <param name="type">The real type.</param>
public PSTypeName(string name, Type type)
{
Name = name;
_type = type;
}
/// <summary>
/// This constructor is used when the type is defined in PowerShell.
/// </summary>
/// <param name="typeDefinitionAst">The type definition from the ast.</param>
public PSTypeName(TypeDefinitionAst typeDefinitionAst)
{
if (typeDefinitionAst == null)
{
throw PSTraceSource.NewArgumentNullException(nameof(typeDefinitionAst));
}
TypeDefinitionAst = typeDefinitionAst;
Name = typeDefinitionAst.Name;
}
/// <summary>
/// This constructor creates a type from a ITypeName.
/// </summary>
public PSTypeName(ITypeName typeName)
{
if (typeName == null)
{
throw PSTraceSource.NewArgumentNullException(nameof(typeName));
}
_type = typeName.GetReflectionType();
if (_type != null)
{
Name = _type.FullName;
}
else
{
var t = typeName as TypeName;
if (t != null && t._typeDefinitionAst != null)
{
TypeDefinitionAst = t._typeDefinitionAst;
Name = TypeDefinitionAst.Name;
}
else
{
_type = null;
Name = typeName.FullName;
}
}
}
/// <summary>
/// Return the name of the type.
/// </summary>
public string Name { get; }
/// <summary>
/// Return the type with metadata, or null if the type is not loaded.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")]
public Type Type
{
get
{
if (!_typeWasCalculated)
{
if (_type == null)
{
if (TypeDefinitionAst != null)
{
_type = TypeDefinitionAst.Type;
}
else
{
TypeResolver.TryResolveType(Name, out _type);
}
}
if (_type == null)
{
// We ignore the exception.
if (Name != null &&
Name.StartsWith('[') &&
Name.EndsWith(']'))
{
string tmp = Name.Substring(1, Name.Length - 2);
TypeResolver.TryResolveType(tmp, out _type);
}
}
_typeWasCalculated = true;
}
return _type;
}
}
private Type _type;
/// <summary>
/// When a type is defined by PowerShell, the ast for that type.
/// </summary>
public TypeDefinitionAst TypeDefinitionAst { get; }
private bool _typeWasCalculated;
/// <summary>
/// Returns a String that represents the current PSTypeName.
/// </summary>
/// <returns>String that represents the current PSTypeName.</returns>
public override string ToString()
{
return Name ?? string.Empty;
}
}
[DebuggerDisplay("{PSTypeName} {Name}")]
internal readonly struct PSMemberNameAndType
{
public readonly string Name;
public readonly PSTypeName PSTypeName;
public readonly object Value;
public PSMemberNameAndType(string name, PSTypeName typeName, object value = null)
{
Name = name;
PSTypeName = typeName;
Value = value;
}
}
/// <summary>
/// Represents dynamic types such as <see cref="System.Management.Automation.PSObject"/>,
/// but can be used where a real type might not be available, in which case the name of the type can be used.
/// The type encodes the members of dynamic objects in the type name.
/// </summary>
internal sealed class PSSyntheticTypeName : PSTypeName
{
internal static PSSyntheticTypeName Create(string typename, IList<PSMemberNameAndType> membersTypes) => Create(new PSTypeName(typename), membersTypes);
internal static PSSyntheticTypeName Create(Type type, IList<PSMemberNameAndType> membersTypes) => Create(new PSTypeName(type), membersTypes);
internal static PSSyntheticTypeName Create(PSTypeName typename, IList<PSMemberNameAndType> membersTypes)
{
var typeName = GetMemberTypeProjection(typename.Name, membersTypes);
var members = new List<PSMemberNameAndType>();
members.AddRange(membersTypes);
members.Sort(static (c1, c2) => string.Compare(c1.Name, c2.Name, StringComparison.OrdinalIgnoreCase));
return new PSSyntheticTypeName(typeName, typename.Type, members);
}
private PSSyntheticTypeName(string typeName, Type type, IList<PSMemberNameAndType> membersTypes)
: base(typeName, type)
{
Members = membersTypes;
if (type != typeof(PSObject))
{
return;
}
for (int i = 0; i < Members.Count; i++)
{
var psMemberNameAndType = Members[i];
if (IsPSTypeName(psMemberNameAndType))
{
Members.RemoveAt(i);
break;
}
}
}
private static bool IsPSTypeName(in PSMemberNameAndType member) => member.Name.Equals(nameof(PSTypeName), StringComparison.OrdinalIgnoreCase);
private static string GetMemberTypeProjection(string typename, IList<PSMemberNameAndType> members)
{
if (typename == typeof(PSObject).FullName)
{
foreach (var mem in members)
{
if (IsPSTypeName(mem))
{
typename = mem.Value.ToString();
}
}
}
var builder = new StringBuilder(typename, members.Count * 7);
builder.Append('#');
foreach (var m in members.OrderBy(static m => m.Name))
{
if (!IsPSTypeName(m))
{
builder.Append(m.Name).Append(':');
}
}
builder.Length--;
return builder.ToString();
}
public IList<PSMemberNameAndType> Members { get; }
}
#nullable enable
internal interface IScriptCommandInfo
{
ScriptBlock ScriptBlock { get; }
}
}