MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections;6using System.Collections.Generic;7using System.Diagnostics.CodeAnalysis;8using System.Globalization;9using System.IO;10using System.Linq;11using System.Management.Automation;12using System.Management.Automation.Internal;13using System.Management.Automation.Language;14using System.Management.Automation.Runspaces;15using System.Threading;16 17using Microsoft.Management.Infrastructure;18 19using Dbg = System.Management.Automation.Diagnostics;20 21//22// Now define the set of commands for manipulating modules.23//24 25namespace Microsoft.PowerShell.Commands26{27 /// <summary>28 /// Implements a cmdlet that gets the list of loaded modules...29 /// </summary>30 [Cmdlet(VerbsCommon.Get, "Module", DefaultParameterSetName = ParameterSet_Loaded,31 HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096696")]32 [OutputType(typeof(PSModuleInfo))]33 public sealed class GetModuleCommand : ModuleCmdletBase, IDisposable34 {35 #region Cmdlet parameters36 37 private const string ParameterSet_Loaded = "Loaded";38 private const string ParameterSet_AvailableLocally = "Available";39 private const string ParameterSet_AvailableInPsrpSession = "PsSession";40 private const string ParameterSet_AvailableInCimSession = "CimSession";41 42 /// <summary>43 /// This parameter specifies the current pipeline object.44 /// </summary>45 [Parameter(ParameterSetName = ParameterSet_Loaded, ValueFromPipeline = true, Position = 0)]46 [Parameter(ParameterSetName = ParameterSet_AvailableLocally, ValueFromPipeline = true, Position = 0)]47 [Parameter(ParameterSetName = ParameterSet_AvailableInPsrpSession, ValueFromPipeline = true, Position = 0)]48 [Parameter(ParameterSetName = ParameterSet_AvailableInCimSession, ValueFromPipeline = true, Position = 0)]49 [ValidateNotNullOrEmpty]50 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays",51 Justification = "Cmdlets use arrays for parameters.")]52 public string[] Name { get; set; }53 54 /// <summary>55 /// This parameter specifies the current pipeline object.56 /// </summary>57 [Parameter(ParameterSetName = ParameterSet_Loaded, ValueFromPipelineByPropertyName = true)]58 [Parameter(ParameterSetName = ParameterSet_AvailableLocally, ValueFromPipelineByPropertyName = true)]59 [Parameter(ParameterSetName = ParameterSet_AvailableInPsrpSession, ValueFromPipelineByPropertyName = true)]60 [Parameter(ParameterSetName = ParameterSet_AvailableInCimSession, ValueFromPipelineByPropertyName = true)]61 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays",62 Justification = "Cmdlets use arrays for parameters.")]63 public ModuleSpecification[] FullyQualifiedName { get; set; }64 65 /// <summary>66 /// If specified, all loaded modules should be returned, otherwise only the visible67 /// modules should be returned.68 /// </summary>69 [Parameter(ParameterSetName = ParameterSet_Loaded)]70 [Parameter(ParameterSetName = ParameterSet_AvailableLocally)]71 public SwitchParameter All { get; set; }72 73 /// <summary>74 /// If specified, then Get-Module will return the set of available modules...75 /// </summary>76 [Parameter(ParameterSetName = ParameterSet_AvailableLocally, Mandatory = true)]77 [Parameter(ParameterSetName = ParameterSet_AvailableInPsrpSession)]78 [Parameter(ParameterSetName = ParameterSet_AvailableInCimSession)]79 public SwitchParameter ListAvailable { get; set; }80 81 /// <summary>82 /// If specified, then Get-Module will return the set of available modules which supports the specified PowerShell edition...83 /// </summary>84 [Parameter(ParameterSetName = ParameterSet_AvailableLocally)]85 [Parameter(ParameterSetName = ParameterSet_AvailableInPsrpSession)]86 [ArgumentCompleter(typeof(PSEditionArgumentCompleter))]87 public string PSEdition { get; set; }88 89 /// <summary>90 /// When set, CompatiblePSEditions checking is disabled for modules in the System32 (Windows PowerShell) module directory.91 /// </summary>92 [Parameter(ParameterSetName = ParameterSet_AvailableLocally)]93 [Parameter(ParameterSetName = ParameterSet_AvailableInPsrpSession)]94 [Parameter(ParameterSetName = ParameterSet_AvailableInCimSession)]95 public SwitchParameter SkipEditionCheck96 {97 get { return (SwitchParameter)BaseSkipEditionCheck; }98 99 set { BaseSkipEditionCheck = value; }100 }101 102 /// <summary>103 /// If specified, then Get-Module refreshes the internal cmdlet analysis cache.104 /// </summary>105 [Parameter(ParameterSetName = ParameterSet_AvailableLocally)]106 [Parameter(ParameterSetName = ParameterSet_AvailableInPsrpSession)]107 [Parameter(ParameterSetName = ParameterSet_AvailableInCimSession)]108 public SwitchParameter Refresh { get; set; }109 110 /// <summary>111 /// If specified, then Get-Module will attempt to discover PowerShell modules on a remote computer using the specified session.112 /// </summary>113 [Parameter(ParameterSetName = ParameterSet_AvailableInPsrpSession, Mandatory = true)]114 [ValidateNotNull]115 public PSSession PSSession { get; set; }116 117 /// <summary>118 /// If specified, then Get-Module will attempt to discover PS-CIM modules on a remote computer using the specified session.119 /// </summary>120 [Parameter(ParameterSetName = ParameterSet_AvailableInCimSession, Mandatory = true)]121 [ValidateNotNull]122 public CimSession CimSession { get; set; }123 124 /// <summary>125 /// For interoperability with 3rd party CIM servers, user can specify custom resource URI.126 /// </summary>127 [Parameter(ParameterSetName = ParameterSet_AvailableInCimSession, Mandatory = false)]128 [ValidateNotNull]129 public Uri CimResourceUri { get; set; }130 131 /// <summary>132 /// For interoperability with 3rd party CIM servers, user can specify custom namespace.133 /// </summary>134 [Parameter(ParameterSetName = ParameterSet_AvailableInCimSession, Mandatory = false)]135 [ValidateNotNullOrEmpty]136 public string CimNamespace { get; set; }137 138 #endregion Cmdlet parameters139 140 #region Remote discovery141 142 private IEnumerable<PSModuleInfo> GetAvailableViaPsrpSessionCore(string[] moduleNames, Runspace remoteRunspace)143 {144 Dbg.Assert(remoteRunspace != null, "Caller should verify remoteRunspace != null");145 146 using (var powerShell = System.Management.Automation.PowerShell.Create())147 {148 powerShell.Runspace = remoteRunspace;149 powerShell.AddCommand("Get-Module");150 powerShell.AddParameter("ListAvailable", true);151 152 if (Refresh.IsPresent)153 {154 powerShell.AddParameter("Refresh", true);155 }156 157 if (moduleNames != null)158 {159 powerShell.AddParameter("Name", moduleNames);160 }161 162 string errorMessageTemplate = string.Format(163 CultureInfo.InvariantCulture,164 Modules.RemoteDiscoveryRemotePsrpCommandFailed,165 "Get-Module");166 foreach (167 PSObject outputObject in168 RemoteDiscoveryHelper.InvokePowerShell(powerShell, this, errorMessageTemplate,169 this.CancellationToken))170 {171 PSModuleInfo moduleInfo = RemoteDiscoveryHelper.RehydratePSModuleInfo(outputObject);172 yield return moduleInfo;173 }174 }175 }176 177 private static PSModuleInfo GetModuleInfoForRemoteModuleWithoutManifest(RemoteDiscoveryHelper.CimModule cimModule)178 {179 return new PSModuleInfo(cimModule.ModuleName, null, null);180 }181 182 private PSModuleInfo ConvertCimModuleInfoToPSModuleInfo(RemoteDiscoveryHelper.CimModule cimModule,183 string computerName)184 {185 try186 {187 bool containedErrors = false;188 189 if (cimModule.MainManifest == null)190 {191 return GetModuleInfoForRemoteModuleWithoutManifest(cimModule);192 }193 194 string temporaryModuleManifestPath = Path.Combine(195 RemoteDiscoveryHelper.GetModulePath(cimModule.ModuleName, null, computerName,196 this.Context.CurrentRunspace),197 Path.GetFileName(cimModule.ModuleName));198 199 Hashtable mainData = null;200 if (!containedErrors)201 {202 mainData = RemoteDiscoveryHelper.ConvertCimModuleFileToManifestHashtable(203 cimModule.MainManifest,204 temporaryModuleManifestPath,205 this,206 ref containedErrors);207 if (mainData == null)208 {209 return GetModuleInfoForRemoteModuleWithoutManifest(cimModule);210 }211 }212 213 if (!containedErrors)214 {215 mainData = RemoteDiscoveryHelper.RewriteManifest(mainData);216 }217 218 Hashtable localizedData = mainData; // TODO/FIXME - this needs full path support from the provider219 220 PSModuleInfo moduleInfo = null;221 if (!containedErrors)222 {223 ImportModuleOptions throwAwayOptions = new ImportModuleOptions();224 moduleInfo = LoadModuleManifest(225 temporaryModuleManifestPath,226 null, // scriptInfo227 mainData,228 localizedData,229 0 /* - don't write errors, don't load elements, don't return null on first error */,230 this.BaseMinimumVersion,231 this.BaseMaximumVersion,232 this.BaseRequiredVersion,233 this.BaseGuid,234 ref throwAwayOptions,235 ref containedErrors);236 }237 238 if ((moduleInfo == null) || containedErrors)239 {240 moduleInfo = GetModuleInfoForRemoteModuleWithoutManifest(cimModule);241 }242 243 return moduleInfo;244 }245 catch (Exception e)246 {247 ErrorRecord errorRecord = RemoteDiscoveryHelper.GetErrorRecordForProcessingOfCimModule(e, cimModule.ModuleName);248 this.WriteError(errorRecord);249 return null;250 }251 }252 253 private IEnumerable<PSModuleInfo> GetAvailableViaCimSessionCore(IEnumerable<string> moduleNames,254 CimSession cimSession, Uri resourceUri,255 string cimNamespace)256 {257 IEnumerable<RemoteDiscoveryHelper.CimModule> remoteModules = RemoteDiscoveryHelper.GetCimModules(258 cimSession,259 resourceUri,260 cimNamespace,261 moduleNames,262 true /* onlyManifests */,263 this,264 this.CancellationToken);265 266 IEnumerable<PSModuleInfo> remoteModuleInfos = remoteModules267 .Select(cimModule => this.ConvertCimModuleInfoToPSModuleInfo(cimModule, cimSession.ComputerName))268 .Where(static moduleInfo => moduleInfo != null);269 270 return remoteModuleInfos;271 }272 273 #endregion Remote discovery274 275 #region Cancellation support276 277 private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();278 279 private CancellationToken CancellationToken280 {281 get { return _cancellationTokenSource.Token; }282 }283 284 /// <summary>285 /// When overridden in the derived class, interrupts currently286 /// running code within the command. It should interrupt BeginProcessing,287 /// ProcessRecord, and EndProcessing.288 /// Default implementation in the base class just returns.289 /// </summary>290 protected override void StopProcessing()291 {292 _cancellationTokenSource.Cancel();293 }294 295 #endregion296 297 #region IDisposable Members298 299 /// <summary>300 /// Release all resources.301 /// </summary>302 public void Dispose()303 {304 if (_disposed)305 {306 return;307 }308 309 _cancellationTokenSource.Dispose();310 311 _disposed = true;312 }313 314 private bool _disposed;315 316 #endregion317 318 private void AssertListAvailableMode()319 {320 if (!this.ListAvailable.IsPresent)321 {322 string errorMessage = Modules.RemoteDiscoveryWorksOnlyInListAvailableMode;323 ArgumentException argumentException = new ArgumentException(errorMessage);324 ErrorRecord errorRecord = new ErrorRecord(325 argumentException,326 "RemoteDiscoveryWorksOnlyInListAvailableMode",327 ErrorCategory.InvalidArgument,328 null);329 this.ThrowTerminatingError(errorRecord);330 }331 }332 333 /// <summary>334 /// Write out the specified modules...335 /// </summary>336 protected override void ProcessRecord()337 {338 // Name and FullyQualifiedName should not be specified at the same time.339 // Throw out terminating error if this is the case.340 if ((Name != null) && (FullyQualifiedName != null))341 {342 string errMsg = StringUtil.Format(SessionStateStrings.GetContent_TailAndHeadCannotCoexist, "Name", "FullyQualifiedName");343 ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "NameAndFullyQualifiedNameCannotBeSpecifiedTogether", ErrorCategory.InvalidOperation, null);344 ThrowTerminatingError(error);345 }346 347 // -SkipEditionCheck only makes sense for -ListAvailable (otherwise the module is already loaded)348 if (SkipEditionCheck && !ListAvailable)349 {350 ErrorRecord error = new ErrorRecord(351 new InvalidOperationException(Modules.SkipEditionCheckNotSupportedWithoutListAvailable),352 nameof(Modules.SkipEditionCheckNotSupportedWithoutListAvailable),353 ErrorCategory.InvalidOperation,354 targetObject: null);355 356 ThrowTerminatingError(error);357 }358 359 var strNames = new List<string>();360 if (Name != null)361 {362 strNames.AddRange(Name);363 }364 365 var moduleSpecTable = new Dictionary<string, ModuleSpecification>(StringComparer.OrdinalIgnoreCase);366 if (FullyQualifiedName != null)367 {368 for (int modSpecIndex = 0; modSpecIndex < FullyQualifiedName.Length; modSpecIndex++)369 {370 FullyQualifiedName[modSpecIndex] = FullyQualifiedName[modSpecIndex].WithNormalizedName(Context, SessionState.Path.CurrentLocation.Path);371 }372 373 moduleSpecTable = FullyQualifiedName.ToDictionary(static moduleSpecification => moduleSpecification.Name, StringComparer.OrdinalIgnoreCase);374 strNames.AddRange(FullyQualifiedName.Select(static spec => spec.Name));375 }376 377 string[] names = strNames.Count > 0 ? strNames.ToArray() : null;378 379 if (ParameterSetName.Equals(ParameterSet_Loaded, StringComparison.OrdinalIgnoreCase))380 {381 AssertNameDoesNotResolveToAPath(names,382 Modules.ModuleDiscoveryForLoadedModulesWorksOnlyForUnQualifiedNames,383 "ModuleDiscoveryForLoadedModulesWorksOnlyForUnQualifiedNames");384 GetLoadedModules(names, moduleSpecTable, this.All);385 }386 else if (ParameterSetName.Equals(ParameterSet_AvailableLocally, StringComparison.OrdinalIgnoreCase))387 {388 if (ListAvailable.IsPresent)389 {390 GetAvailableLocallyModules(names, moduleSpecTable, this.All);391 }392 else393 {394 AssertNameDoesNotResolveToAPath(names,395 Modules.ModuleDiscoveryForLoadedModulesWorksOnlyForUnQualifiedNames,396 "ModuleDiscoveryForLoadedModulesWorksOnlyForUnQualifiedNames");397 GetLoadedModules(names, moduleSpecTable, this.All);398 }399 }400 else if (ParameterSetName.Equals(ParameterSet_AvailableInPsrpSession, StringComparison.OrdinalIgnoreCase))401 {402 AssertListAvailableMode();403 AssertNameDoesNotResolveToAPath(names,404 Modules.RemoteDiscoveryWorksOnlyForUnQualifiedNames,405 "RemoteDiscoveryWorksOnlyForUnQualifiedNames");406 407 GetAvailableViaPsrpSession(names, moduleSpecTable, this.PSSession);408 }409 else if (ParameterSetName.Equals(ParameterSet_AvailableInCimSession, StringComparison.OrdinalIgnoreCase))410 {411 AssertListAvailableMode();412 AssertNameDoesNotResolveToAPath(names,413 Modules.RemoteDiscoveryWorksOnlyForUnQualifiedNames,414 "RemoteDiscoveryWorksOnlyForUnQualifiedNames");415 416 GetAvailableViaCimSession(names, moduleSpecTable, this.CimSession,417 this.CimResourceUri, this.CimNamespace);418 }419 else420 {421 Dbg.Assert(false, "Unrecognized parameter set");422 }423 }424 425 private void AssertNameDoesNotResolveToAPath(string[] names, string stringFormat, string resourceId)426 {427 if (names != null)428 {429 foreach (var n in names)430 {431 if (n.Contains(StringLiterals.DefaultPathSeparator) || n.Contains(StringLiterals.AlternatePathSeparator))432 {433 string errorMessage = StringUtil.Format(stringFormat, n);434 var argumentException = new ArgumentException(errorMessage);435 var errorRecord = new ErrorRecord(436 argumentException,437 resourceId,438 ErrorCategory.InvalidArgument,439 n);440 this.ThrowTerminatingError(errorRecord);441 }442 }443 }444 }445 446 private void GetAvailableViaCimSession(IEnumerable<string> names, IDictionary<string, ModuleSpecification> moduleSpecTable,447 CimSession cimSession, Uri resourceUri, string cimNamespace)448 {449 IEnumerable<PSModuleInfo> remoteModules = GetAvailableViaCimSessionCore(names, cimSession, resourceUri, cimNamespace);450 451 foreach (PSModuleInfo remoteModule in FilterModulesForEditionAndSpecification(remoteModules, moduleSpecTable))452 {453 RemoteDiscoveryHelper.AssociatePSModuleInfoWithSession(remoteModule, cimSession, resourceUri,454 cimNamespace);455 this.WriteObject(remoteModule);456 }457 }458 459 private void GetAvailableViaPsrpSession(string[] names, IDictionary<string, ModuleSpecification> moduleSpecTable, PSSession session)460 {461 IEnumerable<PSModuleInfo> remoteModules = GetAvailableViaPsrpSessionCore(names, session.Runspace);462 463 foreach (PSModuleInfo remoteModule in FilterModulesForEditionAndSpecification(remoteModules, moduleSpecTable))464 {465 RemoteDiscoveryHelper.AssociatePSModuleInfoWithSession(remoteModule, session);466 this.WriteObject(remoteModule);467 }468 }469 470 private void GetAvailableLocallyModules(string[] names, IDictionary<string, ModuleSpecification> moduleSpecTable, bool all)471 {472 IEnumerable<PSModuleInfo> modules = GetModule(names, all, Refresh);473 foreach (PSModuleInfo module in FilterModulesForEditionAndSpecification(modules, moduleSpecTable))474 {475 var psModule = new PSObject(module);476 psModule.TypeNames.Insert(0, "ModuleInfoGrouping");477 WriteObject(psModule);478 }479 }480 481 private void GetLoadedModules(string[] names, IDictionary<string, ModuleSpecification> moduleSpecTable, bool all)482 {483 var modulesToWrite = Context.Modules.GetModules(names, all);484 485 foreach (PSModuleInfo moduleInfo in FilterModulesForEditionAndSpecification(modulesToWrite, moduleSpecTable))486 {487 WriteObject(moduleInfo);488 }489 }490 491 /// <summary>492 /// Filter an enumeration of PowerShell modules based on the required PowerShell edition493 /// and the module specification constraints set for each module (if any).494 /// </summary>495 /// <param name="modules">The modules to filter through.</param>496 /// <param name="moduleSpecificationTable">Module constraints, keyed by module name, to filter modules of that name by.</param>497 /// <returns>All modules from the original input that meet both any module edition and module specification constraints provided.</returns>498 private IEnumerable<PSModuleInfo> FilterModulesForEditionAndSpecification(499 IEnumerable<PSModuleInfo> modules,500 IDictionary<string, ModuleSpecification> moduleSpecificationTable)501 {502#if !UNIX503 // Edition check only applies to Windows System32 module path504 if (!SkipEditionCheck && ListAvailable && !All)505 {506 modules = modules.Where(static module => module.IsConsideredEditionCompatible);507 }508#endif509 510 if (!string.IsNullOrEmpty(PSEdition))511 {512 modules = modules.Where(module => module.CompatiblePSEditions.Contains(PSEdition, StringComparer.OrdinalIgnoreCase));513 }514 515 if (moduleSpecificationTable != null && moduleSpecificationTable.Count > 0)516 {517 modules = FilterModulesForSpecificationMatch(modules, moduleSpecificationTable);518 }519 520 return modules;521 }522 523 /// <summary>524 /// Take an enumeration of modules and only return those that match a specification525 /// in the given specification table, or have no corresponding entry in the specification table.526 /// </summary>527 /// <param name="modules">The modules to filter by specification match.</param>528 /// <param name="moduleSpecificationTable">The specification lookup table to filter the modules on.</param>529 /// <returns>The modules that match their corresponding table entry, or which have no table entry.</returns>530 private static IEnumerable<PSModuleInfo> FilterModulesForSpecificationMatch(531 IEnumerable<PSModuleInfo> modules,532 IDictionary<string, ModuleSpecification> moduleSpecificationTable)533 {534 Dbg.Assert(moduleSpecificationTable != null, $"Caller to verify that {nameof(moduleSpecificationTable)} is not null");535 Dbg.Assert(moduleSpecificationTable.Count != 0, $"Caller to verify that {nameof(moduleSpecificationTable)} is not empty");536 537 foreach (PSModuleInfo module in modules)538 {539 IEnumerable<ModuleSpecification> candidateModuleSpecs = GetCandidateModuleSpecs(moduleSpecificationTable, module);540 541 // Modules with table entries only get returned if they match them542 // We skip the name check since modules have already been prefiltered base on the moduleSpec path/name543 foreach (ModuleSpecification moduleSpec in candidateModuleSpecs)544 {545 if (ModuleIntrinsics.IsModuleMatchingModuleSpec(module, moduleSpec, skipNameCheck: true))546 {547 yield return module;548 }549 }550 }551 }552 553 /// <summary>554 /// Take a dictionary of module specifications and return those that potentially match the module555 /// passed in as a parameter (checks on names and paths).556 /// </summary>557 /// <param name="moduleSpecTable">The module specifications to filter candidates from.</param>558 /// <param name="module">The module to find candidates for from the module specification table.</param>559 /// <returns>The module specifications matching the module based on name, path and subpath.</returns>560 private static IEnumerable<ModuleSpecification> GetCandidateModuleSpecs(561 IDictionary<string, ModuleSpecification> moduleSpecTable,562 PSModuleInfo module)563 {564 const WildcardOptions options = WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant;565 foreach (ModuleSpecification moduleSpec in moduleSpecTable.Values)566 {567 WildcardPattern namePattern = WildcardPattern.Get(moduleSpec.Name, options);568 if (namePattern.IsMatch(module.Name) || moduleSpec.Name == module.Path || module.Path.Contains(moduleSpec.Name))569 {570 yield return moduleSpec;571 }572 }573 }574 }575 576 /// <summary>577 /// Provides argument completion for PSEdition parameter.578 /// </summary>579 public class PSEditionArgumentCompleter : IArgumentCompleter580 {581 /// <summary>582 /// Returns completion results for PSEdition parameter.583 /// </summary>584 /// <param name="commandName">The command name.</param>585 /// <param name="parameterName">The parameter name.</param>586 /// <param name="wordToComplete">The word to complete.</param>587 /// <param name="commandAst">The command AST.</param>588 /// <param name="fakeBoundParameters">The fake bound parameters.</param>589 /// <returns>List of completion results.</returns>590 public IEnumerable<CompletionResult> CompleteArgument(591 string commandName,592 string parameterName,593 string wordToComplete,594 CommandAst commandAst,595 IDictionary fakeBoundParameters)596 => CompletionHelpers.GetMatchingResults(wordToComplete, possibleCompletionValues: Utils.AllowedEditionValues);597 }598}599 