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.Collections.ObjectModel;8using System.IO;9using System.Linq;10using System.Management.Automation;11using System.Management.Automation.Internal;12 13//14// Now define the set of commands for manipulating modules.15//16 17namespace Microsoft.PowerShell.Commands18{19 #region Test-ModuleManifest20 /// <summary>21 /// This cmdlet takes a module manifest and validates the contents...22 /// </summary>23 [Cmdlet(VerbsDiagnostic.Test, "ModuleManifest", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096900")]24 [OutputType(typeof(PSModuleInfo))]25 public sealed class TestModuleManifestCommand : ModuleCmdletBase26 {27 /// <summary>28 /// Creates an instance of the Test-ModuleManifest command.29 /// </summary>30 public TestModuleManifestCommand()31 {32 // Test-ModuleManifest reads a manifest with ModuleCmdletBase.LoadModuleManifest().33 // This will error on an edition-incompatible manifest loaded from the System32 path,34 // unless BaseSkipEditionCheck is true. Since Test-ModuleManifest shouldn't care about35 // module edition (it just tests manifest validity), we always want to set this rather36 // than provide it as a switch on the cmdlet.37 BaseSkipEditionCheck = true;38 }39 40 /// <summary>41 /// The output path for the generated file...42 /// </summary>43 [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0, ValueFromPipelineByPropertyName = true)]44 public string Path45 {46 get { return _path; }47 48 set { _path = value; }49 }50 51 private string _path;52 53 /// <summary>54 /// Implements the record processing for this cmdlet.55 /// </summary>56 protected override void ProcessRecord()57 {58 ProviderInfo provider = null;59 Collection<string> filePaths;60 61 try62 {63 if (Context.EngineSessionState.IsProviderLoaded(Context.ProviderNames.FileSystem))64 {65 filePaths =66 SessionState.Path.GetResolvedProviderPathFromPSPath(_path, out provider);67 }68 else69 {70 filePaths = new Collection<string>();71 filePaths.Add(_path);72 }73 }74 catch (ItemNotFoundException)75 {76 string message = StringUtil.Format(Modules.ModuleNotFound, _path);77 FileNotFoundException fnf = new FileNotFoundException(message);78 ErrorRecord er = new ErrorRecord(fnf, "Modules_ModuleNotFound",79 ErrorCategory.ResourceUnavailable, _path);80 WriteError(er);81 return;82 }83 84 // Make sure that the path is in the file system - that's all we can handle currently...85 if (!provider.NameEquals(this.Context.ProviderNames.FileSystem))86 {87 // "The current provider ({0}) cannot open a file"88 throw InterpreterError.NewInterpreterException(_path, typeof(RuntimeException),89 null, "FileOpenError", ParserStrings.FileOpenError, provider.FullName);90 }91 92 // Make sure at least one file was found...93 if (filePaths == null || filePaths.Count < 1)94 {95 string message = StringUtil.Format(Modules.ModuleNotFound, _path);96 FileNotFoundException fnf = new FileNotFoundException(message);97 ErrorRecord er = new ErrorRecord(fnf, "Modules_ModuleNotFound",98 ErrorCategory.ResourceUnavailable, _path);99 WriteError(er);100 return;101 }102 103 if (filePaths.Count > 1)104 {105 // "The path resolved to more than one file; can only process one file at a time."106 throw InterpreterError.NewInterpreterException(filePaths, typeof(RuntimeException),107 null, "AmbiguousPath", ParserStrings.AmbiguousPath);108 }109 110 string filePath = filePaths[0];111 ExternalScriptInfo scriptInfo = null;112 string ext = System.IO.Path.GetExtension(filePath);113 if (ext.Equals(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase))114 {115 // Create a script info for loading the file...116 string scriptName;117 scriptInfo = GetScriptInfoForFile(filePath, out scriptName, false);118 119 // we should reserve the Context.ModuleBeingProcessed unchanged after loadModuleManifest(), otherwise the module won't be importable next time.120 PSModuleInfo module;121 string _origModuleBeingProcessed = Context.ModuleBeingProcessed;122 try123 {124 module = LoadModuleManifest(125 scriptInfo,126 ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.WriteWarnings /* but don't stop on first error and don't load elements */,127 null,128 null,129 null,130 null);131 132 if (module != null)133 {134 // Validate file existence135 if (module.RequiredAssemblies != null)136 {137 foreach (string requiredAssembliespath in module.RequiredAssemblies)138 {139 if (!IsValidFilePath(requiredAssembliespath, module, true) && !IsValidGacAssembly(requiredAssembliespath))140 {141 string errorMsg = StringUtil.Format(Modules.InvalidRequiredAssembliesInModuleManifest, requiredAssembliespath, filePath);142 var errorRecord = new ErrorRecord(new DirectoryNotFoundException(errorMsg), "Modules_InvalidRequiredAssembliesInModuleManifest",143 ErrorCategory.ObjectNotFound, _path);144 WriteError(errorRecord);145 }146 }147 }148 149 if (!HasValidRootModule(module))150 {151 string errorMsg = StringUtil.Format(Modules.InvalidModuleManifest, module.RootModule, filePath);152 var errorRecord = new ErrorRecord(new ArgumentException(errorMsg), "Modules_InvalidRootModuleInModuleManifest",153 ErrorCategory.InvalidArgument, _path);154 WriteError(errorRecord);155 }156 157 Hashtable data = null;158 Hashtable localizedData = null;159 bool containerErrors = false;160 LoadModuleManifestData(scriptInfo, ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.WriteWarnings, out data, out localizedData, ref containerErrors);161 ModuleSpecification[] nestedModules;162 GetScalarFromData(data, scriptInfo.Path, "NestedModules", ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.WriteWarnings, out nestedModules);163 if (nestedModules != null)164 {165 foreach (ModuleSpecification nestedModule in nestedModules)166 {167 if (!IsValidFilePath(nestedModule.Name, module, true)168 && !IsValidFilePath(nestedModule.Name + StringLiterals.PowerShellILAssemblyExtension, module, true)169 && !IsValidFilePath(nestedModule.Name + StringLiterals.PowerShellNgenAssemblyExtension, module, true)170 && !IsValidFilePath(nestedModule.Name + StringLiterals.PowerShellILExecutableExtension, module, true)171 && !IsValidFilePath(nestedModule.Name + StringLiterals.PowerShellModuleFileExtension, module, true)172 && !IsValidGacAssembly(nestedModule.Name))173 {174 Collection<PSModuleInfo> modules = GetModuleIfAvailable(nestedModule);175 if (modules.Count == 0)176 {177 string errorMsg = StringUtil.Format(Modules.InvalidNestedModuleinModuleManifest, nestedModule.Name, filePath);178 var errorRecord = new ErrorRecord(new DirectoryNotFoundException(errorMsg), "Modules_InvalidNestedModuleinModuleManifest",179 ErrorCategory.ObjectNotFound, _path);180 WriteError(errorRecord);181 }182 }183 }184 }185 186 ModuleSpecification[] requiredModules;187 GetScalarFromData(data, scriptInfo.Path, "RequiredModules", ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.WriteWarnings, out requiredModules);188 if (requiredModules != null)189 {190 foreach (ModuleSpecification requiredModule in requiredModules)191 {192 var modules = GetModule(new[] { requiredModule.Name }, all: false, refresh: true);193 if (modules.Count == 0)194 {195 string errorMsg = StringUtil.Format(Modules.InvalidRequiredModulesinModuleManifest, requiredModule.Name, filePath);196 var errorRecord = new ErrorRecord(new DirectoryNotFoundException(errorMsg), "Modules_InvalidRequiredModulesinModuleManifest",197 ErrorCategory.ObjectNotFound, _path);198 WriteError(errorRecord);199 }200 }201 }202 203 string[] fileListPaths;204 GetScalarFromData(data, scriptInfo.Path, "FileList", ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.WriteWarnings, out fileListPaths);205 if (fileListPaths != null)206 {207 foreach (string fileListPath in fileListPaths)208 {209 if (!IsValidFilePath(fileListPath, module, true))210 {211 string errorMsg = StringUtil.Format(Modules.InvalidFilePathinModuleManifest, fileListPath, filePath);212 var errorRecord = new ErrorRecord(new DirectoryNotFoundException(errorMsg), "Modules_InvalidFilePathinModuleManifest",213 ErrorCategory.ObjectNotFound, _path);214 WriteError(errorRecord);215 }216 }217 }218 219 ModuleSpecification[] moduleListModules;220 GetScalarFromData(data, scriptInfo.Path, "ModuleList", ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.WriteWarnings, out moduleListModules);221 if (moduleListModules != null)222 {223 foreach (ModuleSpecification moduleListModule in moduleListModules)224 {225 var modules = GetModule(new[] { moduleListModule.Name }, all: false, refresh: true);226 if (modules.Count == 0)227 {228 string errorMsg = StringUtil.Format(Modules.InvalidModuleListinModuleManifest, moduleListModule.Name, filePath);229 var errorRecord = new ErrorRecord(new DirectoryNotFoundException(errorMsg), "Modules_InvalidModuleListinModuleManifest",230 ErrorCategory.ObjectNotFound, _path);231 WriteError(errorRecord);232 }233 }234 }235 236 if (module.CompatiblePSEditions.Any())237 {238 // The CompatiblePSEditions module manifest key is supported only on PowerShell version '5.1' or higher.239 // Ensure that PowerShellVersion module manifest key value is '5.1' or higher.240 //241 var minimumRequiredPowerShellVersion = new Version(5, 1);242 if ((module.PowerShellVersion == null) || module.PowerShellVersion < minimumRequiredPowerShellVersion)243 {244 string errorMsg = StringUtil.Format(Modules.InvalidPowerShellVersionInModuleManifest, filePath);245 var errorRecord = new ErrorRecord(new ArgumentException(errorMsg), "Modules_InvalidPowerShellVersionInModuleManifest", ErrorCategory.InvalidArgument, _path);246 WriteError(errorRecord);247 }248 }249 }250 }251 finally252 {253 Context.ModuleBeingProcessed = _origModuleBeingProcessed;254 }255 256 DirectoryInfo parent = null;257 try258 {259 parent = Directory.GetParent(filePath);260 }261 catch (IOException) { }262 catch (UnauthorizedAccessException) { }263 catch (ArgumentException) { }264 265 Version version;266 if (parent != null && Version.TryParse(parent.Name, out version))267 {268 if (!version.Equals(module.Version))269 {270 string message = StringUtil.Format(Modules.InvalidModuleManifestVersion, filePath, module.Version.ToString(), parent.FullName);271 var ioe = new InvalidOperationException(message);272 ErrorRecord er = new ErrorRecord(ioe, "Modules_InvalidModuleManifestVersion",273 ErrorCategory.InvalidArgument, _path);274 ThrowTerminatingError(er);275 }276 277 WriteVerbose(Modules.ModuleVersionEqualsToVersionFolder);278 }279 280 if (module != null)281 {282 WriteObject(module);283 }284 }285 else286 {287 string message = StringUtil.Format(Modules.InvalidModuleManifestPath, filePath);288 InvalidOperationException ioe = new InvalidOperationException(message);289 ErrorRecord er = new ErrorRecord(ioe, "Modules_InvalidModuleManifestPath",290 ErrorCategory.InvalidArgument, _path);291 ThrowTerminatingError(er);292 }293 }294 295 // All module extensions except ".psd1" are valid RootModule extensions296 private static readonly IReadOnlyList<string> s_validRootModuleExtensions = ModuleIntrinsics.PSModuleExtensions297 .Where(static ext => !string.Equals(ext, StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase))298 .ToArray();299 300 /// <summary>301 /// Checks whether the RootModule field of a module is valid or not.302 /// Valid root modules are:303 /// - null304 /// - Empty string305 /// - A valid non-psd1 module file (psm1, cdxml, xaml, dll), as name with extension, name without extension, or path.306 /// </summary>307 /// <param name="module">The module for which we want to check the validity of the root module.</param>308 /// <returns>True if the root module is valid, false otherwise.</returns>309 private bool HasValidRootModule(PSModuleInfo module)310 {311 // Empty/null root modules are allowed312 if (string.IsNullOrEmpty(module.RootModule))313 {314 return true;315 }316 317 // GAC assemblies are allowed318 if (IsValidGacAssembly(module.RootModule))319 {320 return true;321 }322 323 // Check for extensions324 string rootModuleExt = System.IO.Path.GetExtension(module.RootModule);325 if (!string.IsNullOrEmpty(rootModuleExt))326 {327 // Check that the root module's extension is an allowed one328 if (!s_validRootModuleExtensions.Contains(rootModuleExt, StringComparer.OrdinalIgnoreCase))329 {330 return false;331 }332 333 // Check the file path of the full root module334 return IsValidFilePath(module.RootModule, module, verifyPathScope: true);335 }336 337 // We have no extension, so we need to check all of them338 foreach (string extension in s_validRootModuleExtensions)339 {340 if (IsValidFilePath(module.RootModule + extension, module, verifyPathScope: true))341 {342 return true;343 }344 }345 346 return false;347 }348 349 /// <summary>350 /// Check if the given path is valid.351 /// </summary>352 /// <param name="path"></param>353 /// <param name="module"></param>354 /// <param name="verifyPathScope"></param>355 /// <returns></returns>356 private bool IsValidFilePath(string path, PSModuleInfo module, bool verifyPathScope)357 {358 try359 {360 if (!System.IO.Path.IsPathRooted(path))361 {362 // we assume the relative path is under module scope, otherwise we will throw error anyway.363 path = System.IO.Path.GetFullPath(module.ModuleBase + System.IO.Path.DirectorySeparatorChar + path);364 }365 366 // resolve the path so slashes are in the right direction367 CmdletProviderContext cmdContext = new CmdletProviderContext(this);368 Collection<PathInfo> pathInfos = SessionState.Path.GetResolvedPSPathFromPSPath(path, cmdContext);369 if (pathInfos.Count != 1)370 {371 string message = StringUtil.Format(Modules.InvalidModuleManifestPath, path);372 InvalidOperationException ioe = new InvalidOperationException(message);373 ErrorRecord er = new ErrorRecord(ioe, "Modules_InvalidModuleManifestPath", ErrorCategory.InvalidArgument, path);374 ThrowTerminatingError(er);375 }376 377 // `Path` returns the PSProviderPath which is fully qualified to the provider and the filesystem APIs378 // don't understand this. Instead `ProviderPath` returns the path that the FileSystemProvider understands.379 path = pathInfos[0].ProviderPath;380 381 // First, we validate if the path does exist.382 if (!File.Exists(path) && !Directory.Exists(path))383 {384 return false;385 }386 387 // Then, we validate if the path is under module scope388 if (verifyPathScope && !System.IO.Path.GetFullPath(path).StartsWith(System.IO.Path.GetFullPath(module.ModuleBase), StringComparison.OrdinalIgnoreCase))389 {390 return false;391 }392 }393 catch (Exception exception)394 {395 if (exception is ArgumentException || exception is ArgumentNullException || exception is NotSupportedException || exception is PathTooLongException || exception is ItemNotFoundException)396 {397 return false;398 }399 }400 401 return true;402 }403 404 /// <summary>405 /// Check if the given string is a valid gac assembly.406 /// </summary>407 /// <param name="assemblyName"></param>408 /// <returns></returns>409 private static bool IsValidGacAssembly(string assemblyName)410 {411#if UNIX412 return false;413#else414 string gacPath = System.Environment.GetEnvironmentVariable("windir") + "\\Microsoft.NET\\assembly";415 string assemblyFile = assemblyName;416 string ngenAssemblyFile = assemblyName;417 if (!assemblyName.EndsWith(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase))418 {419 assemblyFile = assemblyName + StringLiterals.PowerShellILAssemblyExtension;420 ngenAssemblyFile = assemblyName + StringLiterals.PowerShellNgenAssemblyExtension;421 }422 423 try424 {425 return Directory.EnumerateFiles(gacPath, assemblyFile, SearchOption.AllDirectories).Any()426 || Directory.EnumerateFiles(gacPath, ngenAssemblyFile, SearchOption.AllDirectories).Any();427 }428 catch429 {430 return false;431 }432#endif433 }434 }435 436 #endregion437}438 