MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Generic;6using System.IO;7using System.Linq;8using System.Management.Automation.Configuration;9using System.Management.Automation.Internal;10using System.Management.Automation.Language;11using System.Text;12using System.Threading;13using Microsoft.PowerShell.Commands;14 15using Dbg = System.Management.Automation.Diagnostics;16 17namespace System.Management.Automation18{19 internal static class Constants20 {21 public const string PSModulePathEnvVar = "PSModulePath";22 }23 24 /// <summary>25 /// Encapsulates the basic module operations for a PowerShell engine instance...26 /// </summary>27 public class ModuleIntrinsics28 {29 /// <summary>30 /// Tracer for module analysis.31 /// </summary>32 [TraceSource("Modules", "Module loading and analysis")]33 internal static readonly PSTraceSource Tracer = PSTraceSource.GetTracer("Modules", "Module loading and analysis");34 35 // The %WINDIR%\System32\WindowsPowerShell\v1.0\Modules module path,36 // to load forward compatible Windows PowerShell modules from37 private static readonly string s_windowsPowerShellPSHomeModulePath =38 Path.Combine(System.Environment.SystemDirectory, "WindowsPowerShell", "v1.0", "Modules");39 40 static ModuleIntrinsics()41 {42 // Initialize the module path.43 SetModulePath();44 }45 46 internal ModuleIntrinsics(ExecutionContext context)47 {48 _context = context;49 ModuleTable = new Dictionary<string, PSModuleInfo>(StringComparer.OrdinalIgnoreCase);50 }51 52 private readonly ExecutionContext _context;53 54 // Holds the module collection...55 internal Dictionary<string, PSModuleInfo> ModuleTable { get; }56 57 private const int MaxModuleNestingDepth = 10;58 59 internal void IncrementModuleNestingDepth(PSCmdlet cmdlet, string path)60 {61 if (++ModuleNestingDepth > MaxModuleNestingDepth)62 {63 string message = StringUtil.Format(Modules.ModuleTooDeeplyNested, path, MaxModuleNestingDepth);64 InvalidOperationException ioe = new InvalidOperationException(message);65 ErrorRecord er = new ErrorRecord(ioe, "Modules_ModuleTooDeeplyNested",66 ErrorCategory.InvalidOperation, path);67 // NOTE: this call will throw68 cmdlet.ThrowTerminatingError(er);69 }70 }71 72 internal void DecrementModuleNestingCount()73 {74 --ModuleNestingDepth;75 }76 77 internal int ModuleNestingDepth { get; private set; }78 79 /// <summary>80 /// Create a new module object from a scriptblock specifying the path to set for the module.81 /// </summary>82 /// <param name="name">The name of the module.</param>83 /// <param name="path">The path where the module is rooted.</param>84 /// <param name="scriptBlock">85 /// ScriptBlock that is executed to initialize the module...86 /// </param>87 /// <param name="arguments">88 /// The arguments to pass to the scriptblock used to initialize the module89 /// </param>90 /// <param name="ss">The session state instance to use for this module - may be null.</param>91 /// <param name="results">The results produced from evaluating the scriptblock.</param>92 /// <returns>The newly created module info object.</returns>93 internal PSModuleInfo CreateModule(string name, string path, ScriptBlock scriptBlock, SessionState ss, out List<object> results, params object[] arguments)94 {95 return CreateModuleImplementation(name, path, scriptBlock, null, ss, null, out results, arguments);96 }97 98 /// <summary>99 /// Create a new module object from a ScriptInfo object.100 /// </summary>101 /// <param name="path">The path where the module is rooted.</param>102 /// <param name="scriptInfo">The script info to use to create the module.</param>103 /// <param name="scriptPosition">The position for the command that loaded this module.</param>104 /// <param name="arguments">Optional arguments to pass to the script while executing.</param>105 /// <param name="ss">The session state instance to use for this module - may be null.</param>106 /// <param name="privateData">The private data to use for this module - may be null.</param>107 /// <returns>The constructed module object.</returns>108 internal PSModuleInfo CreateModule(string path, ExternalScriptInfo scriptInfo, IScriptExtent scriptPosition, SessionState ss, object privateData, params object[] arguments)109 {110 List<object> result;111 return CreateModuleImplementation(ModuleIntrinsics.GetModuleName(path), path, scriptInfo, scriptPosition, ss, privateData, out result, arguments);112 }113 114 /// <summary>115 /// Create a new module object from code specifying the path to set for the module.116 /// </summary>117 /// <param name="name">The name of the module.</param>118 /// <param name="path">The path to use for the module root.</param>119 /// <param name="moduleCode">120 /// The code to use to create the module. This can be one of ScriptBlock, string121 /// or ExternalScriptInfo122 /// </param>123 /// <param name="arguments">124 /// Arguments to pass to the module scriptblock during evaluation.125 /// </param>126 /// <param name="result">127 /// The results of the evaluation of the scriptblock.128 /// </param>129 /// <param name="scriptPosition">130 /// The position of the caller of this function so you can tell where the call131 /// to Import-Module (or whatever) occurred. This can be null.132 /// </param>133 /// <param name="ss">The session state instance to use for this module - may be null.</param>134 /// <param name="privateData">The private data to use for this module - may be null.</param>135 /// <returns>The created module.</returns>136 private PSModuleInfo CreateModuleImplementation(string name, string path, object moduleCode, IScriptExtent scriptPosition, SessionState ss, object privateData, out List<object> result, params object[] arguments)137 {138 ScriptBlock sb;139 140 // By default the top-level scope in a session state object is the global scope for the instance.141 // For modules, we need to set its global scope to be another scope object and, chain the top142 // level scope for this sessionstate instance to be the parent. The top level scope for this ss is the143 // script scope for the ss.144 145 // Allocate the session state instance for this module.146 ss ??= new SessionState(_context, true, true);147 148 // Now set up the module's session state to be the current session state149 SessionStateInternal oldSessionState = _context.EngineSessionState;150 PSModuleInfo module = new PSModuleInfo(name, path, _context, ss);151 ss.Internal.Module = module;152 module.PrivateData = privateData;153 154 bool setExitCode = false;155 int exitCode = 0;156 157 try158 {159 _context.EngineSessionState = ss.Internal;160 161 // Build the scriptblock at this point so the references to the module162 // context are correct...163 ExternalScriptInfo scriptInfo = moduleCode as ExternalScriptInfo;164 if (scriptInfo != null)165 {166 sb = scriptInfo.ScriptBlock;167 168 _context.Debugger.RegisterScriptFile(scriptInfo);169 }170 else171 {172 sb = moduleCode as ScriptBlock;173 if (sb != null)174 {175 PSLanguageMode? moduleLanguageMode = sb.LanguageMode;176 sb = sb.Clone();177 sb.LanguageMode = moduleLanguageMode;178 179 sb.SessionState = ss;180 }181 else if (moduleCode is string sbText)182 {183 sb = ScriptBlock.Create(_context, sbText);184 }185 }186 187 if (sb == null)188 throw PSTraceSource.NewInvalidOperationException();189 190 sb.SessionStateInternal = ss.Internal;191 module.LanguageMode = sb.LanguageMode;192 193 InvocationInfo invocationInfo = new InvocationInfo(scriptInfo, scriptPosition);194 195 // Save the module string196 module._definitionExtent = sb.Ast.Extent;197 var ast = sb.Ast;198 while (ast.Parent != null)199 {200 ast = ast.Parent;201 }202 203 // The variables set in the interpreted case get set by InvokeWithPipe in the compiled case.204 Diagnostics.Assert(_context.SessionState.Internal.CurrentScope.LocalsTuple == null,205 "No locals tuple should have been created yet.");206 207 List<object> resultList = new List<object>();208 209 try210 {211 Pipe outputPipe = new Pipe(resultList);212 213 // And run the scriptblock...214 sb.InvokeWithPipe(215 useLocalScope: false,216 errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe,217 dollarUnder: AutomationNull.Value,218 input: AutomationNull.Value,219 scriptThis: AutomationNull.Value,220 outputPipe: outputPipe,221 invocationInfo: invocationInfo,222 args: arguments ?? Array.Empty<object>());223 }224 catch (ExitException ee)225 {226 exitCode = (int)ee.Argument;227 setExitCode = true;228 }229 230 result = resultList;231 }232 finally233 {234 _context.EngineSessionState = oldSessionState;235 }236 237 if (setExitCode)238 {239 _context.SetVariable(SpecialVariables.LastExitCodeVarPath, exitCode);240 }241 242 module.ImplementingAssembly = sb.AssemblyDefiningPSTypes;243 // We force re-population of ExportedTypeDefinitions, now with the actual RuntimeTypes, created above.244 module.CreateExportedTypeDefinitions(sb.Ast as ScriptBlockAst);245 246 return module;247 }248 249 /// <summary>250 /// Allocate a new dynamic module then return a new scriptblock251 /// bound to the module instance.252 /// </summary>253 /// <param name="context">Context to use to create bounded script.</param>254 /// <param name="sb">The scriptblock to bind.</param>255 /// <param name="linkToGlobal">Whether it should be linked to the global session state or not.</param>256 /// <returns>A new scriptblock.</returns>257 internal ScriptBlock CreateBoundScriptBlock(ExecutionContext context, ScriptBlock sb, bool linkToGlobal)258 {259 PSModuleInfo module = new PSModuleInfo(context, linkToGlobal);260 return module.NewBoundScriptBlock(sb, context);261 }262 263 internal List<PSModuleInfo> GetModules(string[] patterns, bool all)264 {265 return GetModuleCore(patterns, all, false);266 }267 268 internal List<PSModuleInfo> GetExactMatchModules(string moduleName, bool all, bool exactMatch)269 {270 moduleName ??= string.Empty;271 272 return GetModuleCore(new string[] { moduleName }, all, exactMatch);273 }274 275 private List<PSModuleInfo> GetModuleCore(string[] patterns, bool all, bool exactMatch)276 {277 string targetModuleName = null;278 List<WildcardPattern> wcpList = new List<WildcardPattern>();279 280 if (exactMatch)281 {282 Dbg.Assert(patterns.Length == 1, "The 'patterns' should only contain one element when it is for an exact match");283 targetModuleName = patterns[0];284 }285 else286 {287 patterns ??= new string[] { "*" };288 289 foreach (string pattern in patterns)290 {291 wcpList.Add(WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase));292 }293 }294 295 List<PSModuleInfo> modulesMatched = new List<PSModuleInfo>();296 297 if (all)298 {299 foreach (PSModuleInfo module in ModuleTable.Values)300 {301 // See if this is the requested module...302 if ((exactMatch && module.Name.Equals(targetModuleName, StringComparison.OrdinalIgnoreCase)) ||303 (!exactMatch && SessionStateUtilities.MatchesAnyWildcardPattern(module.Name, wcpList, false)))304 {305 modulesMatched.Add(module);306 }307 }308 }309 else310 {311 // Create a joint list of local and global modules. Only report a module once.312 // Local modules are reported before global modules...313 Dictionary<string, bool> found = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);314 foreach (var pair in _context.EngineSessionState.ModuleTable)315 {316 string path = pair.Key;317 PSModuleInfo module = pair.Value;318 // See if this is the requested module...319 if ((exactMatch && module.Name.Equals(targetModuleName, StringComparison.OrdinalIgnoreCase)) ||320 (!exactMatch && SessionStateUtilities.MatchesAnyWildcardPattern(module.Name, wcpList, false)))321 {322 modulesMatched.Add(module);323 found[path] = true;324 }325 }326 327 if (_context.EngineSessionState != _context.TopLevelSessionState)328 {329 foreach (var pair in _context.TopLevelSessionState.ModuleTable)330 {331 string path = pair.Key;332 if (!found.ContainsKey(path))333 {334 PSModuleInfo module = pair.Value;335 // See if this is the requested module...336 if ((exactMatch && module.Name.Equals(targetModuleName, StringComparison.OrdinalIgnoreCase)) ||337 (!exactMatch && SessionStateUtilities.MatchesAnyWildcardPattern(module.Name, wcpList, false)))338 {339 modulesMatched.Add(module);340 }341 }342 }343 }344 }345 346 return modulesMatched.OrderBy(static m => m.Name).ToList();347 }348 349 internal List<PSModuleInfo> GetModules(ModuleSpecification[] fullyQualifiedName, bool all)350 {351 List<PSModuleInfo> modulesMatched = new List<PSModuleInfo>();352 353 if (all)354 {355 foreach (var moduleSpec in fullyQualifiedName)356 {357 foreach (PSModuleInfo module in ModuleTable.Values)358 {359 // See if this is the requested module...360 if (IsModuleMatchingModuleSpec(module, moduleSpec))361 {362 modulesMatched.Add(module);363 }364 }365 }366 }367 else368 {369 foreach (var moduleSpec in fullyQualifiedName)370 {371 // Create a joint list of local and global modules. Only report a module once.372 // Local modules are reported before global modules...373 Dictionary<string, bool> found = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);374 foreach (var pair in _context.EngineSessionState.ModuleTable)375 {376 string path = pair.Key;377 PSModuleInfo module = pair.Value;378 // See if this is the requested module...379 if (IsModuleMatchingModuleSpec(module, moduleSpec))380 {381 modulesMatched.Add(module);382 found[path] = true;383 }384 }385 386 if (_context.EngineSessionState != _context.TopLevelSessionState)387 {388 foreach (var pair in _context.TopLevelSessionState.ModuleTable)389 {390 string path = pair.Key;391 if (!found.ContainsKey(path))392 {393 PSModuleInfo module = pair.Value;394 // See if this is the requested module...395 if (IsModuleMatchingModuleSpec(module, moduleSpec))396 {397 modulesMatched.Add(module);398 }399 }400 }401 }402 }403 }404 405 return modulesMatched.OrderBy(static m => m.Name).ToList();406 }407 408 /// <summary>409 /// Check if a given module info object matches a given module specification.410 /// </summary>411 /// <param name="moduleInfo">The module info object to check.</param>412 /// <param name="moduleSpec">The module specification to match the module info object against.</param>413 /// <param name="skipNameCheck">True if we should skip the name check on the module specification.</param>414 /// <returns>True if the module info object meets all the constraints on the module specification, false otherwise.</returns>415 internal static bool IsModuleMatchingModuleSpec(416 PSModuleInfo moduleInfo,417 ModuleSpecification moduleSpec,418 bool skipNameCheck = false)419 {420 return IsModuleMatchingModuleSpec(out ModuleMatchFailure matchFailureReason, moduleInfo, moduleSpec, skipNameCheck);421 }422 423 /// <summary>424 /// Check if a given module info object matches a given module specification.425 /// </summary>426 /// <param name="matchFailureReason">The constraint that caused the match failure, if any.</param>427 /// <param name="moduleInfo">The module info object to check.</param>428 /// <param name="moduleSpec">The module specification to match the module info object against.</param>429 /// <param name="skipNameCheck">True if we should skip the name check on the module specification.</param>430 /// <returns>True if the module info object meets all the constraints on the module specification, false otherwise.</returns>431 internal static bool IsModuleMatchingModuleSpec(432 out ModuleMatchFailure matchFailureReason,433 PSModuleInfo moduleInfo,434 ModuleSpecification moduleSpec,435 bool skipNameCheck = false)436 {437 if (moduleSpec == null)438 {439 matchFailureReason = ModuleMatchFailure.NullModuleSpecification;440 return false;441 }442 443 return IsModuleMatchingConstraints(444 out matchFailureReason,445 moduleInfo,446 skipNameCheck ? null : moduleSpec.Name,447 moduleSpec.Guid,448 moduleSpec.RequiredVersion,449 moduleSpec.Version,450 moduleSpec.MaximumVersion == null ? null : ModuleCmdletBase.GetMaximumVersion(moduleSpec.MaximumVersion));451 }452 453 /// <summary>454 /// Check if a given module info object matches the given constraints.455 /// Constraints given as null are ignored.456 /// </summary>457 /// <param name="moduleInfo">The module info object to check.</param>458 /// <param name="name">The name or normalized absolute path of the expected module.</param>459 /// <param name="guid">The guid of the expected module.</param>460 /// <param name="requiredVersion">The required version of the expected module.</param>461 /// <param name="minimumVersion">The minimum required version of the expected module.</param>462 /// <param name="maximumVersion">The maximum required version of the expected module.</param>463 /// <returns>True if the module info object matches all given constraints, false otherwise.</returns>464 internal static bool IsModuleMatchingConstraints(465 PSModuleInfo moduleInfo,466 string name = null,467 Guid? guid = null,468 Version requiredVersion = null,469 Version minimumVersion = null,470 Version maximumVersion = null)471 {472 return IsModuleMatchingConstraints(473 out ModuleMatchFailure matchFailureReason,474 moduleInfo,475 name,476 guid,477 requiredVersion,478 minimumVersion,479 maximumVersion);480 }481 482 /// <summary>483 /// Check if a given module info object matches the given constraints.484 /// Constraints given as null are ignored.485 /// </summary>486 /// <param name="matchFailureReason">The reason for the module constraint match failing.</param>487 /// <param name="moduleInfo">The module info object to check.</param>488 /// <param name="name">The name or normalized absolute path of the expected module.</param>489 /// <param name="guid">The guid of the expected module.</param>490 /// <param name="requiredVersion">The required version of the expected module.</param>491 /// <param name="minimumVersion">The minimum required version of the expected module.</param>492 /// <param name="maximumVersion">The maximum required version of the expected module.</param>493 /// <returns>True if the module info object matches all given constraints, false otherwise.</returns>494 internal static bool IsModuleMatchingConstraints(495 out ModuleMatchFailure matchFailureReason,496 PSModuleInfo moduleInfo,497 string name,498 Guid? guid,499 Version requiredVersion,500 Version minimumVersion,501 Version maximumVersion)502 {503 // Define that a null module does not meet any constraints504 if (moduleInfo == null)505 {506 matchFailureReason = ModuleMatchFailure.NullModule;507 return false;508 }509 510 return AreModuleFieldsMatchingConstraints(511 out matchFailureReason,512 moduleInfo.Name,513 moduleInfo.Path,514 moduleInfo.Guid,515 moduleInfo.Version,516 name,517 guid,518 requiredVersion,519 minimumVersion,520 maximumVersion521 );522 }523 524 /// <summary>525 /// Check that given module fields meet any given constraints.526 /// </summary>527 /// <param name="moduleName">The name of the module to check.</param>528 /// <param name="modulePath">The path of the module to check.</param>529 /// <param name="moduleGuid">The GUID of the module to check.</param>530 /// <param name="moduleVersion">The version of the module to check.</param>531 /// <param name="requiredName">The name or normalized absolute path the module must have, if any.</param>532 /// <param name="requiredGuid">The GUID the module must have, if any.</param>533 /// <param name="requiredVersion">The exact version the module must have, if any.</param>534 /// <param name="minimumRequiredVersion">The minimum version the module may have, if any.</param>535 /// <param name="maximumRequiredVersion">The maximum version the module may have, if any.</param>536 /// <returns>True if the module parameters match all given constraints, false otherwise.</returns>537 internal static bool AreModuleFieldsMatchingConstraints(538 string moduleName = null,539 string modulePath = null,540 Guid? moduleGuid = null,541 Version moduleVersion = null,542 string requiredName = null,543 Guid? requiredGuid = null,544 Version requiredVersion = null,545 Version minimumRequiredVersion = null,546 Version maximumRequiredVersion = null)547 {548 return AreModuleFieldsMatchingConstraints(549 out ModuleMatchFailure matchFailureReason,550 moduleName,551 modulePath,552 moduleGuid,553 moduleVersion,554 requiredName,555 requiredGuid,556 requiredVersion,557 minimumRequiredVersion,558 maximumRequiredVersion);559 }560 561 /// <summary>562 /// Check that given module fields meet any given constraints.563 /// </summary>564 /// <param name="matchFailureReason">The reason the match failed, if any.</param>565 /// <param name="moduleName">The name of the module to check.</param>566 /// <param name="modulePath">The path of the module to check.</param>567 /// <param name="moduleGuid">The GUID of the module to check.</param>568 /// <param name="moduleVersion">The version of the module to check.</param>569 /// <param name="requiredName">The name or normalized absolute path the module must have, if any.</param>570 /// <param name="requiredGuid">The GUID the module must have, if any.</param>571 /// <param name="requiredVersion">The exact version the module must have, if any.</param>572 /// <param name="minimumRequiredVersion">The minimum version the module may have, if any.</param>573 /// <param name="maximumRequiredVersion">The maximum version the module may have, if any.</param>574 /// <returns>True if the module parameters match all given constraints, false otherwise.</returns>575 internal static bool AreModuleFieldsMatchingConstraints(576 out ModuleMatchFailure matchFailureReason,577 string moduleName,578 string modulePath,579 Guid? moduleGuid,580 Version moduleVersion,581 string requiredName,582 Guid? requiredGuid,583 Version requiredVersion,584 Version minimumRequiredVersion,585 Version maximumRequiredVersion)586 {587 // If a name is required, check that it matches.588 // A required module name may also be an absolute path, so check it against the given module's path as well.589 if (requiredName != null590 && !requiredName.Equals(moduleName, StringComparison.OrdinalIgnoreCase)591 && !MatchesModulePath(modulePath, requiredName))592 {593 matchFailureReason = ModuleMatchFailure.Name;594 return false;595 }596 597 // If a GUID is required, check it matches598 if (requiredGuid != null && !requiredGuid.Equals(moduleGuid))599 {600 matchFailureReason = ModuleMatchFailure.Guid;601 return false;602 }603 604 // Check the versions605 return IsVersionMatchingConstraints(out matchFailureReason, moduleVersion, requiredVersion, minimumRequiredVersion, maximumRequiredVersion);606 }607 608 /// <summary>609 /// Check that a given module version matches the required or minimum/maximum version constraints.610 /// Null constraints are not checked.611 /// </summary>612 /// <param name="version">The module version to check. Must not be null.</param>613 /// <param name="requiredVersion">The version that the given version must be, if not null.</param>614 /// <param name="minimumVersion">The minimum version that the given version must be greater than or equal to, if not null.</param>615 /// <param name="maximumVersion">The maximum version that the given version must be less then or equal to, if not null.</param>616 /// <returns>617 /// True if the version matches the required version, or if it is absent, is between the minimum and maximum versions, and false otherwise.618 /// </returns>619 internal static bool IsVersionMatchingConstraints(620 Version version,621 Version requiredVersion = null,622 Version minimumVersion = null,623 Version maximumVersion = null)624 {625 return IsVersionMatchingConstraints(out ModuleMatchFailure matchFailureReason, version, requiredVersion, minimumVersion, maximumVersion);626 }627 628 /// <summary>629 /// Check that a given module version matches the required or minimum/maximum version constraints.630 /// Null constraints are not checked.631 /// </summary>632 /// <param name="matchFailureReason">The reason why the match failed.</param>633 /// <param name="version">The module version to check. Must not be null.</param>634 /// <param name="requiredVersion">The version that the given version must be, if not null.</param>635 /// <param name="minimumVersion">The minimum version that the given version must be greater than or equal to, if not null.</param>636 /// <param name="maximumVersion">The maximum version that the given version must be less then or equal to, if not null.</param>637 /// <returns>638 /// True if the version matches the required version, or if it is absent, is between the minimum and maximum versions, and false otherwise.639 /// </returns>640 internal static bool IsVersionMatchingConstraints(641 out ModuleMatchFailure matchFailureReason,642 Version version,643 Version requiredVersion = null,644 Version minimumVersion = null,645 Version maximumVersion = null)646 {647 Dbg.Assert(version != null, $"Caller to verify that {nameof(version)} is not null");648 649 // If a RequiredVersion is given it overrides other version settings650 if (requiredVersion != null)651 {652 matchFailureReason = ModuleMatchFailure.RequiredVersion;653 return requiredVersion.Equals(version);654 }655 656 // Check the version is at least the minimum version657 if (minimumVersion != null && version < minimumVersion)658 {659 matchFailureReason = ModuleMatchFailure.MinimumVersion;660 return false;661 }662 663 // Check the version is at most the maximum version664 if (maximumVersion != null && version > maximumVersion)665 {666 matchFailureReason = ModuleMatchFailure.MaximumVersion;667 return false;668 }669 670 matchFailureReason = ModuleMatchFailure.None;671 return true;672 }673 674 /// <summary>675 /// Checks whether a given module path is the same as676 /// a required path.677 /// </summary>678 /// <param name="modulePath">The path of the module whose path to check. This must be the path to the module file (.psd1, .psm1, .dll, etc).</param>679 /// <param name="requiredPath">The path of the required module. This may be the module directory path or the file path. Only normalized absolute paths will work for this.</param>680 /// <returns>True if the module path matches the required path, false otherwise.</returns>681 internal static bool MatchesModulePath(string modulePath, string requiredPath)682 {683 Dbg.Assert(requiredPath != null, $"Caller to verify that {nameof(requiredPath)} is not null");684 685 if (modulePath == null)686 {687 return false;688 }689 690#if UNIX691 const StringComparison strcmp = StringComparison.Ordinal;692#else693 const StringComparison strcmp = StringComparison.OrdinalIgnoreCase;694#endif695 696 // We must check modulePath (e.g. /path/to/module/module.psd1) against several possibilities:697 // 1. "/path/to/module" - Module dir path698 // 2. "/path/to/module/module.psd1" - Module root file path699 // 3. "/path/to/module/2.1/module.psd1" - Versioned module path700 701 // If the required module just matches the module path (case 1), we are done702 if (modulePath.Equals(requiredPath, strcmp))703 {704 return true;705 }706 707 // At this point we are looking for the module directory (case 2 or 3).708 // We can some allocations here if module path doesn't sit under the required path709 // (the required path may still refer to some nested module though)710 if (!modulePath.StartsWith(requiredPath, strcmp))711 {712 return false;713 }714 715 string moduleDirPath = Path.GetDirectoryName(modulePath);716 717 // The module itself may be in a versioned directory (case 3)718 if (Version.TryParse(Path.GetFileName(moduleDirPath), out _))719 {720 moduleDirPath = Path.GetDirectoryName(moduleDirPath);721 }722 723 return moduleDirPath.Equals(requiredPath, strcmp);724 }725 726 /// <summary>727 /// Takes the name of a module as used in a module specification728 /// and either returns it as a simple name (if it was a simple name)729 /// or a fully qualified, PowerShell-resolved path.730 /// </summary>731 /// <param name="moduleName">The name or path of the module from the specification.</param>732 /// <param name="basePath">The path to base relative paths off.</param>733 /// <param name="executionContext">The current execution context.</param>734 /// <returns>735 /// The simple module name if the given one was simple,736 /// otherwise a fully resolved, absolute path to the module.737 /// </returns>738 /// <remarks>739 /// 2018-11-09 rjmholt:740 /// There are several, possibly inconsistent, path handling mechanisms741 /// in the module cmdlets. After looking through all of them and seeing742 /// they all make some assumptions about their caller I wrote this method.743 /// Hopefully we can find a standard path resolution API to settle on.744 /// </remarks>745 internal static string NormalizeModuleName(746 string moduleName,747 string basePath,748 ExecutionContext executionContext)749 {750 if (moduleName == null)751 {752 return null;753 }754 755 // Check whether the module is a path -- if not, it is a simple name and we just return it.756 if (!IsModuleNamePath(moduleName))757 {758 return moduleName;759 }760 761 // Standardize directory separators -- Path.IsPathRooted() will return false for "\path\here" on *nix and for "/path/there" on Windows762 moduleName = moduleName.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);763 764 // Note: Path.IsFullyQualified("\default\root") is false on Windows, but Path.IsPathRooted returns true765 if (!Path.IsPathRooted(moduleName))766 {767 moduleName = Path.Join(basePath, moduleName);768 }769 770 // Use the PowerShell filesystem provider to fully resolve the path771 // If there is a problem, null could be returned -- so default back to the pre-normalized path772 string normalizedPath = ModuleCmdletBase.GetResolvedPath(moduleName, executionContext)?.TrimEnd(StringLiterals.DefaultPathSeparator);773 774 // ModuleCmdletBase.GetResolvePath will return null in the unlikely event that it failed.775 // If it does, we return the fully qualified path generated before.776 return normalizedPath ?? Path.GetFullPath(moduleName);777 }778 779 /// <summary>780 /// Check if a given module name is a path to a module rather than a simple name.781 /// </summary>782 /// <param name="moduleName">The module name to check.</param>783 /// <returns>True if the module name is a path, false otherwise.</returns>784 internal static bool IsModuleNamePath(string moduleName)785 {786 return moduleName.Contains(StringLiterals.DefaultPathSeparator)787 || moduleName.Contains(StringLiterals.AlternatePathSeparator)788 || moduleName.Equals("..")789 || moduleName.Equals(".");790 }791 792 internal static Version GetManifestModuleVersion(string manifestPath)793 {794 try795 {796 Hashtable dataFileSetting =797 PsUtils.GetModuleManifestProperties(798 manifestPath,799 PsUtils.ManifestModuleVersionPropertyName);800 801 object versionValue = dataFileSetting["ModuleVersion"];802 if (versionValue != null)803 {804 Version moduleVersion;805 if (LanguagePrimitives.TryConvertTo(versionValue, out moduleVersion))806 {807 return moduleVersion;808 }809 }810 }811 catch (PSInvalidOperationException) { }812 813 return new Version(0, 0);814 }815 816 internal static Guid GetManifestGuid(string manifestPath)817 {818 try819 {820 Hashtable dataFileSetting =821 PsUtils.GetModuleManifestProperties(822 manifestPath,823 PsUtils.ManifestGuidPropertyName);824 825 object guidValue = dataFileSetting["GUID"];826 if (guidValue != null)827 {828 Guid guidID;829 if (LanguagePrimitives.TryConvertTo(guidValue, out guidID))830 {831 return guidID;832 }833 }834 }835 catch (PSInvalidOperationException) { }836 837 return new Guid();838 }839 840 internal static ExperimentalFeature[] GetExperimentalFeature(string manifestPath)841 {842 try843 {844 Hashtable dataFileSetting =845 PsUtils.GetModuleManifestProperties(846 manifestPath,847 PsUtils.ManifestPrivateDataPropertyName);848 849 object privateData = dataFileSetting["PrivateData"];850 if (privateData is Hashtable hashData && hashData["PSData"] is Hashtable psData)851 {852 object expFeatureValue = psData["ExperimentalFeatures"];853 if (expFeatureValue != null &&854 LanguagePrimitives.TryConvertTo(expFeatureValue, out Hashtable[] features) &&855 features.Length > 0)856 {857 string moduleName = ModuleIntrinsics.GetModuleName(manifestPath);858 var expFeatureList = new List<ExperimentalFeature>();859 foreach (Hashtable feature in features)860 {861 string featureName = feature["Name"] as string;862 if (string.IsNullOrEmpty(featureName))863 {864 continue;865 }866 867 if (ExperimentalFeature.IsModuleFeatureName(featureName, moduleName))868 {869 string featureDescription = feature["Description"] as string;870 expFeatureList.Add(new ExperimentalFeature(featureName, featureDescription, manifestPath,871 ExperimentalFeature.IsEnabled(featureName)));872 }873 }874 875 return expFeatureList.ToArray();876 }877 }878 }879 catch (PSInvalidOperationException) { }880 881 return Array.Empty<ExperimentalFeature>();882 }883 884 // The extensions of all of the files that can be processed with Import-Module, put the ni.dll in front of .dll to have higher priority to be loaded.885 internal static readonly string[] PSModuleProcessableExtensions = new string[]886 {887 StringLiterals.PowerShellDataFileExtension,888 StringLiterals.PowerShellScriptFileExtension,889 StringLiterals.PowerShellModuleFileExtension,890 StringLiterals.PowerShellCmdletizationFileExtension,891 StringLiterals.PowerShellNgenAssemblyExtension,892 StringLiterals.PowerShellILAssemblyExtension,893 StringLiterals.PowerShellILExecutableExtension,894 };895 896 // A list of the extensions to check for implicit module loading and discovery, put the ni.dll in front of .dll to have higher priority to be loaded.897 internal static readonly string[] PSModuleExtensions = new string[]898 {899 StringLiterals.PowerShellDataFileExtension,900 StringLiterals.PowerShellModuleFileExtension,901 StringLiterals.PowerShellCmdletizationFileExtension,902 StringLiterals.PowerShellNgenAssemblyExtension,903 StringLiterals.PowerShellILAssemblyExtension,904 StringLiterals.PowerShellILExecutableExtension,905 };906 907 // A list of the extensions to check for required assemblies.908 internal static readonly string[] ProcessableAssemblyExtensions = new string[]909 {910 StringLiterals.PowerShellNgenAssemblyExtension,911 StringLiterals.PowerShellILAssemblyExtension,912 StringLiterals.PowerShellILExecutableExtension913 };914 915 /// <summary>916 /// Returns true if the extension is one of the module extensions...917 /// </summary>918 /// <param name="extension">The extension to check.</param>919 /// <returns>True if it was a module extension...</returns>920 internal static bool IsPowerShellModuleExtension(string extension)921 {922 foreach (string ext in PSModuleProcessableExtensions)923 {924 if (extension.Equals(ext, StringComparison.OrdinalIgnoreCase))925 {926 return true;927 }928 }929 930 return false;931 }932 933 /// <summary>934 /// Gets the module name from module path.935 /// </summary>936 /// <param name="path">The path to the module.</param>937 /// <returns>The module name.</returns>938 internal static string GetModuleName(string path)939 {940 string fileName = path == null ? string.Empty : Path.GetFileName(path);941 string ext;942 if (fileName.EndsWith(StringLiterals.PowerShellNgenAssemblyExtension, StringComparison.OrdinalIgnoreCase))943 {944 ext = StringLiterals.PowerShellNgenAssemblyExtension;945 }946 else947 {948 ext = Path.GetExtension(fileName);949 }950 951 if (!string.IsNullOrEmpty(ext) && IsPowerShellModuleExtension(ext))952 {953 return fileName.Substring(0, fileName.Length - ext.Length);954 }955 else956 {957 return fileName;958 }959 }960 961 /// <summary>962 /// Gets the personal module path.963 /// </summary>964 /// <returns>Personal module path.</returns>965 internal static string GetPersonalModulePath()966 {967#if UNIX968 return Platform.SelectProductNameForDirectory(Platform.XDG_Type.USER_MODULES);969#else970 string myDocumentsPath = InternalTestHooks.SetMyDocumentsSpecialFolderToBlank ? string.Empty : Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);971 return string.IsNullOrEmpty(myDocumentsPath) ? null : Path.Combine(myDocumentsPath, Utils.ModuleDirectory);972#endif973 }974 975 /// <summary>976 /// Gets the PSHome module path, as known as the "system wide module path" in windows powershell.977 /// </summary>978 /// <returns>The PSHome module path.</returns>979 internal static string GetPSHomeModulePath()980 {981 if (s_psHomeModulePath != null)982 {983 return s_psHomeModulePath;984 }985 986 try987 {988 string psHome = Utils.DefaultPowerShellAppBase;989#if !UNIX990 // Win8: 584267 Powershell Modules are listed twice in x86, and cannot be removed.991 // This happens because 'ModuleTable' uses Path as the key and x86 WinPS has "SysWOW64" in its $PSHOME.992 // Because of this, the module that is getting loaded during startup (through LocalRunspace) is using993 // "SysWow64" in the key. Later, when 'Import-Module' is called, it loads the module using ""System32"994 // in the key.995 // For the cross-platform PowerShell, a user can choose to install it under "C:\Windows\SysWOW64", and996 // thus it may have the same problem as described above. So we keep this line of code.997 psHome = psHome.ToLowerInvariant().Replace(@"\syswow64\", @"\system32\");998#endif999 Interlocked.CompareExchange(ref s_psHomeModulePath, Path.Combine(psHome, "Modules"), null);1000 }1001 catch (System.Security.SecurityException)1002 {1003 }1004 1005 return s_psHomeModulePath;1006 }1007 1008 private static string s_psHomeModulePath;1009 1010 /// <summary>1011 /// Get the module path that is shared among different users.1012 /// It's known as "Program Files" module path in windows powershell.1013 /// </summary>1014 /// <returns></returns>1015 internal static string GetSharedModulePath()1016 {1017#if UNIX1018 return Platform.SelectProductNameForDirectory(Platform.XDG_Type.SHARED_MODULES);1019#else1020 string sharedModulePath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);1021 1022 if (!string.IsNullOrEmpty(sharedModulePath))1023 {1024 sharedModulePath = Path.Combine(sharedModulePath, Utils.ModuleDirectory);1025 }1026 1027 return sharedModulePath;1028#endif1029 }1030 1031#if !UNIX1032 /// <summary>1033 /// Get the path to the Windows PowerShell module directory under the1034 /// System32 directory on Windows (the Windows PowerShell $PSHOME).1035 /// </summary>1036 /// <returns>The path of the Windows PowerShell system module directory.</returns>1037 internal static string GetWindowsPowerShellPSHomeModulePath()1038 {1039 if (!string.IsNullOrEmpty(InternalTestHooks.TestWindowsPowerShellPSHomeLocation))1040 {1041 return InternalTestHooks.TestWindowsPowerShellPSHomeLocation;1042 }1043 1044 return s_windowsPowerShellPSHomeModulePath;1045 }1046#endif1047 1048 /// <summary>1049 /// Combine the PS system-wide module path and the DSC module path1050 /// to get the system module paths.1051 /// </summary>1052 /// <returns></returns>1053 private static string CombineSystemModulePaths()1054 {1055 string psHomeModulePath = GetPSHomeModulePath();1056 string sharedModulePath = GetSharedModulePath();1057 1058 bool isPSHomePathNullOrEmpty = string.IsNullOrEmpty(psHomeModulePath);1059 bool isSharedPathNullOrEmpty = string.IsNullOrEmpty(sharedModulePath);1060 1061 if (!isPSHomePathNullOrEmpty && !isSharedPathNullOrEmpty)1062 {1063 return (sharedModulePath + Path.PathSeparator + psHomeModulePath);1064 }1065 1066 if (!isPSHomePathNullOrEmpty || !isSharedPathNullOrEmpty)1067 {1068 return isPSHomePathNullOrEmpty ? sharedModulePath : psHomeModulePath;1069 }1070 1071 return null;1072 }1073 1074 internal static string GetExpandedEnvironmentVariable(string name, EnvironmentVariableTarget target)1075 {1076 string result = Environment.GetEnvironmentVariable(name, target);1077 if (!string.IsNullOrEmpty(result))1078 {1079 result = Environment.ExpandEnvironmentVariables(result);1080 }1081 1082 return result;1083 }1084 1085 /// <summary>1086 /// Adds paths to a 'combined path' string (like %Path% or %PSModulePath%) if they are not already there.1087 /// </summary>1088 /// <param name="basePath">Path string (like %Path% or %PSModulePath%).</param>1089 /// <param name="pathToAdd">An individual path to add, or multiple paths separated by the path separator character.</param>1090 /// <param name="insertPosition">-1 to append to the end; 0 to insert in the beginning of the string; etc...</param>1091 /// <returns>Result string.</returns>1092 private static string UpdatePath(string basePath, string pathToAdd, ref int insertPosition)1093 {1094 // we don't support if any of the args are null - parent function should ensure this; empty values are ok1095 Dbg.Assert(basePath != null, "basePath should not be null according to contract of the function");1096 Dbg.Assert(pathToAdd != null, "pathToAdd should not be null according to contract of the function");1097 1098 // The 'pathToAdd' could be a 'combined path' (path-separator-separated).1099 string[] newPaths = pathToAdd.Split(1100 Path.PathSeparator,1101 StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);1102 1103 if (newPaths.Length is 0)1104 {1105 // The 'pathToAdd' doesn't really contain any paths to add.1106 return basePath;1107 }1108 1109 var result = new StringBuilder(basePath, capacity: basePath.Length + pathToAdd.Length + newPaths.Length);1110 var addedPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);1111 string[] initialPaths = basePath.Split(1112 Path.PathSeparator,1113 StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);1114 1115 foreach (string p in initialPaths)1116 {1117 // Remove the trailing directory separators.1118 // Trailing white spaces were already removed by 'StringSplitOptions.TrimEntries'.1119 addedPaths.Add(Path.TrimEndingDirectorySeparator(p));1120 }1121 1122 foreach (string subPathToAdd in newPaths)1123 {1124 // Remove the trailing directory separators.1125 // Trailing white spaces were already removed by 'StringSplitOptions.TrimEntries'.1126 string normalizedPath = Path.TrimEndingDirectorySeparator(subPathToAdd);1127 if (addedPaths.Contains(normalizedPath))1128 {1129 // The normalized sub path was already added - skip it.1130 continue;1131 }1132 1133 // The normalized sub path was not found - add it.1134 if (insertPosition is -1 || insertPosition >= result.Length)1135 {1136 // Append the normalized sub path to the end.1137 if (result.Length > 0 && result[^1] != Path.PathSeparator)1138 {1139 result.Append(Path.PathSeparator);1140 }1141 1142 result.Append(normalizedPath);1143 // Next insertion should happen at the end.1144 insertPosition = result.Length;1145 }1146 else1147 {1148 // Insert at the requested location.1149 // This is used by the user-specific module path, the shared module path (<Program Files> location), and the PSHome module path.1150 string strToInsert = normalizedPath + Path.PathSeparator;1151 result.Insert(insertPosition, strToInsert);1152 1153 // Next insertion should happen after the just inserted string.1154 insertPosition += strToInsert.Length;1155 }1156 1157 // Add it to the set.1158 addedPaths.Add(normalizedPath);1159 }1160 1161 return result.ToString();1162 }1163 1164 /// <summary>1165 /// The available module path scopes.1166 /// </summary>1167 public enum PSModulePathScope1168 {1169 /// <summary>The users module path.</summary>1170 User,1171 1172 /// <summary>The Builtin module path. This is where PowerShell is installed (PSHOME).</summary>1173 Builtin,1174 1175 /// <summary>The machine module path. This is the shared location for all users of the system.</summary>1176 Machine1177 }1178 1179 /// <summary>1180 /// Retrieve the current PSModulePath for the specified scope.1181 /// </summary>1182 /// <param name="scope">The scope of module path to retrieve. This can be User, Builtin, or Machine.</param>1183 /// <returns>The string representing the requested module path type.</returns>1184 public static string GetPSModulePath(PSModulePathScope scope)1185 {1186 if (scope == PSModulePathScope.User)1187 {1188 return GetPersonalModulePath();1189 }1190 else if (scope == PSModulePathScope.Builtin)1191 {1192 return GetPSHomeModulePath();1193 }1194 else1195 {1196 return GetSharedModulePath();1197 }1198 }1199 1200 /// <summary>