private void ProcessDirectory(string directoryPath) { var savedDirectory = Directory.GetCurrentDirectory(); Directory.SetCurrentDirectory(directoryPath); _loader = EnvironmentHelpers.CreateProxy <AssemblyLoader>(directoryPath, out _testDomain); foreach (var file in Directory.GetFiles(directoryPath).Where(file => file.EndsWith(".dll"))) { AssemblyRecord assembly = CreateAssemblyRecord(file); _assemblies[assembly.Name] = assembly; if (RequiresExactVersionMatch(assembly)) { AddSharedAssemblyExactVersion(assembly); } else { AddSharedAssembly(assembly); } } // Now check for assembly mismatches foreach (var assembly in _assemblies.Values) { foreach (var reference in assembly.Children) { CheckAssemblyReference(reference, assembly); } } FindExtraAssemblies(); AppDomain.Unload(_testDomain); Directory.SetCurrentDirectory(savedDirectory); }
/// <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) { 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)))) { foreach (var directory in Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory))) { var commandAssemblies = Directory.EnumerateFiles(directory, "*.Commands.*.dll") .Where(f => IsAssemblyFile(f) && !File.Exists(f + "-Help.xml")); foreach (var orphanedAssembly in commandAssemblies) { helpLogger.LogRecord(new HelpIssue() { Assembly = orphanedAssembly, Description = string.Format("{0} has no matching help file", orphanedAssembly), Severity = 0, Remediation = string.Format("Make sure a dll Help file for {0} exists and it is " + "being copied to the output directory.", orphanedAssembly), Target = orphanedAssembly, HelpFile = orphanedAssembly + "-Help.xml", ProblemId = MissingHelpFile }); } var helpFiles = Directory.EnumerateFiles(directory, "*.dll-Help.xml") .Where(f => !processedHelpFiles.Contains(Path.GetFileName(f), StringComparer.OrdinalIgnoreCase)).ToList(); if (helpFiles.Any()) { Directory.SetCurrentDirectory(directory); foreach (var helpFile in helpFiles) { var cmdletFile = helpFile.Substring(0, helpFile.Length - "-Help.xml".Length); var helpFileName = Path.GetFileName(helpFile); var cmdletFileName = Path.GetFileName(cmdletFile); if (File.Exists(cmdletFile)) { processedHelpFiles.Add(helpFileName); helpLogger.Decorator.AddDecorator((h) => { h.HelpFile = helpFileName; h.Assembly = cmdletFileName; }, "Cmdlet"); var proxy = EnvironmentHelpers.CreateProxy <CmdletLoader>(directory, out _appDomain); var cmdlets = proxy.GetCmdlets(cmdletFile); var helpRecords = CmdletHelpParser.GetHelpTopics(helpFile, helpLogger); ValidateHelpRecords(cmdlets, helpRecords, helpLogger); helpLogger.Decorator.Remove("Cmdlet"); AppDomain.Unload(_appDomain); } } Directory.SetCurrentDirectory(savedDirectory); } } } }
private void ProcessDirectory(string directoryPath) { var savedDirectory = Directory.GetCurrentDirectory(); Directory.SetCurrentDirectory(directoryPath); _loader = #if !NETSTANDARD EnvironmentHelpers.CreateProxy <AssemblyLoader>(directoryPath, out _testDomain); #else new AssemblyLoader(); #endif foreach (var file in Directory.GetFiles(directoryPath).Where(file => file.EndsWith(".dll"))) { AssemblyRecord assembly = CreateAssemblyRecord(file); _assemblies[assembly.Name] = assembly; if (RequiresExactVersionMatch(assembly)) { AddSharedAssemblyExactVersion(assembly); } else { AddSharedAssembly(assembly); } } // Now check for assembly mismatches foreach (var assembly in _assemblies.Values) { foreach (var reference in assembly.Children) { CheckAssemblyReference(reference, assembly); } } foreach (var assembly in _assemblies.Values) { foreach (var parent in assembly.ReferencingAssembly) { _dependencyMapLogger.LogRecord( new DependencyMap { AssemblyName = assembly.Name, AssemblyVersion = assembly.Version.ToString(), ReferencingAssembly = parent.Name, ReferencingAssemblyVersion = parent.Version.ToString(), Severity = 3 }); } } FindExtraAssemblies(); #if !NETSTANDARD AppDomain.Unload(_testDomain); #endif Directory.SetCurrentDirectory(savedDirectory); }
/// <summary> /// Generate the serialized module metadata for a given module. /// </summary> public void SerializeModule() { var outputModuleManifestPath = _fileHelper.OutputModuleManifestPath; var outputModuleDirectory = _fileHelper.OutputModuleDirectory; var outputDirectories = _fileHelper.OutputDirectories; var serializedCmdletsDirectory = _fileHelper.SerializedCmdletsDirectory; var moduleName = _fileHelper.ModuleName; IEnumerable <string> nestedModules = null; using (PowerShell powershell = PowerShell.Create()) { powershell.AddScript("(Test-ModuleManifest -Path " + outputModuleManifestPath + ").NestedModules"); var cmdletResult = powershell.Invoke(); nestedModules = cmdletResult.Select(c => c.ToString() + ".dll"); } if (nestedModules.Any()) { List <string> requiredModules = null; using (PowerShell powershell = PowerShell.Create()) { powershell.AddScript("(Test-ModuleManifest -Path " + outputModuleManifestPath + ").RequiredModules"); var cmdletResult = powershell.Invoke(); requiredModules = cmdletResult.Select(c => c.ToString()) .Join(outputDirectories, module => 1, directory => 1, (module, directory) => Path.Combine(directory, module)) .Where(f => Directory.Exists(f)) .ToList(); } requiredModules.Add(outputModuleDirectory); foreach (var nestedModule in nestedModules) { var assemblyPath = Directory.GetFiles(outputModuleDirectory, nestedModule, SearchOption.AllDirectories).FirstOrDefault(); var proxy = EnvironmentHelpers.CreateProxy <CmdletLoader>(outputModuleManifestPath, out _appDomain); var newModuleMetadata = proxy.GetModuleMetadata(assemblyPath, requiredModules); var serializedCmdletName = nestedModule + ".json"; var serializedCmdletFile = Path.Combine(serializedCmdletsDirectory, serializedCmdletName); SerializeCmdlets(serializedCmdletFile, newModuleMetadata); } } else { Console.WriteLine("No nested module(s) found for " + moduleName + " -- skipping serialization step."); } }
/// <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) { 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)))) { foreach (var directory in Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory))) { var helpFiles = Directory.EnumerateFiles(directory, "*.dll-Help.xml") .Where(f => !processedHelpFiles.Contains(Path.GetFileName(f), StringComparer.OrdinalIgnoreCase)).ToList(); if (helpFiles.Any()) { Directory.SetCurrentDirectory(directory); foreach (var helpFile in helpFiles) { var cmdletFile = helpFile.Substring(0, helpFile.Length - "-Help.xml".Length); var helpFileName = Path.GetFileName(helpFile); var cmdletFileName = Path.GetFileName(cmdletFile); if (File.Exists(cmdletFile)) { processedHelpFiles.Add(helpFileName); helpLogger.Decorator.AddDecorator((h) => { h.HelpFile = helpFileName; h.Assembly = cmdletFileName; }, "Cmdlet"); var proxy = EnvironmentHelpers.CreateProxy <CmdletLoader>(directory, out _appDomain); var cmdlets = proxy.GetCmdlets(cmdletFile); var helpRecords = CmdletHelpParser.GetHelpTopics(helpFile, helpLogger); ValidateHelpRecords(cmdlets, helpRecords, helpLogger); helpLogger.Decorator.Remove("Cmdlet"); AppDomain.Unload(_appDomain); } } Directory.SetCurrentDirectory(savedDirectory); } } } }
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") && !s.Contains("Stack") && Directory.Exists(Path.GetFullPath(s)))) { //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); IEnumerable <string> nestedModules = null; List <string> requiredModules = null; var powershell = PowerShell.Create(RunspaceMode.NewRunspace); var script = $"Import-LocalizedData -BaseDirectory {parentDirectory} -FileName {psd1FileName}" + " -BindingVariable ModuleMetadata; $ModuleMetadata.NestedModules; $ModuleMetadata.RequiredModules | % { $_[\"ModuleName\"] };"; powershell.AddScript(script); var cmdletResult = powershell.Invoke(); nestedModules = cmdletResult.Where(c => c != null && c.ToString().StartsWith(".")).Select(c => c.ToString().Substring(2)); requiredModules = cmdletResult.Where(c => c != null && !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."); } } // TODO: Remove IfDef code #if !NETSTANDARD AppDomain.Unload(_appDomain); #endif issueLogger.Decorator.Remove("AssemblyFileName"); } Directory.SetCurrentDirectory(savedDirectory); } } }
public void Analyze(IEnumerable <string> cmdletProbingDirs, Func <IEnumerable <string>, IEnumerable <string> > directoryFilter, Func <string, bool> cmdletFilter) { var savedDirectory = Directory.GetCurrentDirectory(); var processedHelpFiles = new List <string>(); var issueLogger = Logger.CreateLogger <SignatureIssue>(signatureIssueReportLoggerName); List <string> probingDirectories = new List <string>(); if (directoryFilter != null) { cmdletProbingDirs = directoryFilter(cmdletProbingDirs); } foreach (var baseDirectory in cmdletProbingDirs.Where(s => !s.Contains("ServiceManagement") && Directory.Exists(Path.GetFullPath(s)))) { //Add current directory for probing probingDirectories.Add(baseDirectory); probingDirectories.AddRange(Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory))); foreach (var directory in probingDirectories) { var helpFiles = Directory.EnumerateFiles(directory, "*.dll-Help.xml") .Where(f => !processedHelpFiles.Contains(Path.GetFileName(f), StringComparer.OrdinalIgnoreCase)).ToList(); if (helpFiles.Any()) { Directory.SetCurrentDirectory(directory); foreach (var helpFile in helpFiles) { var cmdletFile = helpFile.Substring(0, helpFile.Length - "-Help.xml".Length); var helpFileName = Path.GetFileName(helpFile); var cmdletFileName = Path.GetFileName(cmdletFile); if (File.Exists(cmdletFile)) { issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = cmdletFileName, "AssemblyFileName"); processedHelpFiles.Add(helpFileName); var proxy = EnvironmentHelpers.CreateProxy <CmdletSignatureLoader>(directory, out _appDomain); var cmdlets = proxy.GetCmdlets(cmdletFile); if (cmdletFilter != null) { cmdlets = cmdlets.Where <CmdletSignatureMetadata>((cmdlet) => cmdletFilter(cmdlet.Name)).ToList <CmdletSignatureMetadata>(); } foreach (var cmdlet in cmdlets) { Logger.WriteMessage("Processing cmdlet '{0}'", cmdlet.ClassName); 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."); } 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."); } } AppDomain.Unload(_appDomain); issueLogger.Decorator.Remove("AssemblyFileName"); } } Directory.SetCurrentDirectory(savedDirectory); } } } }
private void AnalyzeMarkdownHelp( IEnumerable <string> scopes, string directory, ReportLogger <HelpIssue> helpLogger, List <string> processedHelpFiles, string savedDirectory) { var helpFolder = Directory.EnumerateDirectories(directory, "help").FirstOrDefault(); var service = Path.GetFileName(directory); if (helpFolder == null) { helpLogger.LogRecord(new HelpIssue() { Assembly = service, Description = string.Format("{0} has no matching help folder", service), Severity = 0, Remediation = string.Format("Make sure a help folder for {0} exists and it is " + "being copied to the output directory.", service), Target = service, HelpFile = service + "/folder", ProblemId = MissingHelpFile }); return; } var helpFiles = Directory.EnumerateFiles(helpFolder, "*.md").Select(f => Path.GetFileNameWithoutExtension(f)).ToList(); if (helpFiles.Any()) { Directory.SetCurrentDirectory(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) { return; } var psd1 = manifestFiles.FirstOrDefault(); var parentDirectory = Directory.GetParent(psd1).FullName; var psd1FileName = Path.GetFileName(psd1); IEnumerable <string> nestedModules = null; List <string> requiredModules = null; PowerShell powershell = PowerShell.Create(); powershell.AddScript("Import-LocalizedData -BaseDirectory " + parentDirectory + " -FileName " + psd1FileName + " -BindingVariable ModuleMetadata; $ModuleMetadata.NestedModules; $ModuleMetadata.RequiredModules | % { $_[\"ModuleName\"] };"); var cmdletResult = powershell.Invoke(); nestedModules = cmdletResult.Where(c => c.ToString().StartsWith(".")).Select(c => c.ToString().Substring(2)); requiredModules = cmdletResult.Where(c => !c.ToString().StartsWith(".")).Select(c => c.ToString()).ToList(); if (nestedModules.Any()) { Directory.SetCurrentDirectory(directory); requiredModules = requiredModules.Join(scopes, module => 1, dir => 1, (module, dir) => Path.Combine(dir, module)) .Where(f => Directory.Exists(f)) .ToList(); requiredModules.Add(directory); List <CmdletMetadata> allCmdlets = new List <CmdletMetadata>(); foreach (var nestedModule in nestedModules) { var assemblyFile = Directory.GetFiles(parentDirectory, nestedModule, SearchOption.AllDirectories).FirstOrDefault(); if (File.Exists(assemblyFile)) { var assemblyFileName = Path.GetFileName(assemblyFile); helpLogger.Decorator.AddDecorator((h) => { h.HelpFile = assemblyFileName; h.Assembly = assemblyFileName; }, "Cmdlet"); processedHelpFiles.Add(assemblyFileName); var proxy = EnvironmentHelpers.CreateProxy <CmdletLoader>(directory, out _appDomain); var module = proxy.GetModuleMetadata(assemblyFile, requiredModules); var cmdlets = module.Cmdlets; allCmdlets.AddRange(cmdlets); helpLogger.Decorator.Remove("Cmdlet"); AppDomain.Unload(_appDomain); } } ValidateHelpRecords(allCmdlets, helpFiles, helpLogger); } Directory.SetCurrentDirectory(savedDirectory); } }
/// <summary> /// Determine which version bump should be applied to a module version. /// This will compare the cmdlet assemblies in the output (built) module manifest with /// the cmdlet assemblies in the saved gallery folder. /// </summary> /// <returns>Version enum representing the version bump to be applied.</returns> public Version GetVersionBumpUsingGallery() { var outputModuleManifestPath = _fileHelper.OutputModuleManifestPath; var outputModuleDirectory = _fileHelper.OutputModuleDirectory; var outputDirectories = _fileHelper.OutputDirectories; var serializedCmdletsDirectory = _fileHelper.SerializedCmdletsDirectory; var galleryModuleVersionDirectory = _fileHelper.GalleryModuleVersionDirectory; var moduleName = _fileHelper.ModuleName; IEnumerable <string> nestedModules = null; using (PowerShell powershell = PowerShell.Create()) { powershell.AddScript("(Test-ModuleManifest -Path " + outputModuleManifestPath + ").NestedModules"); var cmdletResult = powershell.Invoke(); nestedModules = cmdletResult.Select(c => c.ToString() + ".dll"); } Version versionBump = Version.PATCH; if (nestedModules.Any()) { var tempVersionBump = Version.PATCH; var issueLogger = _logger.CreateLogger <BreakingChangeIssue>("BreakingChangeIssues.csv"); List <string> requiredModules = null; List <string> galleryRequiredModules = null; using (PowerShell powershell = PowerShell.Create()) { powershell.AddScript("(Test-ModuleManifest -Path " + outputModuleManifestPath + ").RequiredModules"); var cmdletResult = powershell.Invoke(); requiredModules = cmdletResult.Select(c => c.ToString()) .Join(outputDirectories, module => 1, directory => 1, (module, directory) => Path.Combine(directory, module)) .Where(f => Directory.Exists(f)) .ToList(); galleryRequiredModules = cmdletResult.Select(c => c.ToString()) .Select(f => Directory.GetDirectories(outputModuleDirectory, f).FirstOrDefault()) .Select(f => Directory.GetDirectories(f).FirstOrDefault()) .ToList(); } requiredModules.Add(outputModuleDirectory); galleryRequiredModules.Add(galleryModuleVersionDirectory); foreach (var nestedModule in nestedModules) { var assemblyPath = Directory.GetFiles(galleryModuleVersionDirectory, nestedModule).FirstOrDefault(); var proxy = EnvironmentHelpers.CreateProxy <CmdletLoader>(galleryModuleVersionDirectory, out _appDomain); var oldModuleMetadata = proxy.GetModuleMetadata(assemblyPath, galleryRequiredModules); assemblyPath = Directory.GetFiles(outputModuleDirectory, nestedModule).FirstOrDefault(); proxy = EnvironmentHelpers.CreateProxy <CmdletLoader>(galleryModuleVersionDirectory, out _appDomain); var newModuleMetadata = proxy.GetModuleMetadata(assemblyPath, requiredModules); CmdletLoader.ModuleMetadata = oldModuleMetadata; issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = assemblyPath, "AssemblyFileName"); CheckBreakingChangesInModules(oldModuleMetadata, newModuleMetadata, issueLogger); if (issueLogger.Records.Any()) { tempVersionBump = Version.MAJOR; } else if (!oldModuleMetadata.Equals(newModuleMetadata)) { tempVersionBump = Version.MINOR; } if (tempVersionBump == Version.MAJOR) { versionBump = Version.MAJOR; } else if (tempVersionBump == Version.MINOR && versionBump == Version.PATCH) { versionBump = Version.MINOR; } } } else { throw new NullReferenceException("No nested modules found for " + moduleName); } return(versionBump); }
/// <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) { var savedDirectory = Directory.GetCurrentDirectory(); if (directoryFilter != null) { cmdletProbingDirs = directoryFilter(cmdletProbingDirs); } var logFileName = Path.Combine(OutputFilePath, BreakingChangeAttributeReportLoggerName); //Init the log file TextFileLogger logger = TextFileLogger.GetTextFileLogger(logFileName, CleanBreakingChangesFileBeforeWriting); try { foreach (var baseDirectory in cmdletProbingDirs.Where(s => !s.Contains("ServiceManagement") && !s.Contains("Stack") && Directory.Exists(Path.GetFullPath(s)))) { List <string> probingDirectories = new List <string>(); // Add current directory for probing probingDirectories.Add(baseDirectory); probingDirectories.AddRange(Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory))); foreach (var directory in probingDirectories) { IEnumerable <string> cmdlets = GetCmdletsFilesInFolder(directory); if (cmdlets.Any()) { foreach (var cmdletFileName in cmdlets) { var cmdletFileFullPath = Path.Combine(directory, Path.GetFileName(cmdletFileName)); if (File.Exists(cmdletFileFullPath)) { var proxy = EnvironmentHelpers.CreateProxy <CmdletBreakingChangeAttributeLoader>(directory, out _appDomain); var cmdletDataForModule = proxy.GetModuleBreakingChangeAttributes(cmdletFileFullPath); //If there is nothing in this module just onctinue if (cmdletDataForModule == null) { Console.WriteLine("No breaking change attributes found in module " + cmdletFileName); continue; } if (cmdletFilter != null) { string output = string.Format("Before filter\nmodule cmdlet count: {0}\n", cmdletDataForModule.CmdletList.Count); output += string.Format("\nCmdlet file: {0}", cmdletFileFullPath); cmdletDataForModule.FilterCmdlets(cmdletFilter); output += string.Format("After filter\nmodule cmdlet count: {0}\n", cmdletDataForModule.CmdletList.Count); foreach (var cmdlet in cmdletDataForModule.CmdletList) { output += string.Format("\n\tcmdlet - {0}", cmdlet.CmdletName); } Console.WriteLine(output); } LogBreakingChangesInModule(cmdletDataForModule, logger); } } } } } } finally { if (logger != null) { TextFileLogger.CloseLogger(logFileName); logger = null; } } }
private void AnalyzeMamlHelp( IEnumerable <string> scopes, string directory, ReportLogger <HelpIssue> helpLogger, List <string> processedHelpFiles, string savedDirectory) { var commandAssemblies = Directory.EnumerateFiles(directory, "*.Commands.*.dll") .Where(f => IsAssemblyFile(f) && !File.Exists(f + "-Help.xml")); foreach (var orphanedAssembly in commandAssemblies) { helpLogger.LogRecord(new HelpIssue() { Assembly = orphanedAssembly, Description = string.Format("{0} has no matching help file", orphanedAssembly), Severity = 0, Remediation = string.Format("Make sure a dll Help file for {0} exists and it is " + "being copied to the output directory.", orphanedAssembly), Target = orphanedAssembly, HelpFile = orphanedAssembly + "-Help.xml", ProblemId = MissingHelpFile }); } var helpFiles = Directory.EnumerateFiles(directory, "*.dll-Help.xml") .Where(f => !processedHelpFiles.Contains(Path.GetFileName(f), StringComparer.OrdinalIgnoreCase)).ToList(); if (!helpFiles.Any()) { return; } Directory.SetCurrentDirectory(directory); foreach (var helpFile in helpFiles) { var cmdletFile = helpFile.Substring(0, helpFile.Length - "-Help.xml".Length); var helpFileName = Path.GetFileName(helpFile); var cmdletFileName = Path.GetFileName(cmdletFile); if (!File.Exists(cmdletFile)) { continue; } processedHelpFiles.Add(helpFileName); helpLogger.Decorator.AddDecorator(h => { h.HelpFile = helpFileName; h.Assembly = cmdletFileName; }, "Cmdlet"); // TODO: Remove IfDef #if NETSTANDARD var proxy = new CmdletLoader(); #else var proxy = EnvironmentHelpers.CreateProxy <CmdletLoader>(directory, out _appDomain); #endif var module = proxy.GetModuleMetadata(cmdletFile); var cmdlets = module.Cmdlets; var helpRecords = CmdletHelpParser.GetHelpTopics(helpFile, helpLogger); ValidateHelpRecords(cmdlets, helpRecords, helpLogger); helpLogger.Decorator.Remove("Cmdlet"); // TODO: Remove IfDef code #if !NETSTANDARD AppDomain.Unload(_appDomain); #endif } 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> /// <param name="modulesToAnalyze">The set of modules to analyze</param> public void Analyze(IEnumerable <string> cmdletProbingDirs, Func <IEnumerable <string>, IEnumerable <string> > directoryFilter, Func <string, bool> cmdletFilter, IEnumerable <string> modulesToAnalyze) { if (directoryFilter != null) { cmdletProbingDirs = directoryFilter(cmdletProbingDirs); } var logFileName = Path.Combine(OutputFilePath, BreakingChangeAttributeReportLoggerName); //Init the log file var logger = TextFileLogger.GetTextFileLogger(logFileName, CleanBreakingChangesFileBeforeWriting); try { foreach (var baseDirectory in cmdletProbingDirs.Where(s => !s.Contains("ServiceManagement") && !ModuleFilter.IsAzureStackModule(s) && Directory.Exists(Path.GetFullPath(s)))) { 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 cmdlets = GetCmdletsFilesInFolder(directory); if (!cmdlets.Any()) { continue; } foreach (var cmdletFileName in cmdlets) { var cmdletFileFullPath = Path.Combine(directory, Path.GetFileName(cmdletFileName)); if (!File.Exists(cmdletFileFullPath)) { continue; } // TODO: Remove IfDef #if NETSTANDARD var proxy = new CmdletBreakingChangeAttributeLoader(); #else var proxy = EnvironmentHelpers.CreateProxy <CmdletBreakingChangeAttributeLoader>(directory, out _appDomain); #endif var cmdletDataForModule = proxy.GetModuleBreakingChangeAttributes(cmdletFileFullPath); //If there is nothing in this module just continue if (cmdletDataForModule == null) { Console.WriteLine("No breaking change attributes found in module " + cmdletFileName); continue; } if (cmdletFilter != null) { var output = string.Format("Before filter\nmodule cmdlet count: {0}\n", cmdletDataForModule.CmdletList.Count); output += string.Format("\nCmdlet file: {0}", cmdletFileFullPath); cmdletDataForModule.FilterCmdlets(cmdletFilter); output += string.Format("After filter\nmodule cmdlet count: {0}\n", cmdletDataForModule.CmdletList.Count); foreach (var cmdlet in cmdletDataForModule.CmdletList) { output += string.Format("\n\tcmdlet - {0}", cmdlet.CmdletName); } Console.WriteLine(output); } LogBreakingChangesInModule(cmdletDataForModule, logger); // TODO: Remove IfDef code #if !NETSTANDARD AppDomain.Unload(_appDomain); #endif } } } } finally { if (logger != null) { TextFileLogger.CloseLogger(logFileName); } } }
/// <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) { var savedDirectory = Directory.GetCurrentDirectory(); 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") && !s.Contains("Stack") && Directory.Exists(Path.GetFullPath(s)))) { List <string> probingDirectories = new List <string>(); // Add current directory for probing probingDirectories.Add(baseDirectory); probingDirectories.AddRange(Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory))); foreach (var directory in probingDirectories) { 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); var psd1FileName = Path.GetFileName(psd1); PowerShell powershell = PowerShell.Create(); powershell.AddScript("Import-LocalizedData -BaseDirectory " + parentDirectory + " -FileName " + psd1FileName + " -BindingVariable ModuleMetadata; $ModuleMetadata.NestedModules"); var cmdletResult = powershell.Invoke(); var cmdletFiles = cmdletResult.Select(c => c.ToString().Substring(2)); if (cmdletFiles.Any()) { foreach (var cmdletFileName in cmdletFiles) { var cmdletFileFullPath = Path.Combine(directory, Path.GetFileName(cmdletFileName)); if (File.Exists(cmdletFileFullPath)) { issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = cmdletFileFullPath, "AssemblyFileName"); processedHelpFiles.Add(cmdletFileName); var proxy = EnvironmentHelpers.CreateProxy <CmdletBreakingChangeLoader>(directory, out _appDomain); var newModuleMetadata = proxy.GetModuleMetadata(cmdletFileFullPath); string fileName = cmdletFileName + ".json"; string executingPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).AbsolutePath); string filePath = executingPath + "\\SerializedCmdlets\\" + fileName; bool serialize = false; if (serialize) { SerializeCmdlets(filePath, newModuleMetadata); } else { if (!File.Exists(filePath)) { continue; } var oldModuleMetadata = DeserializeCmdlets(filePath); if (cmdletFilter != null) { string 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}", cmdletFileFullPath); 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); } } } } } } }
/// <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) { var savedDirectory = Directory.GetCurrentDirectory(); var processedHelpFiles = new List <string>(); var issueLogger = Logger.CreateLogger <SignatureIssue>("SignatureIssues.csv"); foreach (var baseDirectory in scopes.Where(s => !s.Contains("ServiceManagement") && Directory.Exists(Path.GetFullPath(s)))) { foreach (var directory in Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory))) { var helpFiles = Directory.EnumerateFiles(directory, "*.dll-Help.xml") .Where(f => !processedHelpFiles.Contains(Path.GetFileName(f), StringComparer.OrdinalIgnoreCase)).ToList(); if (helpFiles.Any()) { Directory.SetCurrentDirectory(directory); foreach (var helpFile in helpFiles) { var cmdletFile = helpFile.Substring(0, helpFile.Length - "-Help.xml".Length); var helpFileName = Path.GetFileName(helpFile); var cmdletFileName = Path.GetFileName(cmdletFile); if (File.Exists(cmdletFile)) { issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = cmdletFileName, "AssemblyFileName"); processedHelpFiles.Add(helpFileName); var proxy = EnvironmentHelpers.CreateProxy <CmdletSignatureLoader>(directory, out _appDomain); var cmdlets = proxy.GetCmdlets(cmdletFile); foreach (var cmdlet in cmdlets) { 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: 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: 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: 2, problemId: 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: 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.IsShouldContinueVerb && !cmdlet.HasForceSwitch) { issueLogger.LogSignatureIssue( cmdlet: cmdlet, severity: 2, problemId: CmdletWithDestructiveVerbNoForce, description: string.Format( "{0} does not have a Force parameter, but the cmdlet verb '{1}' " + "indicates that it may perform destrucvie actions under certain " + "circumstances. Consider wehtehr the cmdlet should have a Force " + "parameter anduse ShouldContinue under some circumstances. ", cmdlet.Name, cmdlet.VerbName), remediation: "Consider wehtehr the cmdlet should have a Force " + "parameter and use ShouldContinue under some circumstances. "); } } AppDomain.Unload(_appDomain); issueLogger.Decorator.Remove("AssemblyFileName"); } } Directory.SetCurrentDirectory(savedDirectory); } } } }
public void Analyze(IEnumerable <string> cmdletProbingDirs, Func <IEnumerable <string>, IEnumerable <string> > directoryFilter, Func <string, bool> cmdletFilter) { var savedDirectory = Directory.GetCurrentDirectory(); var processedHelpFiles = new List <string>(); var issueLogger = Logger.CreateLogger <SignatureIssue>(signatureIssueReportLoggerName); List <string> probingDirectories = new List <string>(); if (directoryFilter != null) { cmdletProbingDirs = directoryFilter(cmdletProbingDirs); } foreach (var baseDirectory in cmdletProbingDirs.Where(s => !s.Contains("ServiceManagement") && !s.Contains("Stack") && Directory.Exists(Path.GetFullPath(s)))) { //Add current directory for probing probingDirectories.Add(baseDirectory); probingDirectories.AddRange(Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory))); foreach (var directory in probingDirectories) { 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); IEnumerable <string> nestedModules = null; List <string> requiredModules = null; PowerShell powershell = PowerShell.Create(); powershell.AddScript("Import-LocalizedData -BaseDirectory " + parentDirectory + " -FileName " + psd1FileName + " -BindingVariable ModuleMetadata; $ModuleMetadata.NestedModules; $ModuleMetadata.RequiredModules | % { $_[\"ModuleName\"] };"); var cmdletResult = powershell.Invoke(); nestedModules = cmdletResult.Where(c => c.ToString().StartsWith(".")).Select(c => c.ToString().Substring(2)); requiredModules = cmdletResult.Where(c => !c.ToString().StartsWith(".")).Select(c => c.ToString()).ToList(); if (nestedModules.Any()) { Directory.SetCurrentDirectory(directory); requiredModules = requiredModules.Join(cmdletProbingDirs, module => 1, dir => 1, (module, dir) => Path.Combine(dir, module)) .Where(f => Directory.Exists(f)) .ToList(); requiredModules.Add(directory); foreach (var nestedModule in nestedModules) { var assemblyFile = Directory.GetFiles(parentDirectory, nestedModule, SearchOption.AllDirectories).FirstOrDefault(); if (File.Exists(assemblyFile)) { issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = assemblyFile, "AssemblyFileName"); processedHelpFiles.Add(assemblyFile); var proxy = EnvironmentHelpers.CreateProxy <CmdletLoader>(directory, out _appDomain); var module = proxy.GetModuleMetadata(assemblyFile, requiredModules); var cmdlets = module.Cmdlets; if (cmdletFilter != null) { cmdlets = cmdlets.Where <CmdletMetadata>((cmdlet) => cmdletFilter(cmdlet.Name)).ToList <CmdletMetadata>(); } foreach (var cmdlet in cmdlets) { Logger.WriteMessage("Processing cmdlet '{0}'", cmdlet.ClassName); 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."); } 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."); } } AppDomain.Unload(_appDomain); 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)))) { 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 = 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 } } } }
private void ProcessDirectory(string directoryPath) { var savedDirectory = Directory.GetCurrentDirectory(); Directory.SetCurrentDirectory(directoryPath); // TODO: Remove IfDef #if NETSTANDARD _loader = new AssemblyLoader(); #else _loader = EnvironmentHelpers.CreateProxy <AssemblyLoader>(directoryPath, out _testDomain); #endif foreach (var file in Directory.GetFiles(directoryPath).Where(file => file.EndsWith(".dll"))) { var assembly = CreateAssemblyRecord(file); _assemblies[assembly.Name] = assembly; if (RequiresExactVersionMatch(assembly)) { AddSharedAssemblyExactVersion(assembly); } else { AddSharedAssembly(assembly); } } // Now check for assembly mismatches foreach (var assembly in _assemblies.Values) { foreach (var reference in assembly.Children) { CheckAssemblyReference(reference, assembly); } } foreach (var assembly in _assemblies.Values) { if (!assembly.Name.Contains("System") && !assembly.Name.Contains("Microsoft.IdentityModel") && !assembly.Name.Equals("Newtonsoft.Json") && !assembly.Name.Equals("Microsoft.AspNetCore.WebUtilities")) { foreach (var parent in assembly.ReferencingAssembly) { _dependencyMapLogger.LogRecord( new DependencyMap { AssemblyName = assembly.Name, AssemblyVersion = assembly.Version.ToString(), ReferencingAssembly = parent.Name, ReferencingAssemblyVersion = parent.Version.ToString(), Severity = 3 }); } } } FindExtraAssemblies(); // TODO: Remove IfDef code #if !NETSTANDARD AppDomain.Unload(_testDomain); #endif Directory.SetCurrentDirectory(savedDirectory); }
/// <summary> /// Determine what version bump should be applied to a module version. /// This will compare the cmdlet assemblies in the output (built) module manifest with /// the corresponding deserialized module metadata from the JSON file. /// </summary> /// <param name="serialize">Whether or not the module metadata should be serialized.</param> /// <returns>Version enum representing the version bump to be applied.</returns> public Version GetVersionBumpUsingSerialized(bool serialize = true) { var outputModuleManifestPath = _fileHelper.OutputModuleManifestPath; var outputModuleDirectory = _fileHelper.OutputModuleDirectory; var outputDirectories = _fileHelper.OutputDirectories; var serializedCmdletsDirectory = _fileHelper.SerializedCmdletsDirectory; var moduleName = _fileHelper.ModuleName; IEnumerable <string> nestedModules = null; using (PowerShell powershell = PowerShell.Create()) { powershell.AddScript("(Test-ModuleManifest -Path " + outputModuleManifestPath + ").NestedModules"); var cmdletResult = powershell.Invoke(); nestedModules = cmdletResult.Select(c => c.ToString() + ".dll"); } Version versionBump = Version.PATCH; if (nestedModules.Any()) { var tempVersionBump = Version.PATCH; var issueLogger = _logger.CreateLogger <BreakingChangeIssue>("BreakingChangeIssues.csv"); List <string> requiredModules = null; using (PowerShell powershell = PowerShell.Create()) { powershell.AddScript("(Test-ModuleManifest -Path " + outputModuleManifestPath + ").RequiredModules"); var cmdletResult = powershell.Invoke(); requiredModules = cmdletResult.Select(c => c.ToString()) .Join(outputDirectories, module => 1, directory => 1, (module, directory) => Path.Combine(directory, module)) .Where(f => Directory.Exists(f)) .ToList(); } requiredModules.Add(outputModuleDirectory); foreach (var nestedModule in nestedModules) { var assemblyPath = Directory.GetFiles(outputModuleDirectory, nestedModule, SearchOption.AllDirectories).FirstOrDefault(); var proxy = EnvironmentHelpers.CreateProxy <CmdletLoader>(outputModuleManifestPath, out _appDomain); var newModuleMetadata = proxy.GetModuleMetadata(assemblyPath, requiredModules); var serializedCmdletName = nestedModule + ".json"; var serializedCmdletFile = Directory.GetFiles(serializedCmdletsDirectory, serializedCmdletName).FirstOrDefault(); if (serializedCmdletFile == null) { var currentColor = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"Warning: {nestedModule} does not have a previously serialized cmdlet for comparison."); Console.ForegroundColor = currentColor; continue; } var oldModuleMetadata = DeserializeCmdlets(serializedCmdletFile); CmdletLoader.ModuleMetadata = oldModuleMetadata; issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = assemblyPath, "AssemblyFileName"); CheckBreakingChangesInModules(oldModuleMetadata, newModuleMetadata, issueLogger); if (issueLogger.Records.Any()) { tempVersionBump = Version.MAJOR; } else if (!oldModuleMetadata.Equals(newModuleMetadata)) { tempVersionBump = Version.MINOR; } if (tempVersionBump != Version.PATCH && serialize) { SerializeCmdlets(serializedCmdletFile, newModuleMetadata); } if (tempVersionBump == Version.MAJOR) { versionBump = Version.MAJOR; } else if (tempVersionBump == Version.MINOR && versionBump == Version.PATCH) { versionBump = Version.MINOR; } } } else { return(Version.PATCH); } return(versionBump); }
private void AnalyzeMarkdownHelp( IEnumerable <string> scopes, string directory, ReportLogger <HelpIssue> helpLogger, List <string> processedHelpFiles, string savedDirectory) { var helpFolder = Directory.EnumerateDirectories(directory, "help").FirstOrDefault(); var service = Path.GetFileName(directory); if (helpFolder == null) { helpLogger.LogRecord(new HelpIssue() { Assembly = service, Description = string.Format("{0} has no matching help folder", service), Severity = 0, Remediation = string.Format("Make sure a help folder for {0} exists and it is " + "being copied to the output directory.", service), Target = service, HelpFile = service + "/folder", ProblemId = MissingHelpFile }); return; } var helpFiles = Directory.EnumerateFiles(helpFolder, "*.md").Select(Path.GetFileNameWithoutExtension).ToList(); // Assume all cmdlets markdown file following format of VERB-AzResource. Dash is required. helpFiles = helpFiles.Where(c => c.Contains("-")).ToList(); if (!helpFiles.Any()) { return; } Directory.SetCurrentDirectory(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) { return; } 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; $ModuleMetadata.NestedModules"; powershell.AddScript(script); var cmdletResult = powershell.Invoke(); var nestedModules = new List <string>(); foreach (var module in cmdletResult) { if (module != null && module.ToString().StartsWith(".")) { nestedModules.Add(module.ToString().Substring(2)); } else if (module != null) { nestedModules.Add(module.ToString()); } } script = $"Import-LocalizedData -BaseDirectory {parentDirectory} -FileName {psd1FileName}" + " -BindingVariable ModuleMetadata; $ModuleMetadata.RequiredModules | % { $_[\"ModuleName\"] };"; cmdletResult = PowerShell.Create().AddScript(script).Invoke(); var requiredModules = cmdletResult.Where(c => c != null && !c.ToString().StartsWith(".")).Select(c => c.ToString()).ToList(); var allCmdlets = new List <CmdletMetadata>(); if (nestedModules.Any()) { Directory.SetCurrentDirectory(directory); requiredModules = requiredModules.Join(scopes, 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; } var assemblyFileName = Path.GetFileName(assemblyFile); helpLogger.Decorator.AddDecorator(h => { h.HelpFile = assemblyFileName; h.Assembly = assemblyFileName; }, "Cmdlet"); processedHelpFiles.Add(assemblyFileName); // 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; allCmdlets.AddRange(cmdlets); helpLogger.Decorator.Remove("Cmdlet"); // TODO: Remove IfDef code #if !NETSTANDARD AppDomain.Unload(_appDomain); #endif } } script = $"Import-LocalizedData -BaseDirectory {parentDirectory} -FileName {psd1FileName} -BindingVariable ModuleMetadata; $ModuleMetadata.FunctionsToExport;"; cmdletResult = PowerShell.Create().AddScript(script).Invoke(); var functionCmdlets = cmdletResult.Select(c => c.ToString()).ToList(); foreach (var cmdlet in functionCmdlets) { var metadata = new CmdletMetadata(); metadata.VerbName = cmdlet.Split("-")[0]; metadata.NounName = cmdlet.Split("-")[1]; allCmdlets.Add(metadata); } ValidateHelpRecords(allCmdlets, helpFiles, helpLogger); ValidateHelpMarkdown(helpFolder, helpFiles, helpLogger); 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) { var savedDirectory = Directory.GetCurrentDirectory(); 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") && Directory.Exists(Path.GetFullPath(s)))) { List <string> probingDirectories = new List <string>(); // Add current directory for probing probingDirectories.Add(baseDirectory); probingDirectories.AddRange(Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory))); foreach (var directory in probingDirectories) { var index = Path.GetFileName(directory).IndexOf("."); var service = Path.GetFileName(directory).Substring(index + 1); var helpFiles = Directory.EnumerateFiles(directory, "*.dll-Help.xml") .Where(f => !processedHelpFiles.Contains(Path.GetFileName(f), StringComparer.OrdinalIgnoreCase)).ToList(); if (helpFiles.Count > 1) { helpFiles = helpFiles.Where(f => Path.GetFileName(f).IndexOf(service) >= 0).ToList(); } if (helpFiles.Any()) { Directory.SetCurrentDirectory(directory); foreach (var helpFile in helpFiles) { var cmdletFile = helpFile.Substring(0, helpFile.Length - "-Help.xml".Length); var helpFileName = Path.GetFileName(helpFile); var cmdletFileName = Path.GetFileName(cmdletFile); if (File.Exists(cmdletFile)) { issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = cmdletFileName, "AssemblyFileName"); processedHelpFiles.Add(helpFileName); var proxy = EnvironmentHelpers.CreateProxy <CmdletBreakingChangeLoader>(directory, out _appDomain); var newModuleMetadata = proxy.GetModuleMetadata(cmdletFile); string fileName = cmdletFileName + ".json"; string executingPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).AbsolutePath); string filePath = executingPath + "\\SerializedCmdlets\\" + fileName; bool serialize = false; if (serialize) { SerializeCmdlets(filePath, newModuleMetadata); } else { var oldModuleMetadata = DeserializeCmdlets(filePath); if (cmdletFilter != null) { oldModuleMetadata.FilterCmdlets(cmdletFilter); newModuleMetadata.FilterCmdlets(cmdletFilter); } RunBreakingChangeChecks(oldModuleMetadata, newModuleMetadata, issueLogger); } } } } } } }