/
githubmirror
/
PowerShell
Обзор
Документация
Войти
/
githubmirror
/
PowerShell
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/System.Management.Automation/engine/Modules/PSModuleInfo.cs
1 650 строк
59 KB
CarloToso
Fix codefactor if part 5 (#19286)
16 мар 2023, 20:02
Не верифицирован
16 мар 2023, 20:02
69e9b43
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; using System.Reflection; using Microsoft.PowerShell.Commands; using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// <summary> /// Class describing a PowerShell module... /// </summary> public sealed class PSModuleInfo { internal const string DynamicModulePrefixString = "__DynamicModule_"; private static readonly ReadOnlyDictionary<string, TypeDefinitionAst> s_emptyTypeDefinitionDictionary = new ReadOnlyDictionary<string, TypeDefinitionAst>(new Dictionary<string, TypeDefinitionAst>(StringComparer.OrdinalIgnoreCase)); // This dictionary doesn't include ExportedTypes from nested modules. private ReadOnlyDictionary<string, TypeDefinitionAst> _exportedTypeDefinitionsNoNested { get; set; } private static readonly HashSet<string> s_scriptModuleExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { StringLiterals.PowerShellModuleFileExtension, }; internal static void SetDefaultDynamicNameAndPath(PSModuleInfo module) { string gs = Guid.NewGuid().ToString(); module.Path = gs; module.Name = "__DynamicModule_" + gs; } /// <summary> /// This object describes a PowerShell module... /// </summary> /// <param name="path">The absolute path to the module.</param> /// <param name="context">The execution context for this engine instance.</param> /// <param name="sessionState">The module's sessionstate object - this may be null if the module is a dll.</param> internal PSModuleInfo(string path, ExecutionContext context, SessionState sessionState) : this(null, path, context, sessionState) { } /// <summary> /// This object describes a PowerShell module... /// </summary> /// <param name="name">The name to use for the module. If null, get it from the path name.</param> /// <param name="path">The absolute path to the module.</param> /// <param name="context">The execution context for this engine instance.</param> /// <param name="sessionState">The module's sessionstate object - this may be null if the module is a dll.</param> /// <param name="languageMode">Language mode for script based modules.</param> internal PSModuleInfo(string name, string path, ExecutionContext context, SessionState sessionState, PSLanguageMode? languageMode) : this(name, path, context, sessionState) { LanguageMode = languageMode; } /// <summary> /// This object describes a PowerShell module... /// </summary> /// <param name="name">The name to use for the module. If null, get it from the path name.</param> /// <param name="path">The absolute path to the module.</param> /// <param name="context">The execution context for this engine instance.</param> /// <param name="sessionState">The module's sessionstate object - this may be null if the module is a dll.</param> internal PSModuleInfo(string name, string path, ExecutionContext context, SessionState sessionState) { if (path != null) { string resolvedPath = ModuleCmdletBase.GetResolvedPath(path, context); // The resolved path might be null if we're building a dynamic module and the path // is just a GUID, not an actual path that can be resolved. Path = resolvedPath ?? path; } SessionState = sessionState; if (sessionState != null) { sessionState.Internal.Module = this; } // Use the name of basename of the path as the module name if no module name is supplied. Name = name ?? ModuleIntrinsics.GetModuleName(Path); } /// <summary> /// Default constructor to create an empty module info. /// </summary> public PSModuleInfo(bool linkToGlobal) : this(LocalPipeline.GetExecutionContextFromTLS(), linkToGlobal) { } /// <summary> /// Default constructor to create an empty module info. /// </summary> internal PSModuleInfo(ExecutionContext context, bool linkToGlobal) { if (context == null) throw new InvalidOperationException("PSModuleInfo"); SetDefaultDynamicNameAndPath(this); // By default the top-level scope in a session state object is the global scope for the instance. // For modules, we need to set its global scope to be another scope object and, chain the top // level scope for this sessionstate instance to be the parent. The top level scope for this ss is the // script scope for the ss. // Allocate the session state instance for this module. SessionState = new SessionState(context, true, linkToGlobal); SessionState.Internal.Module = this; } /// <summary> /// Construct a PSModuleInfo instance initializing it from a scriptblock instead of a script file. /// </summary> /// <param name="scriptBlock">The scriptblock to use to initialize the module.</param> public PSModuleInfo(ScriptBlock scriptBlock) { if (scriptBlock == null) { throw PSTraceSource.NewArgumentException(nameof(scriptBlock)); } // Get the ExecutionContext from the thread. var context = LocalPipeline.GetExecutionContextFromTLS(); if (context == null) throw new InvalidOperationException("PSModuleInfo"); SetDefaultDynamicNameAndPath(this); // By default the top-level scope in a session state object is the global scope for the instance. // For modules, we need to set its global scope to be another scope object and, chain the top // level scope for this sessionstate instance to be the parent. The top level scope for this ss is the // script scope for the ss. // Allocate the session state instance for this module. SessionState = new SessionState(context, true, true); SessionState.Internal.Module = this; LanguageMode = scriptBlock.LanguageMode; // Now set up the module's session state to be the current session state SessionStateInternal oldSessionState = context.EngineSessionState; try { context.EngineSessionState = SessionState.Internal; // Set the PSScriptRoot variable... context.SetVariable(SpecialVariables.PSScriptRootVarPath, Path); scriptBlock = scriptBlock.Clone(); scriptBlock.SessionState = SessionState; Pipe outputPipe = new Pipe { NullPipe = true }; // And run the scriptblock... scriptBlock.InvokeWithPipe( useLocalScope: false, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, dollarUnder: AutomationNull.Value, input: AutomationNull.Value, scriptThis: AutomationNull.Value, outputPipe: outputPipe, invocationInfo: null ); } finally { context.EngineSessionState = oldSessionState; } } /// <summary> /// Specifies the language mode for script based modules. /// </summary> internal PSLanguageMode? LanguageMode { get; set; } = PSLanguageMode.FullLanguage; /// <summary> /// Set to true when script module automatically exports all functions by default. /// </summary> internal bool ModuleAutoExportsAllFunctions { get; set; } internal bool ModuleHasPrivateMembers { get; set; } /// <summary> /// True if the module had errors during loading. /// </summary> internal bool HadErrorsLoading { get; set; } /// <summary> /// ToString() implementation which returns the name of the module. /// </summary> /// <returns>The name of the module.</returns> public override string ToString() { return this.Name; } /// <summary> /// Get/set whether to log Pipeline Execution Detail events. /// </summary> public bool LogPipelineExecutionDetails { get; set; } = false; /// <summary> /// The name of this module. /// </summary> public string Name { get; private set; } = string.Empty; /// <summary> /// Sets the name property of the PSModuleInfo object. /// </summary> /// <param name="name">The name to set it to.</param> internal void SetName(string name) { Name = name; } /// <summary> /// The path to the file that defined this module... /// </summary> public string Path { get; internal set; } = string.Empty; /// <summary> /// If the module is a binary module or a script module that defines /// classes, this property if a reference to the assembly, otherwise /// it is null. /// </summary> public Assembly ImplementingAssembly { get; internal set; } /// <summary> /// If this is a script module, then this property will contain /// the PowerShell source text that was used to define this module. /// </summary> public string Definition { get { return _definitionExtent == null ? string.Empty : _definitionExtent.Text; } } internal IScriptExtent _definitionExtent; /// <summary> /// A description of this module... /// </summary> public string Description { get { return _description; } set { _description = value ?? string.Empty; } } private string _description = string.Empty; /// <summary> /// The guid for this module if one was defined in the module manifest. /// </summary> public Guid Guid { get; private set; } internal void SetGuid(Guid guid) { Guid = guid; } /// <summary> /// The HelpInfo for this module if one was defined in the module manifest. /// </summary> [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")] public string HelpInfoUri { get; private set; } internal bool IsWindowsPowerShellCompatModule { get; set; } internal void SetHelpInfoUri(string uri) { HelpInfoUri = uri; } /// <summary> /// Get the module base directory for this module. For modules loaded via a module /// manifest, this will be the directory containing the manifest file rather than /// the directory containing the actual module file. This is particularly useful /// when loading a GAC'ed assembly. /// </summary> public string ModuleBase { get { return _moduleBase ??= !string.IsNullOrEmpty(Path) ? IO.Path.GetDirectoryName(Path) : string.Empty; } } internal void SetModuleBase(string moduleBase) { _moduleBase = moduleBase; } private string _moduleBase; /// <summary> /// This value is set from the PrivateData member in the module manifest. /// It allows implementor specific data to be passed to the module /// via the manifest file. /// </summary> public object PrivateData { get { return _privateData; } set { _privateData = value; SetPSDataPropertiesFromPrivateData(); } } private object _privateData = null; private void SetPSDataPropertiesFromPrivateData() { // Reset the old values of PSData properties. _tags.Clear(); ReleaseNotes = null; LicenseUri = null; ProjectUri = null; IconUri = null; if (_privateData is Hashtable hashData && hashData["PSData"] is Hashtable psData) { var tagsValue = psData["Tags"]; if (tagsValue is object[] tags && tags.Length > 0) { foreach (var tagString in tags.OfType<string>()) { AddToTags(tagString); } } else if (tagsValue is string tag) { AddToTags(tag); } if (psData["LicenseUri"] is string licenseUri) { LicenseUri = GetUriFromString(licenseUri); } if (psData["ProjectUri"] is string projectUri) { ProjectUri = GetUriFromString(projectUri); } if (psData["IconUri"] is string iconUri) { IconUri = GetUriFromString(iconUri); } ReleaseNotes = psData["ReleaseNotes"] as string; } } private static Uri GetUriFromString(string uriString) { Uri uri = null; if (uriString != null) { // try creating the Uri object // Ignoring the return value from Uri.TryCreate(), as uri value will be null on false or valid uri object on true. Uri.TryCreate(uriString, UriKind.Absolute, out uri); } return uri; } /// <summary> /// Get the experimental features declared in this module. /// </summary> public IEnumerable<ExperimentalFeature> ExperimentalFeatures { get; internal set; } = Utils.EmptyReadOnlyCollection<ExperimentalFeature>(); /// <summary> /// Tags of this module. /// </summary> public IEnumerable<string> Tags { get { return _tags; } } private readonly List<string> _tags = new List<string>(); internal void AddToTags(string tag) { _tags.Add(tag); } /// <summary> /// ProjectUri of this module. /// </summary> public Uri ProjectUri { get; internal set; } /// <summary> /// IconUri of this module. /// </summary> public Uri IconUri { get; internal set; } /// <summary> /// LicenseUri of this module. /// </summary> public Uri LicenseUri { get; internal set; } /// <summary> /// ReleaseNotes of this module. /// </summary> public string ReleaseNotes { get; internal set; } /// <summary> /// Repository SourceLocation of this module. /// </summary> public Uri RepositorySourceLocation { get; internal set; } /// <summary> /// The version of this module. /// </summary> public Version Version { get; private set; } = new Version(0, 0); /// <summary> /// Sets the module version. /// </summary> /// <param name="version">The version to set...</param> internal void SetVersion(Version version) { Version = version; } /// <summary> /// True if the module was compiled (i.e. a .DLL) instead of /// being in PowerShell script... /// </summary> public ModuleType ModuleType { get; private set; } = ModuleType.Script; /// <summary> /// This module as being a compiled module... /// </summary> internal void SetModuleType(ModuleType moduleType) { ModuleType = moduleType; } /// <summary> /// Module Author. /// </summary> public string Author { get; internal set; } /// <summary> /// Controls the module access mode... /// </summary> public ModuleAccessMode AccessMode { get { return _accessMode; } set { if (_accessMode == ModuleAccessMode.Constant) { throw PSTraceSource.NewInvalidOperationException(); } _accessMode = value; } } private ModuleAccessMode _accessMode = ModuleAccessMode.ReadWrite; /// <summary> /// CLR Version. /// </summary> public Version ClrVersion { get; internal set; } /// <summary> /// Company Name. /// </summary> public string CompanyName { get; internal set; } /// <summary> /// Copyright. /// </summary> public string Copyright { get; internal set; } /// <summary> /// .NET Framework Version. /// </summary> public Version DotNetFrameworkVersion { get; internal set; } internal Collection<string> DeclaredFunctionExports = null; internal Collection<string> DeclaredCmdletExports = null; internal Collection<string> DeclaredAliasExports = null; internal Collection<string> DeclaredVariableExports = null; internal List<string> DetectedFunctionExports = new List<string>(); internal List<string> DetectedCmdletExports = new List<string>(); internal Dictionary<string, string> DetectedAliasExports = new Dictionary<string, string>(); /// <summary> /// Lists the functions exported by this module... /// </summary> public Dictionary<string, FunctionInfo> ExportedFunctions { get { Dictionary<string, FunctionInfo> exports = new Dictionary<string, FunctionInfo>(StringComparer.OrdinalIgnoreCase); // If the module is not binary, it may also have functions... if (DeclaredFunctionExports != null) { if (DeclaredFunctionExports.Count == 0) { return exports; } foreach (string fn in DeclaredFunctionExports) { FunctionInfo tempFunction = new FunctionInfo(fn, ScriptBlock.EmptyScriptBlock, null) { Module = this }; exports[fn] = tempFunction; } } else if (SessionState != null) { // If there is no session state object associated with this list, // just return a null list of exports... if (SessionState.Internal.ExportedFunctions != null) { foreach (FunctionInfo fi in SessionState.Internal.ExportedFunctions) { if (!exports.ContainsKey(fi.Name)) { exports[ModuleCmdletBase.AddPrefixToCommandName(fi.Name, fi.Prefix)] = fi; } } } } else { foreach (var detectedExport in DetectedFunctionExports) { if (!exports.ContainsKey(detectedExport)) { FunctionInfo tempFunction = new FunctionInfo(detectedExport, ScriptBlock.EmptyScriptBlock, null) { Module = this }; exports[detectedExport] = tempFunction; } } } return exports; } } private static bool IsScriptModuleFile(string path) { var ext = System.IO.Path.GetExtension(path); return ext != null && s_scriptModuleExtensions.Contains(ext); } /// <summary> /// Lists the types (PowerShell classes, enums, interfaces) exported by this module. /// This returns ASTs for types, created in parse time. /// </summary> public ReadOnlyDictionary<string, TypeDefinitionAst> GetExportedTypeDefinitions() { // We cache exported types from this modules, but not from nestedModules, // because we may not have NestedModules list populated on the first call. // TODO(sevoroby): it may harm perf a little bit. Can we sort it out? if (_exportedTypeDefinitionsNoNested == null) { string rootedPath = null; if (RootModule == null) { if (this.Path != null) { rootedPath = this.Path; } } else { rootedPath = IO.Path.Combine(this.ModuleBase, this.RootModule); } // ExternalScriptInfo.GetScriptBlockAst() uses a cache layer to avoid re-parsing. CreateExportedTypeDefinitions(rootedPath != null && IsScriptModuleFile(rootedPath) && IO.File.Exists(rootedPath) ? (new ExternalScriptInfo(rootedPath, rootedPath)).GetScriptBlockAst() : null); } var res = new Dictionary<string, TypeDefinitionAst>(StringComparer.OrdinalIgnoreCase); foreach (var nestedModule in this.NestedModules) { if (nestedModule == this) { // Circular nested modules could happen with ill-organized module structure. // For example, module folder 'test' has two files: 'test.psd1' and 'test.psm1', and 'test.psd1' has the following content: // "@{ ModuleVersion = '0.0.1'; RootModule = 'test'; NestedModules = @('test') }" // Then, 'Import-Module test.psd1 -PassThru' will return a ModuleInfo object with circular nested modules. continue; } foreach (var typePairs in nestedModule.GetExportedTypeDefinitions()) { // The last one name wins! It's the same for command names in nested modules. // For rootModule C with Two nested modules (A, B) the order is: A, B, C res[typePairs.Key] = typePairs.Value; } } foreach (var typePairs in _exportedTypeDefinitionsNoNested) { res[typePairs.Key] = typePairs.Value; } return new ReadOnlyDictionary<string, TypeDefinitionAst>(res); } /// <summary> /// Create ExportedTypeDefinitions from ast. /// </summary> /// <param name="moduleContentScriptBlockAsts"></param> internal void CreateExportedTypeDefinitions(ScriptBlockAst moduleContentScriptBlockAsts) { if (moduleContentScriptBlockAsts == null) { this._exportedTypeDefinitionsNoNested = s_emptyTypeDefinitionDictionary; } else { this._exportedTypeDefinitionsNoNested = new ReadOnlyDictionary<string, TypeDefinitionAst>( moduleContentScriptBlockAsts.FindAll(static a => (a is TypeDefinitionAst), false) .OfType<TypeDefinitionAst>() .ToDictionary(static a => a.Name, StringComparer.OrdinalIgnoreCase)); } } internal void AddDetectedTypeExports(List<TypeDefinitionAst> typeDefinitions) { this._exportedTypeDefinitionsNoNested = new ReadOnlyDictionary<string, TypeDefinitionAst>( typeDefinitions.ToDictionary(static a => a.Name, StringComparer.OrdinalIgnoreCase)); } /// <summary> /// Prefix. /// </summary> public string Prefix { get; internal set; } /// <summary> /// Add function to the fixed exports list. /// </summary> /// <param name="name">The function to add.</param> internal void AddDetectedFunctionExport(string name) { Dbg.Assert(name != null, "AddDetectedFunctionExport should not be called with a null value"); if (!DetectedFunctionExports.Contains(name)) { DetectedFunctionExports.Add(name); } } /// <summary> /// Lists the functions exported by this module... /// </summary> public Dictionary<string, CmdletInfo> ExportedCmdlets { get { Dictionary<string, CmdletInfo> exports = new Dictionary<string, CmdletInfo>(StringComparer.OrdinalIgnoreCase); if (DeclaredCmdletExports != null) { if (DeclaredCmdletExports.Count == 0) { return exports; } foreach (string fn in DeclaredCmdletExports) { CmdletInfo tempCmdlet = new CmdletInfo(fn, null, null, null, null) { Module = this }; exports[fn] = tempCmdlet; } } else if ((CompiledExports != null) && (CompiledExports.Count > 0)) { foreach (CmdletInfo cmdlet in CompiledExports) { exports[cmdlet.Name] = cmdlet; } } else { foreach (string detectedExport in DetectedCmdletExports) { if (!exports.ContainsKey(detectedExport)) { CmdletInfo tempCmdlet = new CmdletInfo(detectedExport, null, null, null, null) { Module = this }; exports[detectedExport] = tempCmdlet; } } } return exports; } } /// <summary> /// Add CmdletInfo to the fixed exports list... /// </summary> /// <param name="cmdlet">The cmdlet to add...</param> internal void AddDetectedCmdletExport(string cmdlet) { Dbg.Assert(cmdlet != null, "AddDetectedCmdletExport should not be called with a null value"); if (!DetectedCmdletExports.Contains(cmdlet)) { DetectedCmdletExports.Add(cmdlet); } } /// <summary> /// Gets the aggregated list of visible commands exported from the module. If there are two /// commands of different types exported with the same name (e.g. alias 'foo' and cmdlet 'foo') the /// combined dictionary will only contain the highest precedence cmdlet (e.g. the alias 'foo' since /// aliases shadow cmdlets. /// </summary> public Dictionary<string, CommandInfo> ExportedCommands { get { Dictionary<string, CommandInfo> exports = new Dictionary<string, CommandInfo>(StringComparer.OrdinalIgnoreCase); Dictionary<string, CmdletInfo> cmdlets = this.ExportedCmdlets; if (cmdlets != null) { foreach (var cmdlet in cmdlets) { exports[cmdlet.Key] = cmdlet.Value; } } Dictionary<string, FunctionInfo> functions = this.ExportedFunctions; if (functions != null) { foreach (var function in functions) { exports[function.Key] = function.Value; } } Dictionary<string, AliasInfo> aliases = this.ExportedAliases; if (aliases != null) { foreach (var alias in aliases) { exports[alias.Key] = alias.Value; } } return exports; } } /// <summary> /// Add CmdletInfo to the fixed exports list... /// </summary> /// <param name="cmdlet">The cmdlet to add...</param> internal void AddExportedCmdlet(CmdletInfo cmdlet) { Dbg.Assert(cmdlet != null, "AddExportedCmdlet should not be called with a null value"); _compiledExports.Add(cmdlet); } /// <summary> /// Return the merged list of exported cmdlets. This is necessary /// because you may have a binary module with nested modules where /// some cmdlets come from the module and others come from the nested /// module. We need to consolidate the list so it can properly be constrained. /// </summary> internal List<CmdletInfo> CompiledExports { get { // If this module has a session state instance and there are any // exported cmdlets in the session state, migrate them to the // module info _compiledCmdlets entry. if (SessionState != null && SessionState.Internal.ExportedCmdlets != null && SessionState.Internal.ExportedCmdlets.Count > 0) { foreach (CmdletInfo ci in SessionState.Internal.ExportedCmdlets) { _compiledExports.Add(ci); } SessionState.Internal.ExportedCmdlets.Clear(); } return _compiledExports; } } private readonly List<CmdletInfo> _compiledExports = new List<CmdletInfo>(); /// <summary> /// Add AliasInfo to the fixed exports list... /// </summary> /// <param name="aliasInfo">The cmdlet to add...</param> internal void AddExportedAlias(AliasInfo aliasInfo) { Dbg.Assert(aliasInfo != null, "AddExportedAlias should not be called with a null value"); CompiledAliasExports.Add(aliasInfo); } /// <summary> /// Return the merged list of exported aliases. This is necessary /// because you may have a binary module with nested modules where /// some aliases come from the module and others come from the nested /// module. We need to consolidate the list so it can properly be constrained. /// </summary> internal List<AliasInfo> CompiledAliasExports { get; } = new List<AliasInfo>(); /// <summary> /// FileList. /// </summary> public IEnumerable<string> FileList { get { return _fileList; } } private List<string> _fileList = new List<string>(); internal void AddToFileList(string file) { _fileList.Add(file); } /// <summary> /// Lists the PowerShell editions this module is compatible with. This should /// reflect the module manifest the module was loaded with, or if no manifest was given /// or the key was not in the manifest, this should be an empty collection. This /// property is never null. /// </summary> public IEnumerable<string> CompatiblePSEditions { get { return _compatiblePSEditions; } } private readonly List<string> _compatiblePSEditions = new List<string>(); internal void AddToCompatiblePSEditions(string psEdition) { _compatiblePSEditions.Add(psEdition); } internal void AddToCompatiblePSEditions(IEnumerable<string> psEditions) { _compatiblePSEditions.AddRange(psEditions); } /// <summary> /// Describes whether the module was considered compatible at load time. /// Any module not on the System32 module path should have this as true. /// Modules loaded from the System32 module path will have this as true if they /// have declared edition compatibility with PowerShell 6+. Currently, this field /// is true for all non-psd1 module files, when it should not be. Being able to /// load psm1/dll modules from the System32 module path without needing to skip /// the edition check is considered a bug and should be fixed. /// </summary> internal bool IsConsideredEditionCompatible { get; set; } = true; /// <summary> /// ModuleList. /// </summary> public IEnumerable<object> ModuleList { get { return _moduleList; } } private Collection<object> _moduleList = new Collection<object>(); internal void AddToModuleList(object m) { _moduleList.Add(m); } /// <summary> /// Returns the list of child modules of this module. This will only /// be non-empty for module manifests. /// </summary> public ReadOnlyCollection<PSModuleInfo> NestedModules { get { return _readonlyNestedModules ??= new ReadOnlyCollection<PSModuleInfo>(_nestedModules); } } private ReadOnlyCollection<PSModuleInfo> _readonlyNestedModules; /// <summary> /// Add a module to the list of child modules. /// </summary> /// <param name="nestedModule">The module to add.</param> internal void AddNestedModule(PSModuleInfo nestedModule) { AddModuleToList(nestedModule, _nestedModules); } private readonly List<PSModuleInfo> _nestedModules = new List<PSModuleInfo>(); /// <summary> /// PowerShell Host Name. /// </summary> public string PowerShellHostName { get; internal set; } /// <summary> /// PowerShell Host Version. /// </summary> public Version PowerShellHostVersion { get; internal set; } /// <summary> /// PowerShell Version. /// </summary> public Version PowerShellVersion { get; internal set; } /// <summary> /// Processor Architecture. /// </summary> public ProcessorArchitecture ProcessorArchitecture { get; internal set; } /// <summary> /// Scripts to Process. /// </summary> public IEnumerable<string> Scripts { get { return _scripts; } } private List<string> _scripts = new List<string>(); internal void AddScript(string s) { _scripts.Add(s); } /// <summary> /// Required Assemblies. /// </summary> public IEnumerable<string> RequiredAssemblies { get { return _requiredAssemblies; } } private Collection<string> _requiredAssemblies = new Collection<string>(); internal void AddRequiredAssembly(string assembly) { _requiredAssemblies.Add(assembly); } /// <summary> /// Returns the list of required modules of this module. This will only /// be non-empty for module manifests. /// </summary> public ReadOnlyCollection<PSModuleInfo> RequiredModules { get { return _readonlyRequiredModules ??= new ReadOnlyCollection<PSModuleInfo>(_requiredModules); } } private ReadOnlyCollection<PSModuleInfo> _readonlyRequiredModules; /// <summary> /// Add a module to the list of required modules. /// </summary> /// <param name="requiredModule">The module to add.</param> internal void AddRequiredModule(PSModuleInfo requiredModule) { AddModuleToList(requiredModule, _requiredModules); } private List<PSModuleInfo> _requiredModules = new List<PSModuleInfo>(); /// <summary> /// Returns the list of required modules specified in the module manifest of this module. This will only /// be non-empty for module manifests. /// </summary> internal ReadOnlyCollection<ModuleSpecification> RequiredModulesSpecification { get { return _readonlyRequiredModulesSpecification ??= new ReadOnlyCollection<ModuleSpecification>(_requiredModulesSpecification); } } private ReadOnlyCollection<ModuleSpecification> _readonlyRequiredModulesSpecification; /// <summary> /// Add a module to the list of required modules specification. /// </summary> /// <param name="requiredModuleSpecification">The module to add.</param> internal void AddRequiredModuleSpecification(ModuleSpecification requiredModuleSpecification) { _requiredModulesSpecification.Add(requiredModuleSpecification); } private List<ModuleSpecification> _requiredModulesSpecification = new List<ModuleSpecification>(); /// <summary> /// Root Module. /// </summary> public string RootModule { get; internal set; } /// <summary> /// This member is used to copy over the RootModule in case the module is a manifest module /// This is so that only ModuleInfo for modules with type=Manifest have RootModule populated. /// </summary> internal string RootModuleForManifest { get; set; } /// <summary> /// Add a module to the list of modules, avoiding adding duplicates. /// </summary> private static void AddModuleToList(PSModuleInfo module, List<PSModuleInfo> moduleList) { Dbg.Assert(module != null, "AddModuleToList should not be called with a null value"); // Add the module if it isn't already there... foreach (PSModuleInfo m in moduleList) { if (m.Path.Equals(module.Path, StringComparison.OrdinalIgnoreCase)) return; } moduleList.Add(module); } internal static readonly string[] _builtinVariables = new string[] { "_", "this", "input", "args", "true", "false", "null", "PSDefaultParameterValues", "Error", "PSScriptRoot", "PSCommandPath", "MyInvocation", "ExecutionContext", "StackTrace" }; /// <summary> /// Lists the variables exported by this module. /// </summary> public Dictionary<string, PSVariable> ExportedVariables { get { Dictionary<string, PSVariable> exportedVariables = new Dictionary<string, PSVariable>(StringComparer.OrdinalIgnoreCase); if ((DeclaredVariableExports != null) && (DeclaredVariableExports.Count > 0)) { foreach (string fn in DeclaredVariableExports) { exportedVariables[fn] = null; } } else { // If there is no session state object associated with this list, // just return a null list of exports. This will be true if the // module is a compiled module. if (SessionState == null || SessionState.Internal.ExportedVariables == null) { return exportedVariables; } foreach (PSVariable v in SessionState.Internal.ExportedVariables) { exportedVariables[v.Name] = v; } } return exportedVariables; } } /// <summary> /// Lists the aliases exported by this module. /// </summary> public Dictionary<string, AliasInfo> ExportedAliases { get { Dictionary<string, AliasInfo> exportedAliases = new Dictionary<string, AliasInfo>(StringComparer.OrdinalIgnoreCase); if ((DeclaredAliasExports != null) && (DeclaredAliasExports.Count > 0)) { foreach (string fn in DeclaredAliasExports) { AliasInfo tempAlias = new AliasInfo(fn, null, null) { Module = this }; exportedAliases[fn] = tempAlias; } } else if ((CompiledAliasExports != null) && (CompiledAliasExports.Count > 0)) { foreach (AliasInfo ai in CompiledAliasExports) { exportedAliases[ai.Name] = ai; } } else { // There is no session state object associated with this list. if (SessionState == null) { // Check if we detected any if (DetectedAliasExports.Count > 0) { foreach (var pair in DetectedAliasExports) { string detectedExport = pair.Key; if (!exportedAliases.ContainsKey(detectedExport)) { AliasInfo tempAlias = new AliasInfo(detectedExport, pair.Value, null) { Module = this }; exportedAliases[detectedExport] = tempAlias; } } } else { // just return a null list of exports. This will be true if the // module is a compiled module. return exportedAliases; } } else { // We have a session state foreach (AliasInfo ai in SessionState.Internal.ExportedAliases) { exportedAliases[ai.Name] = ai; } } } return exportedAliases; } } /// <summary> /// Add alias to the detected alias list. /// </summary> /// <param name="name">The alias to add.</param> /// <param name="value">The command it resolves to.</param> internal void AddDetectedAliasExport(string name, string value) { Dbg.Assert(name != null, "AddDetectedAliasExport should not be called with a null value"); DetectedAliasExports[name] = value; } /// <summary> /// </summary> public ReadOnlyCollection<string> ExportedDscResources { get { return _declaredDscResourceExports != null ? new ReadOnlyCollection<string>(_declaredDscResourceExports) : Utils.EmptyReadOnlyCollection<string>(); } } internal Collection<string> _declaredDscResourceExports = null; /// <summary> /// The session state instance associated with this module. /// </summary> public SessionState SessionState { get; set; } /// <summary> /// Returns a new scriptblock bound to this module instance. /// </summary> /// <param name="scriptBlockToBind">The original scriptblock.</param> /// <returns>The new bound scriptblock.</returns> public ScriptBlock NewBoundScriptBlock(ScriptBlock scriptBlockToBind) { var context = LocalPipeline.GetExecutionContextFromTLS(); return NewBoundScriptBlock(scriptBlockToBind, context); } internal ScriptBlock NewBoundScriptBlock(ScriptBlock scriptBlockToBind, ExecutionContext context) { if (SessionState == null || context == null) { throw PSTraceSource.NewInvalidOperationException(Modules.InvalidOperationOnBinaryModule); } ScriptBlock newsb; // Now set up the module's session state to be the current session state lock (context.EngineSessionState) { SessionStateInternal oldSessionState = context.EngineSessionState; try { context.EngineSessionState = SessionState.Internal; newsb = scriptBlockToBind.Clone(); newsb.SessionState = SessionState; } finally { context.EngineSessionState = oldSessionState; } } return newsb; } /// <summary> /// Invoke a scriptblock in the context of this module... /// </summary> /// <param name="sb">The scriptblock to invoke.</param> /// <param name="args">Arguments to the scriptblock.</param> /// <returns>The result of the invocation.</returns> public object Invoke(ScriptBlock sb, params object[] args) { if (sb == null) return null; // Temporarily set the scriptblocks session state to be the // modules... SessionStateInternal oldSessionState = sb.SessionStateInternal; object result; try { sb.SessionStateInternal = SessionState.Internal; result = sb.InvokeReturnAsIs(args); } finally { // and restore the scriptblocks session state... sb.SessionStateInternal = oldSessionState; } return result; } /// <summary> /// This routine allows you to get access variable objects in the callers module /// or from the toplevel sessionstate if there is no calling module. /// </summary> /// <param name="variableName"></param> /// <returns></returns> public PSVariable GetVariableFromCallersModule(string variableName) { ArgumentException.ThrowIfNullOrEmpty(variableName); var context = LocalPipeline.GetExecutionContextFromTLS(); SessionState callersSessionState = null; foreach (var sf in context.Debugger.GetCallStack()) { var frameModule = sf.InvocationInfo.MyCommand.Module; if (frameModule == null) { break; } if (frameModule.SessionState != SessionState) { callersSessionState = sf.InvocationInfo.MyCommand.Module.SessionState; break; } } if (callersSessionState != null) { return callersSessionState.Internal.GetVariable(variableName); } else { return context.TopLevelSessionState.GetVariable(variableName); } } /// <summary> /// Copies the local variables in the caller's cope into the module... /// </summary> internal void CaptureLocals() { if (SessionState == null) { throw PSTraceSource.NewInvalidOperationException(Modules.InvalidOperationOnBinaryModule); } var context = LocalPipeline.GetExecutionContextFromTLS(); var tuple = context.EngineSessionState.CurrentScope.LocalsTuple; IEnumerable<PSVariable> variables = context.EngineSessionState.CurrentScope.Variables.Values; if (tuple != null) { var result = new Dictionary<string, PSVariable>(); tuple.GetVariableTable(result, false); variables = result.Values.Concat(variables); } foreach (PSVariable v in variables) { try { // Only copy simple mutable variables... if (v.Options == ScopedItemOptions.None && v is not NullVariable) { PSVariable newVar = new PSVariable(v.Name, v.Value, v.Options, v.Description); // The variable is already defined/set in the scope, and that means the attributes // have already been checked if it was needed, so we don't do it again. newVar.AddParameterAttributesNoChecks(v.Attributes); SessionState.Internal.NewVariable(newVar, false); } } catch (SessionStateException) { } } } /// <summary> /// Build a custom object out of this module... /// </summary> /// <returns>A custom object.</returns> public PSObject AsCustomObject() { if (SessionState == null) { throw PSTraceSource.NewInvalidOperationException(Modules.InvalidOperationOnBinaryModule); } PSObject obj = new PSObject(); foreach (KeyValuePair<string, FunctionInfo> entry in this.ExportedFunctions) { FunctionInfo func = entry.Value; if (func != null) { PSScriptMethod sm = new PSScriptMethod(func.Name, func.ScriptBlock); obj.Members.Add(sm); } } foreach (KeyValuePair<string, PSVariable> entry in this.ExportedVariables) { PSVariable var = entry.Value; if (var != null) { PSVariableProperty sm = new PSVariableProperty(var); obj.Members.Add(sm); } } return obj; } /// <summary> /// Optional script that is going to be called just before Remove-Module cmdlet removes the module. /// </summary> public ScriptBlock OnRemove { get; set; } /// <summary> /// The list of Format files imported by this module. /// </summary> public ReadOnlyCollection<string> ExportedFormatFiles { get; private set; } = new ReadOnlyCollection<string>(new List<string>()); internal void SetExportedFormatFiles(ReadOnlyCollection<string> files) { ExportedFormatFiles = files; } /// <summary> /// The list of types files imported by this module. /// </summary> public ReadOnlyCollection<string> ExportedTypeFiles { get; private set; } = new ReadOnlyCollection<string>(new List<string>()); internal void SetExportedTypeFiles(ReadOnlyCollection<string> files) { ExportedTypeFiles = files; } /// <summary> /// Implements deep copy of a PSModuleInfo instance. /// </summary> /// <returns>A new PSModuleInfo instance.</returns> public PSModuleInfo Clone() { PSModuleInfo clone = (PSModuleInfo)this.MemberwiseClone(); clone._fileList = new List<string>(this.FileList); clone._moduleList = new Collection<object>(_moduleList); foreach (var n in this.NestedModules) { clone.AddNestedModule(n); } clone._readonlyNestedModules = new ReadOnlyCollection<PSModuleInfo>(this.NestedModules); clone._readonlyRequiredModules = new ReadOnlyCollection<PSModuleInfo>(this.RequiredModules); clone._readonlyRequiredModulesSpecification = new ReadOnlyCollection<ModuleSpecification>(this.RequiredModulesSpecification); clone._requiredAssemblies = new Collection<string>(_requiredAssemblies); clone._requiredModulesSpecification = new List<ModuleSpecification>(); clone._requiredModules = new List<PSModuleInfo>(); foreach (var r in _requiredModules) { clone.AddRequiredModule(r); } foreach (var r in _requiredModulesSpecification) { clone.AddRequiredModuleSpecification(r); } clone._scripts = new List<string>(this.Scripts); clone.SessionState = this.SessionState; return clone; } /// <summary> /// Enables or disables the appdomain module path cache. /// </summary> public static bool UseAppDomainLevelModuleCache { get; set; } /// <summary> /// Clear out the appdomain-level module path cache. /// </summary> public static void ClearAppDomainLevelModulePathCache() { s_appdomainModulePathCache.Clear(); } #if DEBUG /// <summary> /// A method available in debug mode providing access to the module path cache. /// </summary> /// <returns></returns> public static object GetAppDomainLevelModuleCache() { return s_appdomainModulePathCache; } #endif /// <summary> /// Look up a module in the appdomain wide module path cache. /// </summary> /// <param name="moduleName">Module name to look up.</param> /// <returns>The path to the matched module.</returns> internal static string ResolveUsingAppDomainLevelModuleCache(string moduleName) { string path; if (s_appdomainModulePathCache.TryGetValue(moduleName, out path)) { return path; } else { return string.Empty; } } /// <summary> /// Add an entry to the appdomain level module path cache. By default, if there already is an entry /// it won't be replace. If force is specified, then it will be updated. \ /// </summary> /// <param name="moduleName"></param> /// <param name="path"></param> /// <param name="force"></param> internal static void AddToAppDomainLevelModuleCache(string moduleName, string path, bool force) { if (force) { s_appdomainModulePathCache.AddOrUpdate(moduleName, path, (modulename, oldPath) => path); } else { s_appdomainModulePathCache.TryAdd(moduleName, path); } } /// <summary> /// If there is an entry for the named module in the appdomain level module path cache, remove it. /// </summary> /// <param name="moduleName">The name of the module to remove from the cache.</param> /// <returns>True if the module was remove.</returns> internal static bool RemoveFromAppDomainLevelCache(string moduleName) { string outString; return s_appdomainModulePathCache.TryRemove(moduleName, out outString); } private static readonly System.Collections.Concurrent.ConcurrentDictionary<string, string> s_appdomainModulePathCache = new System.Collections.Concurrent.ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase); } /// <summary> /// Indicates the type of a module. /// </summary> public enum ModuleType { /// <summary> /// Indicates that this is a script module (a powershell file with a .PSM1 extension) /// </summary> Script = 0, /// <summary> /// Indicates that this is compiled .dll containing cmdlet definitions. /// </summary> Binary = 1, /// <summary> /// Indicates that this module entry was derived from a module manifest and /// may have child modules. /// </summary> Manifest, /// <summary> /// Indicates that this is cmdlets-over-objects module (a powershell file with a .CDXML extension) /// </summary> Cim, } /// <summary> /// Defines the possible access modes for a module... /// </summary> public enum ModuleAccessMode { /// <summary> /// The default access mode for the module. /// </summary> ReadWrite = 0, /// <summary> /// The module is readonly and can only be removed with -force. /// </summary> ReadOnly = 1, /// <summary> /// The module cannot be removed. /// </summary> Constant = 2 } /// <summary> /// An EqualityComparer to compare 2 PSModuleInfo instances. 2 PSModuleInfos are /// considered equal if their Name,Guid and Version are equal. /// </summary> internal sealed class PSModuleInfoComparer : IEqualityComparer<PSModuleInfo> { public bool Equals(PSModuleInfo x, PSModuleInfo y) { // Check whether the compared objects reference the same data. if (object.ReferenceEquals(x, y)) return true; // Check whether any of the compared objects is null. if (x is null || y is null) return false; bool result = string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) && (x.Guid == y.Guid) && (x.Version == y.Version); return result; } public int GetHashCode(PSModuleInfo obj) { unchecked // Overflow is fine, just wrap { int result = 0; if (obj != null) { // picking two different prime numbers to avoid collisions result = 23; if (obj.Name != null) { result = result * 17 + obj.Name.GetHashCode(); } if (obj.Guid != Guid.Empty) { result = result * 17 + obj.Guid.GetHashCode(); } if (obj.Version != null) { result = result * 17 + obj.Version.GetHashCode(); } } return result; } } } }