Example #1
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, IEnumerable <string> modulesToAnalyze)
        {
            var savedDirectory     = Directory.GetCurrentDirectory();
            var processedHelpFiles = new List <string>();
            var helpLogger         = Logger.CreateLogger <HelpIssue>("HelpIssues.csv");

            foreach (var baseDirectory in scopes.Where(s => Directory.Exists(Path.GetFullPath(s))))
            {
                SharedAssemblyLoader.Load(baseDirectory);
                foreach (var directory in Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory)))
                {
                    if (modulesToAnalyze != null &&
                        modulesToAnalyze.Any() &&
                        !modulesToAnalyze.Any(m => directory.EndsWith(m)))
                    {
                        continue;
                    }

                    var dirs = Directory.EnumerateDirectories(directory);
                    if (dirs != null && dirs.Any(d => string.Equals(Path.GetFileName(d), "help", StringComparison.OrdinalIgnoreCase)))
                    {
                        Console.WriteLine($"Analyzing module under {directory} ...");
                        AnalyzeMarkdownHelp(scopes, directory, helpLogger, processedHelpFiles, savedDirectory);
                    }
                }
            }
        }
Example #2
0
        public void Analyze(IEnumerable <string> directories, IEnumerable <String> modulesToAnalyze)
        {
            if (directories == null)
            {
                throw new ArgumentNullException("directories");
            }

            _versionConflictLogger = Logger.CreateLogger <AssemblyVersionConflict>("AssemblyVersionConflict.csv");
            _sharedConflictLogger  = Logger.CreateLogger <SharedAssemblyConflict>("SharedAssemblyConflict.csv");
            _missingAssemblyLogger = Logger.CreateLogger <MissingAssembly>("MissingAssemblies.csv");
            _extraAssemblyLogger   = Logger.CreateLogger <ExtraAssembly>("ExtraAssemblies.csv");
            _dependencyMapLogger   = Logger.CreateLogger <DependencyMap>("DependencyMap.csv");
            foreach (var baseDirectory in directories)
            {
                SharedAssemblyLoader.Load(baseDirectory);
                foreach (var directoryPath in Directory.EnumerateDirectories(baseDirectory))
                {
                    if (modulesToAnalyze != null &&
                        modulesToAnalyze.Any() &&
                        !modulesToAnalyze.Any(m => directoryPath.EndsWith(m)))
                    {
                        continue;
                    }

                    if (!Directory.Exists(directoryPath))
                    {
                        throw new InvalidOperationException("Please pass a valid directory name as the first parameter");
                    }

                    Logger.WriteMessage("Processing Directory {0}", directoryPath);
                    _assemblies.Clear();
                    _loader = new AssemblyMetadataLoader();
                    _versionConflictLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; }, "Directory");
                    _missingAssemblyLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; }, "Directory");
                    _extraAssemblyLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; }, "Directory");
                    _dependencyMapLogger.Decorator.AddDecorator(r => { r.Directory = directoryPath; }, "Directory");
                    _isNetcore = directoryPath.Contains("Az.");
                    ProcessDirectory(directoryPath);
                    _versionConflictLogger.Decorator.Remove("Directory");
                    _missingAssemblyLogger.Decorator.Remove("Directory");
                    _extraAssemblyLogger.Decorator.Remove("Directory");
                    _dependencyMapLogger.Decorator.Remove("Directory");
                }
            }
        }
