MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections.Generic;6using System.Diagnostics.CodeAnalysis;7using System.Linq;8using System.Management.Automation;9using System.Management.Automation.Internal;10using System.Management.Automation.Runspaces;11using System.Reflection;12 13using Dbg = System.Management.Automation.Diagnostics;14 15//16// Now define the set of commands for manipulating modules.17//18 19namespace Microsoft.PowerShell.Commands20{21 #region Remove-Module22 /// <summary>23 /// Implements a cmdlet that gets the list of loaded modules...24 /// </summary>25 [Cmdlet(VerbsCommon.Remove, "Module", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096802")]26 public sealed class RemoveModuleCommand : ModuleCmdletBase27 {28 /// <summary>29 /// This parameter specifies the current pipeline object.30 /// </summary>31 [Parameter(Mandatory = true, ParameterSetName = "name", ValueFromPipeline = true, Position = 0)]32 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")]33 public string[] Name34 {35 get { return _name; }36 37 set { _name = value; }38 }39 40 private string[] _name = Array.Empty<string>();41 42 /// <summary>43 /// This parameter specifies the current pipeline object.44 /// </summary>45 [Parameter(Mandatory = true, ParameterSetName = "FullyQualifiedName", ValueFromPipeline = true, Position = 0)]46 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")]47 public ModuleSpecification[] FullyQualifiedName { get; set; }48 49 /// <summary>50 /// This parameter specifies the current pipeline object.51 /// </summary>52 [Parameter(Mandatory = true, ParameterSetName = "ModuleInfo", ValueFromPipeline = true, Position = 0)]53 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")]54 public PSModuleInfo[] ModuleInfo55 {56 get { return _moduleInfo; }57 58 set { _moduleInfo = value; }59 }60 61 private PSModuleInfo[] _moduleInfo = Array.Empty<PSModuleInfo>();62 63 /// <summary>64 /// If provided, this parameter will allow readonly modules to be removed.65 /// </summary>66 [Parameter]67 public SwitchParameter Force68 {69 get { return BaseForce; }70 71 set { BaseForce = value; }72 }73 74 private int _numberRemoved = 0; // Maintains a count of the number of modules removed...75 76 /// <summary>77 /// Remove the specified modules. Modules can be specified either through a ModuleInfo or a name.78 /// </summary>79 protected override void ProcessRecord()80 {81 // This dictionary has the list of modules to be removed.82 // Key - Module specified as a parameter to Remove-Module83 // Values - List of all modules that need to be removed for this key (includes all nested modules of this module)84 Dictionary<PSModuleInfo, List<PSModuleInfo>> modulesToRemove = new Dictionary<PSModuleInfo, List<PSModuleInfo>>();85 86 foreach (var m in Context.Modules.GetModules(_name, false))87 {88 modulesToRemove.Add(m, new List<PSModuleInfo> { m });89 }90 91 if (FullyQualifiedName != null)92 {93 // TODO:94 // Paths in the module name may fail here because95 // they the wrong directory separator or are relative.96 // Fix with the code below:97 // FullyQualifiedName = FullyQualifiedName.Select(ms => ms.WithNormalizedName(Context, SessionState.Path.CurrentLocation.Path)).ToArray();98 foreach (var m in Context.Modules.GetModules(FullyQualifiedName, false))99 {100 modulesToRemove.Add(m, new List<PSModuleInfo> { m });101 }102 }103 104 foreach (var m in _moduleInfo)105 {106 modulesToRemove.Add(m, new List<PSModuleInfo> { m });107 }108 109 // Add any of the child modules of a manifests to the list of modules to remove...110 Dictionary<PSModuleInfo, List<PSModuleInfo>> nestedModules = new Dictionary<PSModuleInfo, List<PSModuleInfo>>();111 foreach (var entry in modulesToRemove)112 {113 var module = entry.Key;114 if (module.NestedModules != null && module.NestedModules.Count > 0)115 {116 List<PSModuleInfo> nestedModulesWithNoCircularReference = new List<PSModuleInfo>();117 GetAllNestedModules(module, ref nestedModulesWithNoCircularReference);118 nestedModules.Add(module, nestedModulesWithNoCircularReference);119 }120 }121 122 // dont add duplicates to our original modulesToRemove list..so that the123 // evaluation loop below will not duplicate in case of WriteError and WriteWarning.124 // A global list of modules to be removed is maintained for this purpose125 HashSet<PSModuleInfo> globalListOfModules = new HashSet<PSModuleInfo>(new PSModuleInfoComparer());126 127 if (nestedModules.Count > 0)128 {129 foreach (var entry in nestedModules)130 {131 List<PSModuleInfo> values = null;132 if (modulesToRemove.TryGetValue(entry.Key, out values))133 {134 foreach (var module in entry.Value)135 {136 if (!globalListOfModules.Contains(module))137 {138 values.Add(module);139 globalListOfModules.Add(module);140 }141 }142 }143 }144 }145 146 // Check the list of modules to remove and exclude those that cannot or should not be removed147 Dictionary<PSModuleInfo, List<PSModuleInfo>> actualModulesToRemove = new Dictionary<PSModuleInfo, List<PSModuleInfo>>();148 149 // We want to remove the modules starting from the nested modules150 // If we start from the parent module, the nested modules do not get removed and are left orphaned in the parent modules's sessionstate.151 foreach (var entry in modulesToRemove)152 {153 List<PSModuleInfo> moduleList = new List<PSModuleInfo>();154 for (int i = entry.Value.Count - 1; i >= 0; i--)155 {156 PSModuleInfo module = entry.Value[i];157 // See if the module is constant...158 if (module.AccessMode == ModuleAccessMode.Constant)159 {160 string message = StringUtil.Format(Modules.ModuleIsConstant, module.Name);161 InvalidOperationException moduleNotRemoved = new InvalidOperationException(message);162 ErrorRecord er = new ErrorRecord(moduleNotRemoved, "Modules_ModuleIsConstant",163 ErrorCategory.PermissionDenied, module);164 WriteError(er);165 continue;166 }167 168 // See if the module is readonly...169 if (module.AccessMode == ModuleAccessMode.ReadOnly && !BaseForce)170 {171 string message = StringUtil.Format(Modules.ModuleIsReadOnly, module.Name);172 173 if (InitialSessionState.IsConstantEngineModule(module.Name))174 {175 WriteWarning(message);176 }177 else178 {179 InvalidOperationException moduleNotRemoved = new InvalidOperationException(message);180 ErrorRecord er = new ErrorRecord(moduleNotRemoved, "Modules_ModuleIsReadOnly",181 ErrorCategory.PermissionDenied, module);182 WriteError(er);183 }184 185 continue;186 }187 188 if (!ShouldProcess(StringUtil.Format(Modules.ConfirmRemoveModule, module.Name, module.Path)))189 {190 continue;191 }192 193 // If this module provides the current session drive, then we cannot remove it.194 // Abort this command since we don't want to do a partial removal of a module manifest.195 if (ModuleProvidesCurrentSessionDrive(module))196 {197 if (InitialSessionState.IsEngineModule(module.Name))198 {199 if (!BaseForce)200 {201 string message = StringUtil.Format(Modules.CoreModuleCannotBeRemoved, module.Name);202 this.WriteWarning(message);203 }204 205 continue;206 }207 // Specify the overall module name if there is only one.208 // Otherwise specify the particular module name.209 string moduleName = (_name.Length == 1) ? _name[0] : module.Name;210 211 PSInvalidOperationException invalidOperation =212 PSTraceSource.NewInvalidOperationException(213 Modules.ModuleDriveInUse,214 moduleName);215 216 throw (invalidOperation);217 }218 219 // Add module to remove list.220 moduleList.Add(module);221 }222 223 actualModulesToRemove[entry.Key] = moduleList;224 }225 226 // Now remove the modules, first checking the RequiredModules dependencies227 Dictionary<PSModuleInfo, List<PSModuleInfo>> requiredDependencies = GetRequiredDependencies();228 229 foreach (var entry in actualModulesToRemove)230 {231 foreach (var module in entry.Value)232 {233 if (!BaseForce)234 {235 List<PSModuleInfo> requiredBy = null;236 237 if (requiredDependencies.TryGetValue(module, out requiredBy))238 {239 for (int i = requiredBy.Count - 1; i >= 0; i--)240 {241 if (actualModulesToRemove.ContainsKey(requiredBy[i]))242 {243 requiredBy.RemoveAt(i);244 }245 }246 247 if (requiredBy.Count > 0)248 {249 string message = StringUtil.Format(Modules.ModuleIsRequired, module.Name, requiredBy[0].Name);250 InvalidOperationException moduleNotRemoved = new InvalidOperationException(message);251 ErrorRecord er = new ErrorRecord(moduleNotRemoved, "Modules_ModuleIsRequired",252 ErrorCategory.PermissionDenied, module);253 WriteError(er);254 continue;255 }256 }257 }258 259 _numberRemoved++;260 261 this.RemoveModule(module, entry.Key.Name);262 }263 }264 }265 266 private bool ModuleProvidesCurrentSessionDrive(PSModuleInfo module)267 {268 if (module.ModuleType == ModuleType.Binary)269 {270 Dictionary<string, List<ProviderInfo>> providers = Context.TopLevelSessionState.Providers;271 foreach (KeyValuePair<string, List<ProviderInfo>> pList in providers)272 {273 Dbg.Assert(pList.Value != null, "There should never be a null list of entries in the provider table");274 foreach (ProviderInfo pInfo in pList.Value)275 {276 string implTypeAssemblyLocation = pInfo.ImplementingType.Assembly.Location;277 if (implTypeAssemblyLocation.Equals(module.Path, StringComparison.OrdinalIgnoreCase))278 {279 foreach (PSDriveInfo dInfo in Context.TopLevelSessionState.GetDrivesForProvider(pInfo.FullName))280 {281 if (dInfo == SessionState.Drive.Current)282 {283 return true;284 }285 }286 }287 }288 }289 }290 291 return false;292 }293 294 private static void GetAllNestedModules(PSModuleInfo module, ref List<PSModuleInfo> nestedModulesWithNoCircularReference)295 {296 List<PSModuleInfo> nestedModules = new List<PSModuleInfo>();297 if (module.NestedModules != null && module.NestedModules.Count > 0)298 {299 foreach (var nestedModule in module.NestedModules)300 {301 if (!nestedModulesWithNoCircularReference.Contains(nestedModule))302 {303 nestedModulesWithNoCircularReference.Add(nestedModule);304 nestedModules.Add(nestedModule);305 }306 }307 308 foreach (PSModuleInfo child in nestedModules)309 {310 GetAllNestedModules(child, ref nestedModulesWithNoCircularReference);311 }312 }313 }314 315 /// <summary>316 /// Returns a map from a module to the list of modules that require it.317 /// </summary>318 private Dictionary<PSModuleInfo, List<PSModuleInfo>> GetRequiredDependencies()319 {320 Dictionary<PSModuleInfo, List<PSModuleInfo>> requiredDependencies = new Dictionary<PSModuleInfo, List<PSModuleInfo>>();321 322 foreach (PSModuleInfo module in Context.Modules.GetModules(new string[] { "*" }, false))323 {324 foreach (PSModuleInfo requiredModule in module.RequiredModules)325 {326 List<PSModuleInfo> requiredByList = null;327 328 if (!requiredDependencies.TryGetValue(requiredModule, out requiredByList))329 {330 requiredDependencies.Add(requiredModule, requiredByList = new List<PSModuleInfo>());331 }332 333 requiredByList.Add(module);334 }335 }336 337 return requiredDependencies;338 }339 340 /// <summary>341 /// Reports an error if no modules were removed...342 /// </summary>343 protected override void EndProcessing()344 {345 // Write an error record if specific modules were to be removed.346 // By specific, we mean either a name sting with no wildcards or347 // or a PSModuleInfo object. If the removal request only includes patterns348 // then we won't write the error.349 350 if (_numberRemoved == 0 && !MyInvocation.BoundParameters.ContainsKey("WhatIf"))351 {352 bool hasWildcards = true;353 bool isEngineModule = true;354 foreach (string n in _name)355 {356 if (!InitialSessionState.IsEngineModule(n))357 {358 isEngineModule = false;359 }360 361 if (!WildcardPattern.ContainsWildcardCharacters(n))362 hasWildcards = false;363 }364 365 if (FullyQualifiedName != null && (FullyQualifiedName.Any(static moduleSpec => !InitialSessionState.IsEngineModule(moduleSpec.Name))))366 {367 isEngineModule = false;368 }369 370 if (!isEngineModule && (!hasWildcards || _moduleInfo.Length != 0 || (FullyQualifiedName != null && FullyQualifiedName.Length != 0)))371 {372 string message = StringUtil.Format(Modules.NoModulesRemoved);373 InvalidOperationException invalidOp = new InvalidOperationException(message);374 ErrorRecord er = new ErrorRecord(invalidOp, "Modules_NoModulesRemoved",375 ErrorCategory.ResourceUnavailable, null);376 WriteError(er);377 }378 }379 }380 }381 #endregion Remove-Module382}383 