/// <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 = new CmdletLoader(); 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 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 } } } }
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); } } }
/// <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() { Console.WriteLine("Comparing the cmdlet assumblies with seemblies in the saved gallery folder..."); 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 = new CmdletLoader(); var oldModuleMetadata = proxy.GetModuleMetadata(assemblyPath, galleryRequiredModules); assemblyPath = Directory.GetFiles(outputModuleDirectory, nestedModule).FirstOrDefault(); proxy = new CmdletLoader(); 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> /// 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) { Console.WriteLine("Comparing the cmdlet assumblies with metadata from JSON file..."); 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()); } 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 nestedModuleName in nestedModules) { // Handcrafted modules assume its nested module always is DLL file. var nestedModule = nestedModuleName + ".dll"; var assemblyPath = Directory.GetFiles(outputModuleDirectory, nestedModule, SearchOption.AllDirectories).FirstOrDefault(); // However we support PSM1, PSD1 other nested module type. Skip this check and we need to use a different design soon. if (assemblyPath == null) { continue; } var proxy = new CmdletLoader(); 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; var newCmdletFile = Path.Join(serializedCmdletsDirectory, serializedCmdletName); SerializeCmdlets(newCmdletFile, newModuleMetadata); continue; } var oldModuleMetadata = DeserializeCmdlets(serializedCmdletFile); CmdletLoader.ModuleMetadata = oldModuleMetadata; issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = assemblyPath, "AssemblyFileName"); CheckBreakingChangesInModules(oldModuleMetadata, newModuleMetadata, issueLogger); if (issueLogger.Records.Any()) { var currentColor = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"Detected below {issueLogger.Records.Count} breack change(s):"); foreach (IReportRecord record in issueLogger.Records) { Console.WriteLine(((BreakingChangeIssue)record).Target + " " + record.ProblemId + " " + record.Description); } Console.ForegroundColor = currentColor; 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); }
/// <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 = new CmdletLoader(); 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; var newCmdletFile = Path.Join(serializedCmdletsDirectory, serializedCmdletName); SerializeCmdlets(newCmdletFile, newModuleMetadata); 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); }