Example #3
0
        public static void Main(string[] args)
        {
            var executingAssemblyPath      = Assembly.GetExecutingAssembly().Location;
            var versionControllerDirectory = Directory.GetParent(executingAssemblyPath).FullName;
            var artifactsDirectory         = Directory.GetParent(versionControllerDirectory).FullName;

            _rootDirectory      = Directory.GetParent(artifactsDirectory).FullName;
            _projectDirectories = new List <string> {
                Path.Combine(_rootDirectory, @"src\")
            }.Where((d) => Directory.Exists(d)).ToList();
            _outputDirectories = new List <string> {
                Path.Combine(_rootDirectory, @"artifacts\Release\")
            }.Where((d) => Directory.Exists(d)).ToList();

            SharedAssemblyLoader.Load(_outputDirectories.FirstOrDefault());
            var exceptionsDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Exceptions");

            if (args != null && args.Length > 0)
            {
                exceptionsDirectory = args[0];
            }

            if (!Directory.Exists(exceptionsDirectory))
            {
                throw new ArgumentException("Please provide a path to the Exceptions folder in the output directory (artifacts/Exceptions).");
            }

            _moduleNameFilter = string.Empty;
            if (args != null && args.Length > 1)
            {
                _moduleNameFilter = args[1] + ".psd1";
            }

            ConsolidateExceptionFiles(exceptionsDirectory);
            ValidateManifest();
            BumpVersions();
            ValidateVersionBump();
        }
Example #4
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))))
            {
                SharedAssemblyLoader.Load(baseDirectory);
                var probingDirectories = new List <string> {
                    baseDirectory
                };

                // Add current directory for probing
                probingDirectories.AddRange(Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory)));

                foreach (var directory in probingDirectories)
                {
                    if (modulesToAnalyze != null &&
                        modulesToAnalyze.Any() &&
                        !modulesToAnalyze.Any(m => directory.EndsWith(m)))
                    {
                        continue;
                    }

                    var service = Path.GetFileName(directory);

                    var manifestFiles = Directory.EnumerateFiles(directory, "*.psd1").ToList();

                    if (manifestFiles.Count > 1)
                    {
                        manifestFiles = manifestFiles.Where(f => Path.GetFileName(f).IndexOf(service) >= 0).ToList();
                    }

                    if (manifestFiles.Count == 0)
                    {
                        continue;
                    }

                    var psd1            = manifestFiles.FirstOrDefault();
                    var parentDirectory = Directory.GetParent(psd1).FullName;
                    var psd1FileName    = Path.GetFileName(psd1);

                    string moduleName = psd1FileName.Replace(".psd1", "");

                    Console.WriteLine(directory);
                    Directory.SetCurrentDirectory(directory);

                    issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = moduleName, "AssemblyFileName");
                    processedHelpFiles.Add(moduleName);

                    var newModuleMetadata = MetadataLoader.GetModuleMetadata(moduleName);
                    var fileName          = $"{moduleName}.json";
                    var executingPath     = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).AbsolutePath);

                    var filePath = Path.Combine(executingPath, "SerializedCmdlets", fileName);

                    if (!File.Exists(filePath))
                    {
                        continue;
                    }

                    var oldModuleMetadata = ModuleMetadata.DeserializeCmdlets(filePath);

                    if (cmdletFilter != null)
                    {
                        var output = string.Format("Before filter\nOld module cmdlet count: {0}\nNew module cmdlet count: {1}",
                                                   oldModuleMetadata.Cmdlets.Count, newModuleMetadata.Cmdlets.Count);

                        output += string.Format("\nCmdlet file: {0}", moduleName);

                        oldModuleMetadata.FilterCmdlets(cmdletFilter);
                        newModuleMetadata.FilterCmdlets(cmdletFilter);

                        output += string.Format("After filter\nOld module cmdlet count: {0}\nNew module cmdlet count: {1}",
                                                oldModuleMetadata.Cmdlets.Count, newModuleMetadata.Cmdlets.Count);

                        foreach (var cmdlet in oldModuleMetadata.Cmdlets)
                        {
                            output += string.Format("\n\tOld cmdlet - {0}", cmdlet.Name);
                        }

                        foreach (var cmdlet in newModuleMetadata.Cmdlets)
                        {
                            output += string.Format("\n\tNew cmdlet - {0}", cmdlet.Name);
                        }

                        issueLogger.WriteMessage(output + Environment.NewLine);
                    }

                    RunBreakingChangeChecks(oldModuleMetadata, newModuleMetadata, issueLogger);
                }
            }
        }
