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);
        }
Exemple #4
0
        /// <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.");
            }
        }
Exemple #5
0
        /// <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);
            }
        }
Exemple #9
0
        /// <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);
        }
Exemple #10
0
        /// <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);
        }
Exemple #12
0
        /// <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);
                }
            }
        }
Exemple #13
0
        /// <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);
                                }
                            }
                        }
                    }
                }
            }
        }
Exemple #14
0
        /// <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);
                    }
                }
            }
        }
Exemple #16
0
        /// <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);
                                }
                            }
                        }
                    }
                }
            }
        }