public static int Main(string[] args) { // TODO: Take a switch saying whether to use TeamCity logger // TODO: Show extraneous packages, exclusions, etc. // TODO: Get this from the command line var ignoreAssistanceMode = IgnoreAssistanceMode.None; ignoreAssistanceMode = IgnoreAssistanceMode.ShowNew; if (args.Length < 1 || args.Length > 2) { Console.WriteLine(@"USAGE: NuGetSuperBVT.exe c:\path\to\packages [c:\path\to\packages-to-scan.json]"); return(ReturnBadArgs); } var logger = new PackageVerifierLogger(); IDictionary <string, PackageSet> packageSets = null; if (args.Length >= 2) { string packagesToScanJsonFilePath = args[1]; if (!File.Exists(packagesToScanJsonFilePath)) { logger.LogError("Couldn't find packages JSON file at {0}", packagesToScanJsonFilePath); return(ReturnBadArgs); } string packagesToScanJsonFileContent = File.ReadAllText(packagesToScanJsonFilePath); packageSets = JsonConvert.DeserializeObject <IDictionary <string, PackageSet> >(packagesToScanJsonFileContent, new JsonSerializerSettings() { MissingMemberHandling = MissingMemberHandling.Error }); logger.LogInfo("Read {0} package set(s) from {1}", packageSets.Count, packagesToScanJsonFilePath); } var totalTimeStopWatch = Stopwatch.StartNew(); var nupkgsPath = args[0]; // TODO: Look this up using reflection or something var allRules = new IPackageVerifierRule[] { new AssemblyHasDocumentFileRule(), new AssemblyHasVersionAttributesRule(), new AssemblyHasServicingAttributeRule(), new AssemblyStrongNameRule(), new AuthenticodeSigningRule(), new PowerShellScriptIsSignedRule(), new RequiredPackageMetadataRule(), new SatellitePackageRule(), new StrictSemanticVersionValidationRule(), }.ToDictionary(t => t.GetType().Name, t => t); var localPackageRepo = new LocalPackageRepository(nupkgsPath); var numPackagesInRepo = localPackageRepo.GetPackages().Count(); logger.LogInfo("Found {0} packages in {1}", numPackagesInRepo, nupkgsPath); var processedPackages = new HashSet <IPackage>(); var totalErrors = 0; var totalWarnings = 0; var ignoreAssistanceData = new Dictionary <string, IDictionary <string, IDictionary <string, string> > >(StringComparer.OrdinalIgnoreCase); foreach (var packageSet in packageSets) { logger.LogInfo("Processing package set '{0}' with {1} package(s)", packageSet.Key, packageSet.Value.Packages.Count); var packageSetRuleInfo = packageSet.Value.Rules; var packageSetRules = packageSetRuleInfo.Select(ruleId => allRules.Single(rule => string.Equals(rule.Key, ruleId, StringComparison.OrdinalIgnoreCase)).Value); var analyzer = new PackageAnalyzer(); foreach (var ruleInstance in packageSetRules) { analyzer.Rules.Add(ruleInstance); } IList <IssueIgnore> issuesToIgnore = GetIgnoresFromFile(packageSet.Value.Packages); var issueProcessor = new IssueProcessor(issuesToIgnore); foreach (var packageInfo in packageSet.Value.Packages) { var packageId = packageInfo.Key; var packageIgnoreInfo = packageInfo.Value; var packagesWithId = localPackageRepo.FindPackagesById(packageId); if (!packagesWithId.Any()) { logger.LogError("Couldn't find package '{0}' in the repo", packageId); totalErrors++; continue; } if (packagesWithId.Count() > 1) { logger.LogError("Found more than one package with id '{0}' in the repo", packageId); totalErrors++; continue; } var package = packagesWithId.Single(); var packageTimeStopWatch = Stopwatch.StartNew(); logger.LogInfo("Analyzing {0} ({1})", package.Id, package.Version); var issues = analyzer.AnalyzePackage(localPackageRepo, package, logger).ToList(); var packageErrorsAndWarnings = ProcessPackageIssues( ignoreAssistanceMode, logger, issueProcessor, ignoreAssistanceData, package, issues); totalErrors += packageErrorsAndWarnings.Item1; totalWarnings += packageErrorsAndWarnings.Item2; packageTimeStopWatch.Stop(); logger.LogInfo("Took {0}ms", packageTimeStopWatch.ElapsedMilliseconds); Console.WriteLine(); processedPackages.Add(package); } } var unprocessedPackages = localPackageRepo.GetPackages().Except(processedPackages); if (unprocessedPackages.Any()) { logger.LogWarning("Found {0} unprocessed packages. Every package in the repo should be listed in exactly one package set. Running all rules on unlisted packages.", unprocessedPackages.Count()); // For unprocessed packages we run all rules (because we have no idea what exactly to run) var analyzer = new PackageAnalyzer(); foreach (var ruleInstance in allRules.Values) { analyzer.Rules.Add(ruleInstance); } var issueProcessor = new IssueProcessor(issuesToIgnore: null); foreach (var unprocessedPackage in unprocessedPackages) { logger.LogWarning("\tUnprocessed package: {0} ({1})", unprocessedPackage.Id, unprocessedPackage.Version); var packageTimeStopWatch = Stopwatch.StartNew(); logger.LogInfo("Analyzing {0} ({1})", unprocessedPackage.Id, unprocessedPackage.Version); var issues = analyzer.AnalyzePackage(localPackageRepo, unprocessedPackage, logger).ToList(); var packageErrorsAndWarnings = ProcessPackageIssues( ignoreAssistanceMode, logger, issueProcessor, ignoreAssistanceData, unprocessedPackage, issues); totalErrors += packageErrorsAndWarnings.Item1; totalWarnings += packageErrorsAndWarnings.Item2; packageTimeStopWatch.Stop(); logger.LogInfo("Took {0}ms", packageTimeStopWatch.ElapsedMilliseconds); Console.WriteLine(); } } if (ignoreAssistanceMode != IgnoreAssistanceMode.None) { Console.WriteLine("Showing JSON for ignore content:"); Console.WriteLine(JsonConvert.SerializeObject(ignoreAssistanceData, Formatting.Indented)); Console.WriteLine(); } LogLevel errorLevel = LogLevel.Info; if (totalWarnings > 0) { errorLevel = LogLevel.Warning; } if (totalErrors > 0) { errorLevel = LogLevel.Error; } logger.Log( errorLevel, "SUMMARY: {0} error(s) and {1} warning(s) found", totalErrors, totalWarnings); totalTimeStopWatch.Stop(); logger.LogInfo("Total took {0}ms", totalTimeStopWatch.ElapsedMilliseconds); Console.ReadLine(); return((totalErrors + totalWarnings > 0) ? ReturnErrorsOrWarnings : ReturnOk); }
private static Tuple <int, int> ProcessPackageIssues( IgnoreAssistanceMode ignoreAssistanceMode, PackageVerifierLogger logger, IssueProcessor issueProcessor, Dictionary <string, IDictionary <string, IDictionary <string, string> > > ignoreAssistanceData, IPackage package, List <PackageVerifierIssue> issues) { var issuesToReport = issues.Select(issue => issueProcessor.GetIssueReport(issue, package)).ToList(); if (issuesToReport.Count > 0) { var infos = issuesToReport.Where(issueReport => issueReport.IssueLevel == LogLevel.Info).ToList(); var warnings = issuesToReport.Where(issueReport => issueReport.IssueLevel == LogLevel.Warning).ToList(); var errors = issuesToReport.Where(issueReport => issueReport.IssueLevel == LogLevel.Error).ToList(); LogLevel errorLevel = LogLevel.Info; if (warnings.Count > 0) { errorLevel = LogLevel.Warning; } if (errors.Count > 0) { errorLevel = LogLevel.Error; } logger.Log( errorLevel, "{0} error(s) and {1} warning(s) found with package {2} ({3})", errors.Count, warnings.Count, package.Id, package.Version); foreach (var issueToReport in issuesToReport) { // If requested, track ignores to assist if (ignoreAssistanceMode == IgnoreAssistanceMode.ShowAll || (ignoreAssistanceMode == IgnoreAssistanceMode.ShowNew && issueToReport.IgnoreJustification == null)) { IDictionary <string, IDictionary <string, string> > packageIgnoreInfo; if (!ignoreAssistanceData.TryGetValue(package.Id, out packageIgnoreInfo)) { packageIgnoreInfo = new Dictionary <string, IDictionary <string, string> >(StringComparer.OrdinalIgnoreCase); ignoreAssistanceData.Add(package.Id, packageIgnoreInfo); } IDictionary <string, string> packageRuleInfo; if (!packageIgnoreInfo.TryGetValue(issueToReport.PackageIssue.IssueId, out packageRuleInfo)) { packageRuleInfo = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); packageIgnoreInfo.Add(issueToReport.PackageIssue.IssueId, packageRuleInfo); } if (packageRuleInfo.ContainsKey(issueToReport.PackageIssue.Instance ?? "*")) { Console.WriteLine("ALERT!!!!!!!!!!!!! Already added key {0}", issueToReport.PackageIssue.Instance); } else { packageRuleInfo.Add(issueToReport.PackageIssue.Instance ?? "*", issueToReport.IgnoreJustification ?? "Enter justification"); } } PrintPackageIssue(logger, issueToReport); } return(new Tuple <int, int>(errors.Count, warnings.Count)); } else { logger.LogInfo("No issues found with package {0} ({1})", package.Id, package.Version); return(new Tuple <int, int>(0, 0)); } }
private static Tuple<int, int> ProcessPackageIssues( IgnoreAssistanceMode ignoreAssistanceMode, PackageVerifierLogger logger, IssueProcessor issueProcessor, Dictionary<string, IDictionary<string, IDictionary<string, string>>> ignoreAssistanceData, IPackage package, List<PackageVerifierIssue> issues) { var issuesToReport = issues.Select(issue => issueProcessor.GetIssueReport(issue, package)).ToList(); if (issuesToReport.Count > 0) { var infos = issuesToReport.Where(issueReport => issueReport.IssueLevel == LogLevel.Info).ToList(); var warnings = issuesToReport.Where(issueReport => issueReport.IssueLevel == LogLevel.Warning).ToList(); var errors = issuesToReport.Where(issueReport => issueReport.IssueLevel == LogLevel.Error).ToList(); LogLevel errorLevel = LogLevel.Info; if (warnings.Count > 0) { errorLevel = LogLevel.Warning; } if (errors.Count > 0) { errorLevel = LogLevel.Error; } logger.Log( errorLevel, "{0} error(s) and {1} warning(s) found with package {2} ({3})", errors.Count, warnings.Count, package.Id, package.Version); foreach (var issueToReport in issuesToReport) { // If requested, track ignores to assist if (ignoreAssistanceMode == IgnoreAssistanceMode.ShowAll || (ignoreAssistanceMode == IgnoreAssistanceMode.ShowNew && issueToReport.IgnoreJustification == null)) { IDictionary<string, IDictionary<string, string>> packageIgnoreInfo; if (!ignoreAssistanceData.TryGetValue(package.Id, out packageIgnoreInfo)) { packageIgnoreInfo = new Dictionary<string, IDictionary<string, string>>(StringComparer.OrdinalIgnoreCase); ignoreAssistanceData.Add(package.Id, packageIgnoreInfo); } IDictionary<string, string> packageRuleInfo; if (!packageIgnoreInfo.TryGetValue(issueToReport.PackageIssue.IssueId, out packageRuleInfo)) { packageRuleInfo = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); packageIgnoreInfo.Add(issueToReport.PackageIssue.IssueId, packageRuleInfo); } if (packageRuleInfo.ContainsKey(issueToReport.PackageIssue.Instance ?? "*")) { Console.WriteLine("ALERT!!!!!!!!!!!!! Already added key {0}", issueToReport.PackageIssue.Instance); } else { packageRuleInfo.Add(issueToReport.PackageIssue.Instance ?? "*", issueToReport.IgnoreJustification ?? "Enter justification"); } } PrintPackageIssue(logger, issueToReport); } return new Tuple<int, int>(errors.Count, warnings.Count); } else { logger.LogInfo("No issues found with package {0} ({1})", package.Id, package.Version); return new Tuple<int, int>(0, 0); } }
public static int Main(string[] args) { // TODO: Take a switch saying whether to use TeamCity logger // TODO: Show extraneous packages, exclusions, etc. // TODO: Get this from the command line var ignoreAssistanceMode = IgnoreAssistanceMode.None; ignoreAssistanceMode = IgnoreAssistanceMode.ShowNew; if (args.Length < 1 || args.Length > 2) { Console.WriteLine(@"USAGE: NuGetSuperBVT.exe c:\path\to\packages [c:\path\to\packages-to-scan.json]"); return ReturnBadArgs; } var logger = new PackageVerifierLogger(); IDictionary<string, PackageSet> packageSets = null; if (args.Length >= 2) { string packagesToScanJsonFilePath = args[1]; if (!File.Exists(packagesToScanJsonFilePath)) { logger.LogError("Couldn't find packages JSON file at {0}", packagesToScanJsonFilePath); return ReturnBadArgs; } string packagesToScanJsonFileContent = File.ReadAllText(packagesToScanJsonFilePath); packageSets = JsonConvert.DeserializeObject<IDictionary<string, PackageSet>>(packagesToScanJsonFileContent, new JsonSerializerSettings() { MissingMemberHandling = MissingMemberHandling.Error }); logger.LogInfo("Read {0} package set(s) from {1}", packageSets.Count, packagesToScanJsonFilePath); } var totalTimeStopWatch = Stopwatch.StartNew(); var nupkgsPath = args[0]; // TODO: Look this up using reflection or something var allRules = new IPackageVerifierRule[] { new AssemblyHasDocumentFileRule(), new AssemblyHasVersionAttributesRule(), new AssemblyHasServicingAttributeRule(), new AssemblyStrongNameRule(), new AuthenticodeSigningRule(), new PowerShellScriptIsSignedRule(), new RequiredPackageMetadataRule(), new SatellitePackageRule(), new StrictSemanticVersionValidationRule(), }.ToDictionary(t => t.GetType().Name, t => t); var localPackageRepo = new LocalPackageRepository(nupkgsPath); var numPackagesInRepo = localPackageRepo.GetPackages().Count(); logger.LogInfo("Found {0} packages in {1}", numPackagesInRepo, nupkgsPath); var processedPackages = new HashSet<IPackage>(); var totalErrors = 0; var totalWarnings = 0; var ignoreAssistanceData = new Dictionary<string, IDictionary<string, IDictionary<string, string>>>(StringComparer.OrdinalIgnoreCase); foreach (var packageSet in packageSets) { logger.LogInfo("Processing package set '{0}' with {1} package(s)", packageSet.Key, packageSet.Value.Packages.Count); var packageSetRuleInfo = packageSet.Value.Rules; var packageSetRules = packageSetRuleInfo.Select(ruleId => allRules.Single(rule => string.Equals(rule.Key, ruleId, StringComparison.OrdinalIgnoreCase)).Value); var analyzer = new PackageAnalyzer(); foreach (var ruleInstance in packageSetRules) { analyzer.Rules.Add(ruleInstance); } IList<IssueIgnore> issuesToIgnore = GetIgnoresFromFile(packageSet.Value.Packages); var issueProcessor = new IssueProcessor(issuesToIgnore); foreach (var packageInfo in packageSet.Value.Packages) { var packageId = packageInfo.Key; var packageIgnoreInfo = packageInfo.Value; var packagesWithId = localPackageRepo.FindPackagesById(packageId); if (!packagesWithId.Any()) { logger.LogError("Couldn't find package '{0}' in the repo", packageId); totalErrors++; continue; } if (packagesWithId.Count() > 1) { logger.LogError("Found more than one package with id '{0}' in the repo", packageId); totalErrors++; continue; } var package = packagesWithId.Single(); var packageTimeStopWatch = Stopwatch.StartNew(); logger.LogInfo("Analyzing {0} ({1})", package.Id, package.Version); var issues = analyzer.AnalyzePackage(localPackageRepo, package, logger).ToList(); var packageErrorsAndWarnings = ProcessPackageIssues( ignoreAssistanceMode, logger, issueProcessor, ignoreAssistanceData, package, issues); totalErrors += packageErrorsAndWarnings.Item1; totalWarnings += packageErrorsAndWarnings.Item2; packageTimeStopWatch.Stop(); logger.LogInfo("Took {0}ms", packageTimeStopWatch.ElapsedMilliseconds); Console.WriteLine(); processedPackages.Add(package); } } var unprocessedPackages = localPackageRepo.GetPackages().Except(processedPackages); if (unprocessedPackages.Any()) { logger.LogWarning("Found {0} unprocessed packages. Every package in the repo should be listed in exactly one package set. Running all rules on unlisted packages.", unprocessedPackages.Count()); // For unprocessed packages we run all rules (because we have no idea what exactly to run) var analyzer = new PackageAnalyzer(); foreach (var ruleInstance in allRules.Values) { analyzer.Rules.Add(ruleInstance); } var issueProcessor = new IssueProcessor(issuesToIgnore: null); foreach (var unprocessedPackage in unprocessedPackages) { logger.LogWarning("\tUnprocessed package: {0} ({1})", unprocessedPackage.Id, unprocessedPackage.Version); var packageTimeStopWatch = Stopwatch.StartNew(); logger.LogInfo("Analyzing {0} ({1})", unprocessedPackage.Id, unprocessedPackage.Version); var issues = analyzer.AnalyzePackage(localPackageRepo, unprocessedPackage, logger).ToList(); var packageErrorsAndWarnings = ProcessPackageIssues( ignoreAssistanceMode, logger, issueProcessor, ignoreAssistanceData, unprocessedPackage, issues); totalErrors += packageErrorsAndWarnings.Item1; totalWarnings += packageErrorsAndWarnings.Item2; packageTimeStopWatch.Stop(); logger.LogInfo("Took {0}ms", packageTimeStopWatch.ElapsedMilliseconds); Console.WriteLine(); } } if (ignoreAssistanceMode != IgnoreAssistanceMode.None) { Console.WriteLine("Showing JSON for ignore content:"); Console.WriteLine(JsonConvert.SerializeObject(ignoreAssistanceData, Formatting.Indented)); Console.WriteLine(); } LogLevel errorLevel = LogLevel.Info; if (totalWarnings > 0) { errorLevel = LogLevel.Warning; } if (totalErrors > 0) { errorLevel = LogLevel.Error; } logger.Log( errorLevel, "SUMMARY: {0} error(s) and {1} warning(s) found", totalErrors, totalWarnings); totalTimeStopWatch.Stop(); logger.LogInfo("Total took {0}ms", totalTimeStopWatch.ElapsedMilliseconds); Console.ReadLine(); return (totalErrors + totalWarnings > 0) ? ReturnErrorsOrWarnings : ReturnOk; }
private static int Execute( IDictionary <string, PackageSet> packageSets, IEnumerable <FileInfo> nupkgs, List <string> excludedRuleNames, IPackageVerifierLogger logger, IgnoreAssistanceMode ignoreAssistanceMode) { var allRules = typeof(Program).Assembly.GetTypes() .Where(t => typeof(IPackageVerifierRule).IsAssignableFrom(t) && !t.IsAbstract) .ToDictionary( t => t.Name, t => { var rule = (IPackageVerifierRule)Activator.CreateInstance(t); if (rule is CompositeRule compositeRule) { return(compositeRule.GetRules()); } else { return(new[] { rule }); } }); var packages = nupkgs.ToDictionary(file => { var reader = new PackageArchiveReader(file.FullName); return((IPackageMetadata) new PackageBuilder(reader.GetNuspec(), basePath: null)); }); var processedPackages = new HashSet <IPackageMetadata>(); var totalErrors = 0; var totalWarnings = 0; var ignoreAssistanceData = new Dictionary <string, PackageVerifierOptions>( StringComparer.OrdinalIgnoreCase); PackageSet defaultPackageSet = null; IEnumerable <IPackageVerifierRule> defaultRuleSet = null; IEnumerable <IssueIgnore> defaultIssuesToIgnore = null; foreach (var packageSet in packageSets) { logger.LogInfo( "Processing package set '{0}' with {1} package(s)", packageSet.Key, packageSet.Value.Packages?.Count ?? 0); var packageSetRuleInfo = packageSet.Value.Rules; var packageSetRules = packageSetRuleInfo.SelectMany( ruleId => allRules.SingleOrDefault( rule => string.Equals(rule.Key, ruleId, StringComparison.OrdinalIgnoreCase)).Value) .Where(rule => !excludedRuleNames.Contains(rule.GetType().Name)); if (string.Equals(packageSet.Key, "Default", StringComparison.OrdinalIgnoreCase)) { defaultPackageSet = packageSet.Value; defaultRuleSet = packageSetRules; defaultIssuesToIgnore = GetIgnoresFromFile(packageSet.Value.Packages); continue; } var analyzer = new PackageAnalyzer(); foreach (var ruleInstance in packageSetRules) { analyzer.Rules.Add(ruleInstance); } var issuesToIgnore = GetIgnoresFromFile(packageSet.Value.Packages); var issueProcessor = new IssueProcessor(issuesToIgnore); if (packageSet.Value.Packages != null) { foreach (var packageInfo in packageSet.Value.Packages) { var packageId = packageInfo.Key; var packagesWithId = packages.Where(p => p.Key.Id.Equals(packageId)); if (!packagesWithId.Any()) { logger.LogError("Couldn't find package '{0}' in the repo", packageId); totalErrors++; continue; } foreach (var packagePair in packagesWithId) { var package = packagePair.Key; logger.LogInfo("Analyzing {0} ({1})", package.Id, package.Version); List <PackageVerifierIssue> issues; using (var context = new PackageAnalysisContext { PackageFileInfo = packagePair.Value, Metadata = package, Logger = logger, Options = packageInfo.Value, }) { issues = analyzer.AnalyzePackage(context).ToList(); } var packageErrorsAndWarnings = ProcessPackageIssues( ignoreAssistanceMode, logger, issueProcessor, ignoreAssistanceData, package, issues); totalErrors += packageErrorsAndWarnings.Item1; totalWarnings += packageErrorsAndWarnings.Item2; processedPackages.Add(package); } } } foreach (var issue in issueProcessor.RemainingIssuesToIgnore) { // TODO: Don't show this for rules that we don't run. logger.LogWarning( "Unnecessary exclusion in {0}{3}Issue: {1}{3}Instance: {2}{3}", issue.PackageId, issue.IssueId, issue.Instance, Environment.NewLine); } } var unlistedPackages = packages.Keys.Except(processedPackages); if (unlistedPackages.Any()) { // For unlisted packages we run the rules from 'Default' package set if present // or we run all rules (because we have no idea what exactly to run) var analyzer = new PackageAnalyzer(); var unlistedPackageRules = defaultRuleSet ?? allRules.Values.SelectMany(f => f).Where(r => !excludedRuleNames.Contains(r.GetType().Name)); foreach (var ruleInstance in unlistedPackageRules) { analyzer.Rules.Add(ruleInstance); } var issueProcessor = new IssueProcessor(issuesToIgnore: defaultIssuesToIgnore); foreach (var unlistedPackage in unlistedPackages) { logger.LogInfo("Analyzing {0} ({1})", unlistedPackage.Id, unlistedPackage.Version); List <PackageVerifierIssue> issues; PackageVerifierOptions packageOptions = null; defaultPackageSet?.Packages?.TryGetValue(unlistedPackage.Id, out packageOptions); using (var context = new PackageAnalysisContext { PackageFileInfo = packages[unlistedPackage], Metadata = unlistedPackage, Logger = logger, Options = packageOptions, }) { issues = analyzer.AnalyzePackage(context).ToList(); } var packageErrorsAndWarnings = ProcessPackageIssues( ignoreAssistanceMode, logger, issueProcessor, ignoreAssistanceData, unlistedPackage, issues); totalErrors += packageErrorsAndWarnings.Item1; totalWarnings += packageErrorsAndWarnings.Item2; } foreach (var issue in issueProcessor.RemainingIssuesToIgnore) { // TODO: Don't show this for rules that we don't run. logger.LogWarning( "Unnecessary exclusion in {0}{3}Issue: {1}{3}Instance: {2}{3}", issue.PackageId, issue.IssueId, issue.Instance, Environment.NewLine); } } if (ignoreAssistanceMode != IgnoreAssistanceMode.None && ignoreAssistanceData.Any()) { Console.WriteLine("Showing JSON for ignore content:"); Console.WriteLine(JsonConvert.SerializeObject(ignoreAssistanceData, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); Console.WriteLine(); } var errorLevel = LogLevel.Normal; if (totalWarnings > 0) { errorLevel = LogLevel.Warning; } if (totalErrors > 0) { errorLevel = LogLevel.Error; } logger.Log( errorLevel, "SUMMARY: {0} error(s) and {1} warning(s) found", totalErrors, totalWarnings); return((totalErrors + totalWarnings > 0) ? ReturnErrorsOrWarnings : ReturnOk); }