Example #5
0
        public void Analyze(IEnumerable <string> cmdletProbingDirs,
                            Func <IEnumerable <string>, IEnumerable <string> > directoryFilter,
                            Func <string, bool> cmdletFilter,
                            IEnumerable <string> modulesToAnalyze)
        {
            var savedDirectory     = Directory.GetCurrentDirectory();
            var processedHelpFiles = new List <string>();
            var issueLogger        = Logger.CreateLogger <SignatureIssue>(_signatureIssueReportLoggerName);

            var probingDirectories = new List <string>();

            if (directoryFilter != null)
            {
                cmdletProbingDirs = directoryFilter(cmdletProbingDirs);
            }

            foreach (var baseDirectory in cmdletProbingDirs.Where(s => !s.Contains("ServiceManagement") &&
                                                                  !ModuleFilter.IsAzureStackModule(s) && Directory.Exists(Path.GetFullPath(s))))
            {
                SharedAssemblyLoader.Load(baseDirectory);

                //Add current directory for probing
                probingDirectories.Add(baseDirectory);
                probingDirectories.AddRange(Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory)));

                foreach (var directory in probingDirectories)
                {
                    if (modulesToAnalyze != null &&
                        modulesToAnalyze.Any() &&
                        !modulesToAnalyze.Any(m => directory.EndsWith(m)))
                    {
                        continue;
                    }

                    var service       = Path.GetFileName(directory);
                    var manifestFiles = Directory.EnumerateFiles(directory, "*.psd1").ToList();
                    if (manifestFiles.Count > 1)
                    {
                        manifestFiles = manifestFiles.Where(f => Path.GetFileName(f).IndexOf(service) >= 0).ToList();
                    }

                    if (!manifestFiles.Any())
                    {
                        continue;
                    }

                    var    psd1            = manifestFiles.FirstOrDefault();
                    var    parentDirectory = Directory.GetParent(psd1).FullName;
                    var    psd1FileName    = Path.GetFileName(psd1);
                    string moduleName      = psd1FileName.Replace(".psd1", "");

                    Directory.SetCurrentDirectory(directory);

                    issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = moduleName, "AssemblyFileName");
                    processedHelpFiles.Add(moduleName);

                    var module = MetadataLoader.GetModuleMetadata(moduleName);
                    CmdletLoader.ModuleMetadata = module;
                    var cmdlets = module.Cmdlets;

                    if (cmdletFilter != null)
                    {
                        cmdlets = cmdlets.Where(cmdlet => cmdletFilter(cmdlet.Name)).ToList();
                    }

                    foreach (var cmdlet in cmdlets)
                    {
                        Logger.WriteMessage("Processing cmdlet '{0}'", cmdlet.ClassName);
                        const string defaultRemediation = "Determine if the cmdlet should implement ShouldProcess and " +
                                                          "if so determine if it should implement Force / ShouldContinue";
                        if (!cmdlet.SupportsShouldProcess && cmdlet.HasForceSwitch)
                        {
                            issueLogger.LogSignatureIssue(
                                cmdlet: cmdlet,
                                severity: 0,
                                problemId: SignatureProblemId.ForceWithoutShouldProcessAttribute,
                                description: string.Format("{0} Has  -Force parameter but does not set the SupportsShouldProcess " +
                                                           "property to true in the Cmdlet attribute.", cmdlet.Name),
                                remediation: defaultRemediation);
                        }
                        if (!cmdlet.SupportsShouldProcess && cmdlet.ConfirmImpact != ConfirmImpact.Medium)
                        {
                            issueLogger.LogSignatureIssue(
                                cmdlet: cmdlet,
                                severity: 2,
                                problemId: SignatureProblemId.ConfirmLeveleWithNoShouldProcess,
                                description:
                                string.Format("{0} Changes the ConfirmImpact but does not set the " +
                                              "SupportsShouldProcess property to true in the cmdlet attribute.",
                                              cmdlet.Name),
                                remediation: defaultRemediation);
                        }
                        if (!cmdlet.SupportsShouldProcess && cmdlet.IsShouldProcessVerb)
                        {
                            issueLogger.LogSignatureIssue(
                                cmdlet: cmdlet,
                                severity: 1,
                                problemId: SignatureProblemId.ActionIndicatesShouldProcess,
                                description:
                                string.Format(
                                    "{0} Does not support ShouldProcess but the cmdlet verb {1} indicates that it should.",
                                    cmdlet.Name, cmdlet.VerbName),
                                remediation: defaultRemediation);
                        }
                        if (cmdlet.ConfirmImpact != ConfirmImpact.Medium)
                        {
                            issueLogger.LogSignatureIssue(
                                cmdlet: cmdlet,
                                severity: 2,
                                problemId: SignatureProblemId.ConfirmLevelChange,
                                description:
                                string.Format("{0} changes the confirm impact.  Please ensure that the " +
                                              "change in ConfirmImpact is justified", cmdlet.Name),
                                remediation:
                                "Verify that ConfirmImpact is changed appropriately by the cmdlet. " +
                                "It is very rare for a cmdlet to change the ConfirmImpact.");
                        }
                        if (!cmdlet.IsApprovedVerb)
                        {
                            issueLogger.LogSignatureIssue(
                                cmdlet: cmdlet,
                                severity: 1,
                                problemId: SignatureProblemId.CmdletWithUnapprovedVerb,
                                description:
                                string.Format(
                                    "{0} uses the verb '{1}', which is not on the list of approved " +
                                    "verbs for PowerShell commands. Use the cmdlet 'Get-Verb' to see " +
                                    "the full list of approved verbs and consider renaming the cmdlet.",
                                    cmdlet.Name, cmdlet.VerbName),
                                remediation: "Consider renaming the cmdlet to use an approved verb for PowerShell.");
                        }

                        if (!cmdlet.HasSingularNoun)
                        {
                            issueLogger.LogSignatureIssue(
                                cmdlet: cmdlet,
                                severity: 1,
                                problemId: SignatureProblemId.CmdletWithPluralNoun,
                                description:
                                string.Format(
                                    "{0} uses the noun '{1}', which does not follow the enforced " +
                                    "naming convention of using a singular noun for a cmdlet name.",
                                    cmdlet.Name, cmdlet.NounName),
                                remediation: "Consider using a singular noun for the cmdlet name.");
                        }

                        if (!cmdlet.OutputTypes.Any())
                        {
                            issueLogger.LogSignatureIssue(
                                cmdlet: cmdlet,
                                severity: 1,
                                problemId: SignatureProblemId.CmdletWithNoOutputType,
                                description:
                                string.Format(
                                    "Cmdlet '{0}' has no defined output type.", cmdlet.Name),
                                remediation: "Add an OutputType attribute that declares the type of the object(s) returned " +
                                "by this cmdlet. If this cmdlet returns no output, please set the output " +
                                "type to 'bool' and make sure to implement the 'PassThru' parameter.");
                        }

                        foreach (var parameter in cmdlet.GetParametersWithPluralNoun())
                        {
                            issueLogger.LogSignatureIssue(
                                cmdlet: cmdlet,
                                severity: 1,
                                problemId: SignatureProblemId.ParameterWithPluralNoun,
                                description:
                                string.Format(
                                    "Parameter {0} of cmdlet {1} does not follow the enforced " +
                                    "naming convention of using a singular noun for a parameter name.",
                                    parameter.Name, cmdlet.Name),
                                remediation: "Consider using a singular noun for the parameter name.");
                        }

                        foreach (var parameterSet in cmdlet.ParameterSets)
                        {
                            if (parameterSet.Name.Contains(" "))
                            {
                                issueLogger.LogSignatureIssue(
                                    cmdlet: cmdlet,
                                    severity: 1,
                                    problemId: SignatureProblemId.ParameterSetWithSpace,
                                    description:
                                    string.Format(
                                        "Parameter set '{0}' of cmdlet '{1}' contains a space, which " +
                                        "is discouraged for PowerShell parameter sets.",
                                        parameterSet.Name, cmdlet.Name),
                                    remediation: "Remove the space(s) in the parameter set name.");
                            }

                            if (parameterSet.Parameters.Any(p => p.Position >= 4))
                            {
                                issueLogger.LogSignatureIssue(
                                    cmdlet: cmdlet,
                                    severity: 1,
                                    problemId: SignatureProblemId.ParameterWithOutOfRangePosition,
                                    description:
                                    string.Format(
                                        "Parameter set '{0}' of cmdlet '{1}' contains at least one parameter " +
                                        "with a position larger than four, which is discouraged.",
                                        parameterSet.Name, cmdlet.Name),
                                    remediation: "Limit the number of positional parameters in a single parameter set to " +
                                    "four or fewer.");
                            }
                        }

                        if (cmdlet.ParameterSets.Count > 2 && cmdlet.DefaultParameterSetName == "__AllParameterSets")
                        {
                            issueLogger.LogSignatureIssue(
                                cmdlet: cmdlet,
                                severity: 1,
                                problemId: SignatureProblemId.MultipleParameterSetsWithNoDefault,
                                description:
                                string.Format(
                                    "Cmdlet '{0}' has multiple parameter sets, but no defined default parameter set.",
                                    cmdlet.Name),
                                remediation: "Define a default parameter set in the cmdlet attribute.");
                        }

                        ValidateParameterSetWithMandatoryEqual(cmdlet, issueLogger);
                        ValidateParameterSetWithLenientMandatoryEqual(cmdlet, issueLogger);
                    }
                    issueLogger.Decorator.Remove("AssemblyFileName");
                    Directory.SetCurrentDirectory(savedDirectory);
                }
            }
        }
        /// <summary>
        /// Given a set of directory paths containing PowerShell module folders,
        /// analyze the breaking changes in the modules and report any issues
        ///
        /// Filters can be added to find breaking changes for specific modules
        /// </summary>
        /// <param name="cmdletProbingDirs">Set of directory paths containing PowerShell module folders to be checked for breaking changes.</param>
        /// <param name="directoryFilter">Function that filters the directory paths to be checked.</param>
        /// <param name="cmdletFilter">Function that filters the cmdlets to be checked.</param>
        public void Analyze(
            IEnumerable <string> cmdletProbingDirs,
            Func <IEnumerable <string>, IEnumerable <string> > directoryFilter,
            Func <string, bool> cmdletFilter,
            IEnumerable <string> modulesToAnalyze)
        {
            var processedHelpFiles = new List <string>();
            var issueLogger        = Logger.CreateLogger <BreakingChangeIssue>("BreakingChangeIssues.csv");

            if (directoryFilter != null)
            {
                cmdletProbingDirs = directoryFilter(cmdletProbingDirs);
            }

            foreach (var baseDirectory in cmdletProbingDirs.Where(s => !s.Contains("ServiceManagement") &&
                                                                  !ModuleFilter.IsAzureStackModule(s) && Directory.Exists(Path.GetFullPath(s))))
            {
                SharedAssemblyLoader.Load(baseDirectory);
                var probingDirectories = new List <string> {
                    baseDirectory
                };

                // Add current directory for probing
                probingDirectories.AddRange(Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory)));

                foreach (var directory in probingDirectories)
                {
                    if (modulesToAnalyze != null &&
                        modulesToAnalyze.Any() &&
                        !modulesToAnalyze.Any(m => directory.EndsWith(m)))
                    {
                        continue;
                    }

                    var service = Path.GetFileName(directory);

                    var manifestFiles = Directory.EnumerateFiles(directory, "*.psd1").ToList();

                    if (manifestFiles.Count > 1)
                    {
                        manifestFiles = manifestFiles.Where(f => Path.GetFileName(f).IndexOf(service) >= 0).ToList();
                    }

                    if (manifestFiles.Count == 0)
                    {
                        continue;
                    }

                    var psd1            = manifestFiles.FirstOrDefault();
                    var parentDirectory = Directory.GetParent(psd1).FullName;
                    var psd1FileName    = Path.GetFileName(psd1);
                    var powershell      = PowerShell.Create();

                    var script = $"Import-LocalizedData -BaseDirectory {parentDirectory} -FileName {psd1FileName} -BindingVariable ModuleMetadata;";
                    powershell.AddScript($"{script} $ModuleMetadata.NestedModules;");
                    var cmdletResult  = powershell.Invoke();
                    var nestedModules = cmdletResult.Where(c => c != null).Select(c => c.ToString()).Select(c => (c.StartsWith(".") ? c.Substring(2) : c)).ToList();

                    powershell.AddScript($"{script} $ModuleMetadata.RequiredModules | % {{ $_[\"ModuleName\"] }};");
                    cmdletResult = powershell.Invoke();
                    var requiredModules = cmdletResult.Where(c => !c.ToString().StartsWith(".")).Select(c => c.ToString()).ToList();

                    if (!nestedModules.Any())
                    {
                        continue;
                    }

                    Directory.SetCurrentDirectory(directory);

                    requiredModules = requiredModules.Join(cmdletProbingDirs,
                                                           module => 1,
                                                           dir => 1,
                                                           (module, dir) => Path.Combine(dir, module))
                                      .Where(Directory.Exists)
                                      .ToList();

                    requiredModules.Add(directory);

                    foreach (var nestedModule in nestedModules)
                    {
                        var assemblyFile     = Directory.GetFiles(parentDirectory, nestedModule, SearchOption.AllDirectories).FirstOrDefault();
                        var assemblyFileName = Path.GetFileName(assemblyFile);
                        if (!File.Exists(assemblyFile))
                        {
                            continue;
                        }

                        issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = assemblyFileName, "AssemblyFileName");
                        processedHelpFiles.Add(assemblyFileName);
// TODO: Remove IfDef
#if NETSTANDARD
                        var proxy = new CmdletLoader();
#else
                        var proxy = EnvironmentHelpers.CreateProxy <CmdletLoader>(directory, out _appDomain);
#endif
                        var newModuleMetadata = proxy.GetModuleMetadata(assemblyFile, requiredModules);

                        var fileName      = assemblyFileName + ".json";
                        var executingPath =
                            Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).AbsolutePath);

                        var filePath = Path.Combine(executingPath, "SerializedCmdlets", fileName);

