/// <summary> /// Given a set of directory paths containing PowerShell module folders, analyze the help /// in the module folders and report any issues /// </summary> /// <param name="scopes"></param> public void Analyze(IEnumerable <string> scopes, IEnumerable <string> modulesToAnalyze) { var savedDirectory = Directory.GetCurrentDirectory(); var processedHelpFiles = new List <string>(); var helpLogger = Logger.CreateLogger <HelpIssue>("HelpIssues.csv"); foreach (var baseDirectory in scopes.Where(s => Directory.Exists(Path.GetFullPath(s)))) { SharedAssemblyLoader.Load(baseDirectory); foreach (var directory in Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory))) { if (modulesToAnalyze != null && modulesToAnalyze.Any() && !modulesToAnalyze.Any(m => directory.EndsWith(m))) { continue; } var dirs = Directory.EnumerateDirectories(directory); if (dirs != null && dirs.Any(d => string.Equals(Path.GetFileName(d), "help", StringComparison.OrdinalIgnoreCase))) { Console.WriteLine($"Analyzing module under {directory} ..."); AnalyzeMarkdownHelp(scopes, directory, helpLogger, processedHelpFiles, savedDirectory); } } } }
public void Analyze(IEnumerable <string> directories, IEnumerable <String> modulesToAnalyze) { if (directories == null) { throw new ArgumentNullException("directories"); } _versionConflictLogger = Logger.CreateLogger <AssemblyVersionConflict>("AssemblyVersionConflict.csv"); _sharedConflictLogger = Logger.CreateLogger <SharedAssemblyConflict>("SharedAssemblyConflict.csv"); _missingAssemblyLogger = Logger.CreateLogger <MissingAssembly>("MissingAssemblies.csv"); _extraAssemblyLogger = Logger.CreateLogger <ExtraAssembly>("ExtraAssemblies.csv"); _dependencyMapLogger = Logger.CreateLogger <DependencyMap>("DependencyMap.csv"); foreach (var baseDirectory in directories) { SharedAssemblyLoader.Load(baseDirectory); foreach (var directoryPath in Directory.EnumerateDirectories(baseDirectory)) { if (modulesToAnalyze != null && modulesToAnalyze.Any() && !modulesToAnalyze.Any(m => directoryPath.EndsWith(m))) { continue; } if (!Directory.Exists(directoryPath)) { throw new InvalidOperationException("Please pass a valid directory name as the first parameter"); } Logger.WriteMessage("Processing Directory {0}", directoryPath); _assemblies.Clear(); _loader = new AssemblyMetadataLoader(); _versionConflictLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; }, "Directory"); _missingAssemblyLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; }, "Directory"); _extraAssemblyLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; }, "Directory"); _dependencyMapLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; }, "Directory"); _isNetcore = directoryPath.Contains("Az."); ProcessDirectory(directoryPath); _versionConflictLogger.Decorator.Remove("Directory"); _missingAssemblyLogger.Decorator.Remove("Directory"); _extraAssemblyLogger.Decorator.Remove("Directory"); _dependencyMapLogger.Decorator.Remove("Directory"); } } }
public static void Main(string[] args) { var executingAssemblyPath = Assembly.GetExecutingAssembly().Location; var versionControllerDirectory = Directory.GetParent(executingAssemblyPath).FullName; var artifactsDirectory = Directory.GetParent(versionControllerDirectory).FullName; _rootDirectory = Directory.GetParent(artifactsDirectory).FullName; _projectDirectories = new List <string> { Path.Combine(_rootDirectory, @"src\") }.Where((d) => Directory.Exists(d)).ToList(); _outputDirectories = new List <string> { Path.Combine(_rootDirectory, @"artifacts\Release\") }.Where((d) => Directory.Exists(d)).ToList(); SharedAssemblyLoader.Load(_outputDirectories.FirstOrDefault()); var exceptionsDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Exceptions"); if (args != null && args.Length > 0) { exceptionsDirectory = args[0]; } if (!Directory.Exists(exceptionsDirectory)) { throw new ArgumentException("Please provide a path to the Exceptions folder in the output directory (artifacts/Exceptions)."); } _moduleNameFilter = string.Empty; if (args != null && args.Length > 1) { _moduleNameFilter = args[1] + ".psd1"; } ConsolidateExceptionFiles(exceptionsDirectory); ValidateManifest(); BumpVersions(); ValidateVersionBump(); }
/// <summary> /// Given a set of directory paths containing PowerShell module folders, /// analyze the breaking changes in the modules and report any issues /// /// Filters can be added to find breaking changes for specific modules /// </summary> /// <param name="cmdletProbingDirs">Set of directory paths containing PowerShell module folders to be checked for breaking changes.</param> /// <param name="directoryFilter">Function that filters the directory paths to be checked.</param> /// <param name="cmdletFilter">Function that filters the cmdlets to be checked.</param> public void Analyze( IEnumerable <string> cmdletProbingDirs, Func <IEnumerable <string>, IEnumerable <string> > directoryFilter, Func <string, bool> cmdletFilter, IEnumerable <string> modulesToAnalyze) { var processedHelpFiles = new List <string>(); var issueLogger = Logger.CreateLogger <BreakingChangeIssue>("BreakingChangeIssues.csv"); if (directoryFilter != null) { cmdletProbingDirs = directoryFilter(cmdletProbingDirs); } foreach (var baseDirectory in cmdletProbingDirs.Where(s => !s.Contains("ServiceManagement") && !ModuleFilter.IsAzureStackModule(s) && Directory.Exists(Path.GetFullPath(s)))) { SharedAssemblyLoader.Load(baseDirectory); var probingDirectories = new List <string> { baseDirectory }; // Add current directory for probing probingDirectories.AddRange(Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory))); foreach (var directory in probingDirectories) { if (modulesToAnalyze != null && modulesToAnalyze.Any() && !modulesToAnalyze.Any(m => directory.EndsWith(m))) { continue; } var service = Path.GetFileName(directory); var manifestFiles = Directory.EnumerateFiles(directory, "*.psd1").ToList(); if (manifestFiles.Count > 1) { manifestFiles = manifestFiles.Where(f => Path.GetFileName(f).IndexOf(service) >= 0).ToList(); } if (manifestFiles.Count == 0) { continue; } var psd1 = manifestFiles.FirstOrDefault(); var parentDirectory = Directory.GetParent(psd1).FullName; var psd1FileName = Path.GetFileName(psd1); string moduleName = psd1FileName.Replace(".psd1", ""); Console.WriteLine(directory); Directory.SetCurrentDirectory(directory); issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = moduleName, "AssemblyFileName"); processedHelpFiles.Add(moduleName); var newModuleMetadata = MetadataLoader.GetModuleMetadata(moduleName); var fileName = $"{moduleName}.json"; var executingPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).AbsolutePath); var filePath = Path.Combine(executingPath, "SerializedCmdlets", fileName); if (!File.Exists(filePath)) { continue; } var oldModuleMetadata = ModuleMetadata.DeserializeCmdlets(filePath); if (cmdletFilter != null) { var output = string.Format("Before filter\nOld module cmdlet count: {0}\nNew module cmdlet count: {1}", oldModuleMetadata.Cmdlets.Count, newModuleMetadata.Cmdlets.Count); output += string.Format("\nCmdlet file: {0}", moduleName); oldModuleMetadata.FilterCmdlets(cmdletFilter); newModuleMetadata.FilterCmdlets(cmdletFilter); output += string.Format("After filter\nOld module cmdlet count: {0}\nNew module cmdlet count: {1}", oldModuleMetadata.Cmdlets.Count, newModuleMetadata.Cmdlets.Count); foreach (var cmdlet in oldModuleMetadata.Cmdlets) { output += string.Format("\n\tOld cmdlet - {0}", cmdlet.Name); } foreach (var cmdlet in newModuleMetadata.Cmdlets) { output += string.Format("\n\tNew cmdlet - {0}", cmdlet.Name); } issueLogger.WriteMessage(output + Environment.NewLine); } RunBreakingChangeChecks(oldModuleMetadata, newModuleMetadata, issueLogger); } } }
public void Analyze(IEnumerable <string> cmdletProbingDirs, Func <IEnumerable <string>, IEnumerable <string> > directoryFilter, Func <string, bool> cmdletFilter, IEnumerable <string> modulesToAnalyze) { var savedDirectory = Directory.GetCurrentDirectory(); var processedHelpFiles = new List <string>(); var issueLogger = Logger.CreateLogger <SignatureIssue>(_signatureIssueReportLoggerName); var probingDirectories = new List <string>(); if (directoryFilter != null) { cmdletProbingDirs = directoryFilter(cmdletProbingDirs); } foreach (var baseDirectory in cmdletProbingDirs.Where(s => !s.Contains("ServiceManagement") && !ModuleFilter.IsAzureStackModule(s) && Directory.Exists(Path.GetFullPath(s)))) { SharedAssemblyLoader.Load(baseDirectory); //Add current directory for probing probingDirectories.Add(baseDirectory); probingDirectories.AddRange(Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory))); foreach (var directory in probingDirectories) { if (modulesToAnalyze != null && modulesToAnalyze.Any() && !modulesToAnalyze.Any(m => directory.EndsWith(m))) { continue; } var service = Path.GetFileName(directory); var manifestFiles = Directory.EnumerateFiles(directory, "*.psd1").ToList(); if (manifestFiles.Count > 1) { manifestFiles = manifestFiles.Where(f => Path.GetFileName(f).IndexOf(service) >= 0).ToList(); } if (!manifestFiles.Any()) { continue; } var psd1 = manifestFiles.FirstOrDefault(); var parentDirectory = Directory.GetParent(psd1).FullName; var psd1FileName = Path.GetFileName(psd1); string moduleName = psd1FileName.Replace(".psd1", ""); Directory.SetCurrentDirectory(directory); issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = moduleName, "AssemblyFileName"); processedHelpFiles.Add(moduleName); var module = MetadataLoader.GetModuleMetadata(moduleName); CmdletLoader.ModuleMetadata = module; var cmdlets = module.Cmdlets; if (cmdletFilter != null) { cmdlets = cmdlets.Where(cmdlet => cmdletFilter(cmdlet.Name)).ToList(); } foreach (var cmdlet in cmdlets) { Logger.WriteMessage("Processing cmdlet '{0}'", cmdlet.ClassName); const string defaultRemediation = "Determine if the cmdlet should implement ShouldProcess and " + "if so determine if it should implement Force / ShouldContinue"; if (!cmdlet.SupportsShouldProcess && cmdlet.HasForceSwitch) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 0, problemId: SignatureProblemId.ForceWithoutShouldProcessAttribute, description: string.Format("{0} Has -Force parameter but does not set the SupportsShouldProcess " + "property to true in the Cmdlet attribute.", cmdlet.Name), remediation: defaultRemediation); } if (!cmdlet.SupportsShouldProcess && cmdlet.ConfirmImpact != ConfirmImpact.Medium) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 2, problemId: SignatureProblemId.ConfirmLeveleWithNoShouldProcess, description: string.Format("{0} Changes the ConfirmImpact but does not set the " + "SupportsShouldProcess property to true in the cmdlet attribute.", cmdlet.Name), remediation: defaultRemediation); } if (!cmdlet.SupportsShouldProcess && cmdlet.IsShouldProcessVerb) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.ActionIndicatesShouldProcess, description: string.Format( "{0} Does not support ShouldProcess but the cmdlet verb {1} indicates that it should.", cmdlet.Name, cmdlet.VerbName), remediation: defaultRemediation); } if (cmdlet.ConfirmImpact != ConfirmImpact.Medium) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 2, problemId: SignatureProblemId.ConfirmLevelChange, description: string.Format("{0} changes the confirm impact. Please ensure that the " + "change in ConfirmImpact is justified", cmdlet.Name), remediation: "Verify that ConfirmImpact is changed appropriately by the cmdlet. " + "It is very rare for a cmdlet to change the ConfirmImpact."); } if (!cmdlet.IsApprovedVerb) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.CmdletWithUnapprovedVerb, description: string.Format( "{0} uses the verb '{1}', which is not on the list of approved " + "verbs for PowerShell commands. Use the cmdlet 'Get-Verb' to see " + "the full list of approved verbs and consider renaming the cmdlet.", cmdlet.Name, cmdlet.VerbName), remediation: "Consider renaming the cmdlet to use an approved verb for PowerShell."); } if (!cmdlet.HasSingularNoun) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.CmdletWithPluralNoun, description: string.Format( "{0} uses the noun '{1}', which does not follow the enforced " + "naming convention of using a singular noun for a cmdlet name.", cmdlet.Name, cmdlet.NounName), remediation: "Consider using a singular noun for the cmdlet name."); } if (!cmdlet.OutputTypes.Any()) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.CmdletWithNoOutputType, description: string.Format( "Cmdlet '{0}' has no defined output type.", cmdlet.Name), remediation: "Add an OutputType attribute that declares the type of the object(s) returned " + "by this cmdlet. If this cmdlet returns no output, please set the output " + "type to 'bool' and make sure to implement the 'PassThru' parameter."); } foreach (var parameter in cmdlet.GetParametersWithPluralNoun()) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.ParameterWithPluralNoun, description: string.Format( "Parameter {0} of cmdlet {1} does not follow the enforced " + "naming convention of using a singular noun for a parameter name.", parameter.Name, cmdlet.Name), remediation: "Consider using a singular noun for the parameter name."); } foreach (var parameterSet in cmdlet.ParameterSets) { if (parameterSet.Name.Contains(" ")) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.ParameterSetWithSpace, description: string.Format( "Parameter set '{0}' of cmdlet '{1}' contains a space, which " + "is discouraged for PowerShell parameter sets.", parameterSet.Name, cmdlet.Name), remediation: "Remove the space(s) in the parameter set name."); } if (parameterSet.Parameters.Any(p => p.Position >= 4)) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.ParameterWithOutOfRangePosition, description: string.Format( "Parameter set '{0}' of cmdlet '{1}' contains at least one parameter " + "with a position larger than four, which is discouraged.", parameterSet.Name, cmdlet.Name), remediation: "Limit the number of positional parameters in a single parameter set to " + "four or fewer."); } } if (cmdlet.ParameterSets.Count > 2 && cmdlet.DefaultParameterSetName == "__AllParameterSets") { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.MultipleParameterSetsWithNoDefault, description: string.Format( "Cmdlet '{0}' has multiple parameter sets, but no defined default parameter set.", cmdlet.Name), remediation: "Define a default parameter set in the cmdlet attribute."); } ValidateParameterSetWithMandatoryEqual(cmdlet, issueLogger); ValidateParameterSetWithLenientMandatoryEqual(cmdlet, issueLogger); } issueLogger.Decorator.Remove("AssemblyFileName"); Directory.SetCurrentDirectory(savedDirectory); } } }
/// <summary> /// Given a set of directory paths containing PowerShell module folders, /// analyze the breaking changes in the modules and report any issues /// /// Filters can be added to find breaking changes for specific modules /// </summary> /// <param name="cmdletProbingDirs">Set of directory paths containing PowerShell module folders to be checked for breaking changes.</param> /// <param name="directoryFilter">Function that filters the directory paths to be checked.</param> /// <param name="cmdletFilter">Function that filters the cmdlets to be checked.</param> public void Analyze( IEnumerable <string> cmdletProbingDirs, Func <IEnumerable <string>, IEnumerable <string> > directoryFilter, Func <string, bool> cmdletFilter, IEnumerable <string> modulesToAnalyze) { var processedHelpFiles = new List <string>(); var issueLogger = Logger.CreateLogger <BreakingChangeIssue>("BreakingChangeIssues.csv"); if (directoryFilter != null) { cmdletProbingDirs = directoryFilter(cmdletProbingDirs); } foreach (var baseDirectory in cmdletProbingDirs.Where(s => !s.Contains("ServiceManagement") && !ModuleFilter.IsAzureStackModule(s) && Directory.Exists(Path.GetFullPath(s)))) { SharedAssemblyLoader.Load(baseDirectory); var probingDirectories = new List <string> { baseDirectory }; // Add current directory for probing probingDirectories.AddRange(Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory))); foreach (var directory in probingDirectories) { if (modulesToAnalyze != null && modulesToAnalyze.Any() && !modulesToAnalyze.Any(m => directory.EndsWith(m))) { continue; } var service = Path.GetFileName(directory); var manifestFiles = Directory.EnumerateFiles(directory, "*.psd1").ToList(); if (manifestFiles.Count > 1) { manifestFiles = manifestFiles.Where(f => Path.GetFileName(f).IndexOf(service) >= 0).ToList(); } if (manifestFiles.Count == 0) { continue; } var psd1 = manifestFiles.FirstOrDefault(); var parentDirectory = Directory.GetParent(psd1).FullName; var psd1FileName = Path.GetFileName(psd1); var powershell = PowerShell.Create(); var script = $"Import-LocalizedData -BaseDirectory {parentDirectory} -FileName {psd1FileName} -BindingVariable ModuleMetadata;"; powershell.AddScript($"{script} $ModuleMetadata.NestedModules;"); var cmdletResult = powershell.Invoke(); var nestedModules = cmdletResult.Where(c => c != null).Select(c => c.ToString()).Select(c => (c.StartsWith(".") ? c.Substring(2) : c)).ToList(); powershell.AddScript($"{script} $ModuleMetadata.RequiredModules | % {{ $_[\"ModuleName\"] }};"); cmdletResult = powershell.Invoke(); var requiredModules = cmdletResult.Where(c => !c.ToString().StartsWith(".")).Select(c => c.ToString()).ToList(); if (!nestedModules.Any()) { continue; } Directory.SetCurrentDirectory(directory); requiredModules = requiredModules.Join(cmdletProbingDirs, module => 1, dir => 1, (module, dir) => Path.Combine(dir, module)) .Where(Directory.Exists) .ToList(); requiredModules.Add(directory); foreach (var nestedModule in nestedModules) { var assemblyFile = Directory.GetFiles(parentDirectory, nestedModule, SearchOption.AllDirectories).FirstOrDefault(); var assemblyFileName = Path.GetFileName(assemblyFile); if (!File.Exists(assemblyFile)) { continue; } issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = assemblyFileName, "AssemblyFileName"); processedHelpFiles.Add(assemblyFileName); // TODO: Remove IfDef #if NETSTANDARD var proxy = new CmdletLoader(); #else var proxy = EnvironmentHelpers.CreateProxy <CmdletLoader>(directory, out _appDomain); #endif var newModuleMetadata = proxy.GetModuleMetadata(assemblyFile, requiredModules); var fileName = assemblyFileName + ".json"; var executingPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).AbsolutePath); var filePath = Path.Combine(executingPath, "SerializedCmdlets", fileName); #if SERIALIZE SerializeCmdlets(filePath, newModuleMetadata); #endif if (!File.Exists(filePath)) { continue; } var oldModuleMetadata = DeserializeCmdlets(filePath); if (cmdletFilter != null) { var output = string.Format("Before filter\nOld module cmdlet count: {0}\nNew module cmdlet count: {1}", oldModuleMetadata.Cmdlets.Count, newModuleMetadata.Cmdlets.Count); output += string.Format("\nCmdlet file: {0}", assemblyFileName); oldModuleMetadata.FilterCmdlets(cmdletFilter); newModuleMetadata.FilterCmdlets(cmdletFilter); output += string.Format("After filter\nOld module cmdlet count: {0}\nNew module cmdlet count: {1}", oldModuleMetadata.Cmdlets.Count, newModuleMetadata.Cmdlets.Count); foreach (var cmdlet in oldModuleMetadata.Cmdlets) { output += string.Format("\n\tOld cmdlet - {0}", cmdlet.Name); } foreach (var cmdlet in newModuleMetadata.Cmdlets) { output += string.Format("\n\tNew cmdlet - {0}", cmdlet.Name); } issueLogger.WriteMessage(output + Environment.NewLine); } RunBreakingChangeChecks(oldModuleMetadata, newModuleMetadata, issueLogger); // TODO: Remove IfDef code #if !NETSTANDARD AppDomain.Unload(_appDomain); #endif } } } }
public void Analyze(IEnumerable <string> cmdletProbingDirs, Func <IEnumerable <string>, IEnumerable <string> > directoryFilter, Func <string, bool> cmdletFilter, IEnumerable <string> modulesToAnalyze) { var savedDirectory = Directory.GetCurrentDirectory(); var processedHelpFiles = new List <string>(); var issueLogger = Logger.CreateLogger <SignatureIssue>(_signatureIssueReportLoggerName); var probingDirectories = new List <string>(); if (directoryFilter != null) { cmdletProbingDirs = directoryFilter(cmdletProbingDirs); } foreach (var baseDirectory in cmdletProbingDirs.Where(s => !s.Contains("ServiceManagement") && !ModuleFilter.IsAzureStackModule(s) && Directory.Exists(Path.GetFullPath(s)))) { SharedAssemblyLoader.Load(baseDirectory); //Add current directory for probing probingDirectories.Add(baseDirectory); probingDirectories.AddRange(Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory))); foreach (var directory in probingDirectories) { if (modulesToAnalyze != null && modulesToAnalyze.Any() && !modulesToAnalyze.Any(m => directory.EndsWith(m))) { continue; } var service = Path.GetFileName(directory); var manifestFiles = Directory.EnumerateFiles(directory, "*.psd1").ToList(); if (manifestFiles.Count > 1) { manifestFiles = manifestFiles.Where(f => Path.GetFileName(f).IndexOf(service) >= 0).ToList(); } if (!manifestFiles.Any()) { continue; } var psd1 = manifestFiles.FirstOrDefault(); var parentDirectory = Directory.GetParent(psd1).FullName; var psd1FileName = Path.GetFileName(psd1); var powershell = PowerShell.Create(); var script = $"Import-LocalizedData -BaseDirectory {parentDirectory} -FileName {psd1FileName} -BindingVariable ModuleMetadata;"; powershell.AddScript($"{script} $ModuleMetadata.NestedModules;"); var cmdletResult = powershell.Invoke(); var nestedModules = cmdletResult.Where(c => c != null).Select(c => c.ToString()).Select(c => (c.StartsWith(".") ? c.Substring(2) : c)).ToList(); powershell.AddScript($"{script} $ModuleMetadata.RequiredModules | % {{ $_[\"ModuleName\"] }};"); cmdletResult = powershell.Invoke(); var requiredModules = cmdletResult.Where(c => !c.ToString().StartsWith(".")).Select(c => c.ToString()).ToList(); if (!nestedModules.Any()) { continue; } Directory.SetCurrentDirectory(directory); requiredModules = requiredModules.Join(cmdletProbingDirs, module => 1, dir => 1, (module, dir) => Path.Combine(dir, module)) .Where(Directory.Exists) .ToList(); requiredModules.Add(directory); foreach (var nestedModule in nestedModules) { var assemblyFile = Directory.GetFiles(parentDirectory, nestedModule, SearchOption.AllDirectories).FirstOrDefault(); if (!File.Exists(assemblyFile)) { continue; } issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = assemblyFile, "AssemblyFileName"); processedHelpFiles.Add(assemblyFile); // TODO: Remove IfDef #if NETSTANDARD var proxy = new CmdletLoader(); #else var proxy = EnvironmentHelpers.CreateProxy <CmdletLoader>(directory, out _appDomain); #endif var module = proxy.GetModuleMetadata(assemblyFile, requiredModules); var cmdlets = module.Cmdlets; if (cmdletFilter != null) { cmdlets = cmdlets.Where(cmdlet => cmdletFilter(cmdlet.Name)).ToList(); } foreach (var cmdlet in cmdlets) { Logger.WriteMessage("Processing cmdlet '{0}'", cmdlet.ClassName); const string defaultRemediation = "Determine if the cmdlet should implement ShouldProcess and " + "if so determine if it should implement Force / ShouldContinue"; if (!cmdlet.SupportsShouldProcess && cmdlet.HasForceSwitch) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 0, problemId: SignatureProblemId.ForceWithoutShouldProcessAttribute, description: string.Format("{0} Has -Force parameter but does not set the SupportsShouldProcess " + "property to true in the Cmdlet attribute.", cmdlet.Name), remediation: defaultRemediation); } if (!cmdlet.SupportsShouldProcess && cmdlet.ConfirmImpact != ConfirmImpact.Medium) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 2, problemId: SignatureProblemId.ConfirmLeveleWithNoShouldProcess, description: string.Format("{0} Changes the ConfirmImpact but does not set the " + "SupportsShouldProcess property to true in the cmdlet attribute.", cmdlet.Name), remediation: defaultRemediation); } if (!cmdlet.SupportsShouldProcess && cmdlet.IsShouldProcessVerb) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.ActionIndicatesShouldProcess, description: string.Format( "{0} Does not support ShouldProcess but the cmdlet verb {1} indicates that it should.", cmdlet.Name, cmdlet.VerbName), remediation: defaultRemediation); } if (cmdlet.ConfirmImpact != ConfirmImpact.Medium) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 2, problemId: SignatureProblemId.ConfirmLevelChange, description: string.Format("{0} changes the confirm impact. Please ensure that the " + "change in ConfirmImpact is justified", cmdlet.Name), remediation: "Verify that ConfirmImpact is changed appropriately by the cmdlet. " + "It is very rare for a cmdlet to change the ConfirmImpact."); } if (!cmdlet.IsApprovedVerb) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.CmdletWithUnapprovedVerb, description: string.Format( "{0} uses the verb '{1}', which is not on the list of approved " + "verbs for PowerShell commands. Use the cmdlet 'Get-Verb' to see " + "the full list of approved verbs and consider renaming the cmdlet.", cmdlet.Name, cmdlet.VerbName), remediation: "Consider renaming the cmdlet to use an approved verb for PowerShell."); } if (!cmdlet.HasSingularNoun) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.CmdletWithPluralNoun, description: string.Format( "{0} uses the noun '{1}', which does not follow the enforced " + "naming convention of using a singular noun for a cmdlet name.", cmdlet.Name, cmdlet.NounName), remediation: "Consider using a singular noun for the cmdlet name."); } if (!cmdlet.OutputTypes.Any()) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.CmdletWithNoOutputType, description: string.Format( "Cmdlet '{0}' has no defined output type.", cmdlet.Name), remediation: "Add an OutputType attribute that declares the type of the object(s) returned " + "by this cmdlet. If this cmdlet returns no output, please set the output " + "type to 'bool' and make sure to implement the 'PassThru' parameter."); } foreach (var parameter in cmdlet.GetParametersWithPluralNoun()) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.ParameterWithPluralNoun, description: string.Format( "Parameter {0} of cmdlet {1} does not follow the enforced " + "naming convention of using a singular noun for a parameter name.", parameter.Name, cmdlet.Name), remediation: "Consider using a singular noun for the parameter name."); } foreach (var parameterSet in cmdlet.ParameterSets) { if (parameterSet.Name.Contains(" ")) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.ParameterSetWithSpace, description: string.Format( "Parameter set '{0}' of cmdlet '{1}' contains a space, which " + "is discouraged for PowerShell parameter sets.", parameterSet.Name, cmdlet.Name), remediation: "Remove the space(s) in the parameter set name."); } if (parameterSet.Parameters.Any(p => p.Position >= 4)) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.ParameterWithOutOfRangePosition, description: string.Format( "Parameter set '{0}' of cmdlet '{1}' contains at least one parameter " + "with a position larger than four, which is discouraged.", parameterSet.Name, cmdlet.Name), remediation: "Limit the number of positional parameters in a single parameter set to " + "four or fewer."); } } if (cmdlet.ParameterSets.Count > 2 && cmdlet.DefaultParameterSetName == "__AllParameterSets") { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 1, problemId: SignatureProblemId.MultipleParameterSetsWithNoDefault, description: string.Format( "Cmdlet '{0}' has multiple parameter sets, but no defined default parameter set.", cmdlet.Name), remediation: "Define a default parameter set in the cmdlet attribute."); } //if (cmdlet.DefaultParameterSet.Parameters.Count == 0) //{ // issueLogger.LogSignatureIssue( // cmdlet: cmdlet, // severity: 1, // problemId: SignatureProblemId.EmptyDefaultParameterSet, // description: // string.Format( // "Default parameter set '{0}' of cmdlet '{1}' is empty.", // cmdlet.DefaultParameterSetName, cmdlet.Name), // remediation: "Set a non empty parameter set as the default parameter set."); //} ValidateParameterSetWithMandatoryEqual(cmdlet, issueLogger); ValidateParameterSetWithLenientMandatoryEqual(cmdlet, issueLogger); } // TODO: Remove IfDef code #if !NETSTANDARD AppDomain.Unload(_appDomain); #endif issueLogger.Decorator.Remove("AssemblyFileName"); } Directory.SetCurrentDirectory(savedDirectory); } } }