#if SERIALIZE
                        SerializeCmdlets(filePath, newModuleMetadata);
#endif

                        if (!File.Exists(filePath))
                        {
                            continue;
                        }

                        var oldModuleMetadata = DeserializeCmdlets(filePath);

                        if (cmdletFilter != null)
                        {
                            var output = string.Format("Before filter\nOld module cmdlet count: {0}\nNew module cmdlet count: {1}",
                                                       oldModuleMetadata.Cmdlets.Count, newModuleMetadata.Cmdlets.Count);

                            output += string.Format("\nCmdlet file: {0}", assemblyFileName);

                            oldModuleMetadata.FilterCmdlets(cmdletFilter);
                            newModuleMetadata.FilterCmdlets(cmdletFilter);

                            output += string.Format("After filter\nOld module cmdlet count: {0}\nNew module cmdlet count: {1}",
                                                    oldModuleMetadata.Cmdlets.Count, newModuleMetadata.Cmdlets.Count);

                            foreach (var cmdlet in oldModuleMetadata.Cmdlets)
                            {
                                output += string.Format("\n\tOld cmdlet - {0}", cmdlet.Name);
                            }

                            foreach (var cmdlet in newModuleMetadata.Cmdlets)
                            {
                                output += string.Format("\n\tNew cmdlet - {0}", cmdlet.Name);
                            }

                            issueLogger.WriteMessage(output + Environment.NewLine);
                        }

                        RunBreakingChangeChecks(oldModuleMetadata, newModuleMetadata, issueLogger);
// TODO: Remove IfDef code
#if !NETSTANDARD
                        AppDomain.Unload(_appDomain);
#endif
                    }
                }
            }
        }
Example #7
0
        public void Analyze(IEnumerable <string> cmdletProbingDirs,
                            Func <IEnumerable <string>, IEnumerable <string> > directoryFilter,
                            Func <string, bool> cmdletFilter,
                            IEnumerable <string> modulesToAnalyze)
        {
            var savedDirectory     = Directory.GetCurrentDirectory();
            var processedHelpFiles = new List <string>();
            var issueLogger        = Logger.CreateLogger <SignatureIssue>(_signatureIssueReportLoggerName);

            var probingDirectories = new List <string>();

            if (directoryFilter != null)
            {
                cmdletProbingDirs = directoryFilter(cmdletProbingDirs);
            }

            foreach (var baseDirectory in cmdletProbingDirs.Where(s => !s.Contains("ServiceManagement") &&
                                                                  !ModuleFilter.IsAzureStackModule(s) && Directory.Exists(Path.GetFullPath(s))))
            {
                SharedAssemblyLoader.Load(baseDirectory);

                //Add current directory for probing
                probingDirectories.Add(baseDirectory);
                probingDirectories.AddRange(Directory.EnumerateDirectories(Path.GetFullPath(baseDirectory)));

                foreach (var directory in probingDirectories)
                {
                    if (modulesToAnalyze != null &&
                        modulesToAnalyze.Any() &&
                        !modulesToAnalyze.Any(m => directory.EndsWith(m)))
                    {
                        continue;
                    }

                    var service       = Path.GetFileName(directory);
                    var manifestFiles = Directory.EnumerateFiles(directory, "*.psd1").ToList();
                    if (manifestFiles.Count > 1)
                    {
                        manifestFiles = manifestFiles.Where(f => Path.GetFileName(f).IndexOf(service) >= 0).ToList();
                    }

                    if (!manifestFiles.Any())
                    {
                        continue;
                    }

                    var psd1            = manifestFiles.FirstOrDefault();
                    var parentDirectory = Directory.GetParent(psd1).FullName;
                    var psd1FileName    = Path.GetFileName(psd1);
                    var powershell      = PowerShell.Create();

                    var script = $"Import-LocalizedData -BaseDirectory {parentDirectory} -FileName {psd1FileName} -BindingVariable ModuleMetadata;";
                    powershell.AddScript($"{script} $ModuleMetadata.NestedModules;");
                    var cmdletResult  = powershell.Invoke();
                    var nestedModules = cmdletResult.Where(c => c != null).Select(c => c.ToString()).Select(c => (c.StartsWith(".") ? c.Substring(2) : c)).ToList();

                    powershell.AddScript($"{script} $ModuleMetadata.RequiredModules | % {{ $_[\"ModuleName\"] }};");
                    cmdletResult = powershell.Invoke();
                    var requiredModules = cmdletResult.Where(c => !c.ToString().StartsWith(".")).Select(c => c.ToString()).ToList();

                    if (!nestedModules.Any())
                    {
                        continue;
                    }

                    Directory.SetCurrentDirectory(directory);

                    requiredModules = requiredModules.Join(cmdletProbingDirs,
                                                           module => 1,
                                                           dir => 1,
                                                           (module, dir) => Path.Combine(dir, module))
                                      .Where(Directory.Exists)
                                      .ToList();

                    requiredModules.Add(directory);

                    foreach (var nestedModule in nestedModules)
                    {
                        var assemblyFile = Directory.GetFiles(parentDirectory, nestedModule, SearchOption.AllDirectories).FirstOrDefault();
                        if (!File.Exists(assemblyFile))
                        {
                            continue;
                        }

                        issueLogger.Decorator.AddDecorator(a => a.AssemblyFileName = assemblyFile, "AssemblyFileName");
                        processedHelpFiles.Add(assemblyFile);
// TODO: Remove IfDef
#if NETSTANDARD
                        var proxy = new CmdletLoader();
#else
                        var proxy = EnvironmentHelpers.CreateProxy <CmdletLoader>(directory, out _appDomain);
#endif
                        var module  = proxy.GetModuleMetadata(assemblyFile, requiredModules);
                        var cmdlets = module.Cmdlets;

                        if (cmdletFilter != null)
                        {
                            cmdlets = cmdlets.Where(cmdlet => cmdletFilter(cmdlet.Name)).ToList();
                        }

                        foreach (var cmdlet in cmdlets)
                        {
                            Logger.WriteMessage("Processing cmdlet '{0}'", cmdlet.ClassName);
                            const string defaultRemediation = "Determine if the cmdlet should implement ShouldProcess and " +
                                                              "if so determine if it should implement Force / ShouldContinue";
                            if (!cmdlet.SupportsShouldProcess && cmdlet.HasForceSwitch)
                            {
                                issueLogger.LogSignatureIssue(
                                    cmdlet: cmdlet,
                                    severity: 0,
                                    problemId: SignatureProblemId.ForceWithoutShouldProcessAttribute,
                                    description: string.Format("{0} Has  -Force parameter but does not set the SupportsShouldProcess " +
                                                               "property to true in the Cmdlet attribute.", cmdlet.Name),
                                    remediation: defaultRemediation);
                            }
                            if (!cmdlet.SupportsShouldProcess && cmdlet.ConfirmImpact != ConfirmImpact.Medium)
                            {
                                issueLogger.LogSignatureIssue(
                                    cmdlet: cmdlet,
                                    severity: 2,
                                    problemId: SignatureProblemId.ConfirmLeveleWithNoShouldProcess,
                                    description:
                                    string.Format("{0} Changes the ConfirmImpact but does not set the " +
                                                  "SupportsShouldProcess property to true in the cmdlet attribute.",
                                                  cmdlet.Name),
                                    remediation: defaultRemediation);
                            }
                            if (!cmdlet.SupportsShouldProcess && cmdlet.IsShouldProcessVerb)
                            {
                                issueLogger.LogSignatureIssue(
                                    cmdlet: cmdlet,
                                    severity: 1,
                                    problemId: SignatureProblemId.ActionIndicatesShouldProcess,
                                    description:
                                    string.Format(
                                        "{0} Does not support ShouldProcess but the cmdlet verb {1} indicates that it should.",
                                        cmdlet.Name, cmdlet.VerbName),
                                    remediation: defaultRemediation);
                            }
                            if (cmdlet.ConfirmImpact != ConfirmImpact.Medium)
                            {
                                issueLogger.LogSignatureIssue(
                                    cmdlet: cmdlet,
                                    severity: 2,
                                    problemId: SignatureProblemId.ConfirmLevelChange,
                                    description:
                                    string.Format("{0} changes the confirm impact.  Please ensure that the " +
                                                  "change in ConfirmImpact is justified", cmdlet.Name),
                                    remediation:
                                    "Verify that ConfirmImpact is changed appropriately by the cmdlet. " +
                                    "It is very rare for a cmdlet to change the ConfirmImpact.");
                            }
                            if (!cmdlet.IsApprovedVerb)
                            {
                                issueLogger.LogSignatureIssue(
                                    cmdlet: cmdlet,
                                    severity: 1,
                                    problemId: SignatureProblemId.CmdletWithUnapprovedVerb,
                                    description:
                                    string.Format(
                                        "{0} uses the verb '{1}', which is not on the list of approved " +
                                        "verbs for PowerShell commands. Use the cmdlet 'Get-Verb' to see " +
                                        "the full list of approved verbs and consider renaming the cmdlet.",
                                        cmdlet.Name, cmdlet.VerbName),
                                    remediation: "Consider renaming the cmdlet to use an approved verb for PowerShell.");
                            }

                            if (!cmdlet.HasSingularNoun)
                            {
                                issueLogger.LogSignatureIssue(
                                    cmdlet: cmdlet,
                                    severity: 1,
                                    problemId: SignatureProblemId.CmdletWithPluralNoun,
                                    description:
                                    string.Format(
                                        "{0} uses the noun '{1}', which does not follow the enforced " +
                                        "naming convention of using a singular noun for a cmdlet name.",
                                        cmdlet.Name, cmdlet.NounName),
                                    remediation: "Consider using a singular noun for the cmdlet name.");
                            }

                            if (!cmdlet.OutputTypes.Any())
                            {
                                issueLogger.LogSignatureIssue(
                                    cmdlet: cmdlet,
                                    severity: 1,
                                    problemId: SignatureProblemId.CmdletWithNoOutputType,
                                    description:
                                    string.Format(
                                        "Cmdlet '{0}' has no defined output type.", cmdlet.Name),
                                    remediation: "Add an OutputType attribute that declares the type of the object(s) returned " +
                                    "by this cmdlet. If this cmdlet returns no output, please set the output " +
                                    "type to 'bool' and make sure to implement the 'PassThru' parameter.");
                            }

                            foreach (var parameter in cmdlet.GetParametersWithPluralNoun())
                            {
                                issueLogger.LogSignatureIssue(
                                    cmdlet: cmdlet,
                                    severity: 1,
                                    problemId: SignatureProblemId.ParameterWithPluralNoun,
                                    description:
                                    string.Format(
                                        "Parameter {0} of cmdlet {1} does not follow the enforced " +
                                        "naming convention of using a singular noun for a parameter name.",
                                        parameter.Name, cmdlet.Name),
                                    remediation: "Consider using a singular noun for the parameter name.");
                            }

                            foreach (var parameterSet in cmdlet.ParameterSets)
                            {
                                if (parameterSet.Name.Contains(" "))
                                {
                                    issueLogger.LogSignatureIssue(
                                        cmdlet: cmdlet,
                                        severity: 1,
                                        problemId: SignatureProblemId.ParameterSetWithSpace,
                                        description:
                                        string.Format(
                                            "Parameter set '{0}' of cmdlet '{1}' contains a space, which " +
                                            "is discouraged for PowerShell parameter sets.",
                                            parameterSet.Name, cmdlet.Name),
                                        remediation: "Remove the space(s) in the parameter set name.");
                                }

                                if (parameterSet.Parameters.Any(p => p.Position >= 4))
                                {
                                    issueLogger.LogSignatureIssue(
                                        cmdlet: cmdlet,
                                        severity: 1,
                                        problemId: SignatureProblemId.ParameterWithOutOfRangePosition,
                                        description:
                                        string.Format(
                                            "Parameter set '{0}' of cmdlet '{1}' contains at least one parameter " +
                                            "with a position larger than four, which is discouraged.",
                                            parameterSet.Name, cmdlet.Name),
                                        remediation: "Limit the number of positional parameters in a single parameter set to " +
                                        "four or fewer.");
                                }
                            }

                            if (cmdlet.ParameterSets.Count > 2 && cmdlet.DefaultParameterSetName == "__AllParameterSets")
                            {
                                issueLogger.LogSignatureIssue(
                                    cmdlet: cmdlet,
                                    severity: 1,
                                    problemId: SignatureProblemId.MultipleParameterSetsWithNoDefault,
                                    description:
                                    string.Format(
                                        "Cmdlet '{0}' has multiple parameter sets, but no defined default parameter set.",
                                        cmdlet.Name),
                                    remediation: "Define a default parameter set in the cmdlet attribute.");
                            }

                            //if (cmdlet.DefaultParameterSet.Parameters.Count == 0)
                            //{
                            //    issueLogger.LogSignatureIssue(
                            //        cmdlet: cmdlet,
                            //        severity: 1,
                            //        problemId: SignatureProblemId.EmptyDefaultParameterSet,
                            //        description:
                            //        string.Format(
                            //            "Default parameter set '{0}' of cmdlet '{1}' is empty.",
                            //            cmdlet.DefaultParameterSetName, cmdlet.Name),
                            //        remediation: "Set a non empty parameter set as the default parameter set.");
                            //}

                            ValidateParameterSetWithMandatoryEqual(cmdlet, issueLogger);
                            ValidateParameterSetWithLenientMandatoryEqual(cmdlet, issueLogger);
                        }
// TODO: Remove IfDef code
#if !NETSTANDARD
                        AppDomain.Unload(_appDomain);
#endif
                        issueLogger.Decorator.Remove("AssemblyFileName");
                    }
                    Directory.SetCurrentDirectory(savedDirectory);
                }
            }
        }