diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs
index 7f7a76a41..de6306f05 100644
--- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs
+++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs
@@ -243,7 +243,19 @@ public SwitchParameter AttachAndDebug
set { attachAndDebug = value; }
}
private bool attachAndDebug = false;
+
#endif
+ ///
+ /// Write a summary of rule violations to the host, which might be undesirable in some cases, therefore this switch is optional.
+ ///
+ [Parameter(Mandatory = false)]
+ public SwitchParameter ReportSummary
+ {
+ get { return reportSummary; }
+ set { reportSummary = value; }
+ }
+ private SwitchParameter reportSummary;
+
#endregion Parameters
#region Overrides
@@ -424,9 +436,49 @@ private void WriteToOutput(IEnumerable diagnosticRecords)
{
foreach (ILogger logger in ScriptAnalyzer.Instance.Loggers)
{
+ var errorCount = 0;
+ var warningCount = 0;
+ var infoCount = 0;
+
foreach (DiagnosticRecord diagnostic in diagnosticRecords)
{
logger.LogObject(diagnostic, this);
+ switch (diagnostic.Severity)
+ {
+ case DiagnosticSeverity.Information:
+ infoCount++;
+ break;
+ case DiagnosticSeverity.Warning:
+ warningCount++;
+ break;
+ case DiagnosticSeverity.Error:
+ errorCount++;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(diagnostic.Severity), $"Severity '{diagnostic.Severity}' is unknown");
+ }
+ }
+
+ if (ReportSummary.IsPresent)
+ {
+ var numberOfRuleViolations = infoCount + warningCount + errorCount;
+ if (numberOfRuleViolations == 0)
+ {
+ Host.UI.WriteLine("0 rule violations found.");
+ }
+ else
+ {
+ var pluralS = numberOfRuleViolations > 1 ? "s" : string.Empty;
+ var message = $"{numberOfRuleViolations} rule violation{pluralS} found. Severity distribution: {DiagnosticSeverity.Error} = {errorCount}, {DiagnosticSeverity.Warning} = {warningCount}, {DiagnosticSeverity.Information} = {infoCount}";
+ if (warningCount + errorCount == 0)
+ {
+ ConsoleHostHelper.DisplayMessageUsingSystemProperties(Host, "WarningForegroundColor", "WarningBackgroundColor", message);
+ }
+ else
+ {
+ ConsoleHostHelper.DisplayMessageUsingSystemProperties(Host, "ErrorForegroundColor", "ErrorBackgroundColor", message);
+ }
+ }
}
}
diff --git a/Engine/Generic/ConsoleHostHelper.cs b/Engine/Generic/ConsoleHostHelper.cs
new file mode 100644
index 000000000..961962fd2
--- /dev/null
+++ b/Engine/Generic/ConsoleHostHelper.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Management.Automation.Host;
+
+namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic
+{
+ internal static class ConsoleHostHelper
+ {
+ internal static void DisplayMessageUsingSystemProperties(PSHost psHost, string foregroundColorPropertyName, string backgroundPropertyName, string message)
+ {
+ var gotForegroundColor = TryGetPrivateDataConsoleColor(psHost, foregroundColorPropertyName, out ConsoleColor foregroundColor);
+ var gotBackgroundColor = TryGetPrivateDataConsoleColor(psHost, backgroundPropertyName, out ConsoleColor backgroundColor);
+ if (gotForegroundColor && gotBackgroundColor)
+ {
+ psHost.UI.WriteLine(foregroundColor: foregroundColor, backgroundColor: backgroundColor, value: message);
+ }
+ else
+ {
+ psHost.UI.WriteLine(message);
+ }
+ }
+
+ private static bool TryGetPrivateDataConsoleColor(PSHost psHost, string propertyName, out ConsoleColor consoleColor)
+ {
+ consoleColor = default(ConsoleColor);
+ var property = psHost.PrivateData.Properties[propertyName];
+ if (property == null)
+ {
+ return false;
+ }
+
+ try
+ {
+ consoleColor = (ConsoleColor)Enum.Parse(typeof(ConsoleColor), property.Value.ToString(), true);
+ }
+ catch (InvalidCastException)
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1
index 2b03bc5e1..bec1134ce 100644
--- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1
+++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1
@@ -504,4 +504,16 @@ Describe "Test -EnableExit Switch" {
powershell -Command 'Import-Module PSScriptAnalyzer; Invoke-ScriptAnalyzer -ScriptDefinition gci -EnableExit'
$LASTEXITCODE | Should -Be 1
}
+
+ Describe "-ReportSummary switch" {
+ $reportSummaryFor1Warning = '*1 rule violation found. Severity distribution: Error = 0, Warning = 1, Information = 0*'
+ It "prints the correct report summary using the -NoReportSummary switch" {
+ $result = powershell -command 'Invoke-Scriptanalyzer -ScriptDefinition gci -ReportSummary'
+ "$result" | Should -BeLike $reportSummaryFor1Warning
+ }
+ It "does not print the report summary when not using -NoReportSummary switch" {
+ $result = powershell -command 'Invoke-Scriptanalyzer -ScriptDefinition gci'
+ "$result" | Should -Not -BeLike $reportSummaryFor1Warning
+ }
+ }
}
diff --git a/Tests/Engine/LibraryUsage.tests.ps1 b/Tests/Engine/LibraryUsage.tests.ps1
index 3bf7890bd..68f3b68ee 100644
--- a/Tests/Engine/LibraryUsage.tests.ps1
+++ b/Tests/Engine/LibraryUsage.tests.ps1
@@ -52,10 +52,13 @@ function Invoke-ScriptAnalyzer {
[Parameter(Mandatory = $false)]
[switch] $Fix,
+
+ [Parameter(Mandatory = $false)]
+ [switch] $EnableExit,
[Parameter(Mandatory = $false)]
- [switch] $EnableExit
- )
+ [switch] $ReportSummary
+ )
if ($null -eq $CustomRulePath)
{
@@ -98,6 +101,19 @@ function Invoke-ScriptAnalyzer {
}
$results
+
+ if ($ReportSummary.IsPresent)
+ {
+ if ($null -ne $results)
+ {
+ # This is not the exact message that it would print but close enough
+ Write-Host "$($results.Count) rule violations found. Severity distribution: Error = 1, Warning = 3, Information = 5" -ForegroundColor Red
+ }
+ else
+ {
+ Write-Host '0 rule violations found.' -ForegroundColor Green
+ }
+ }
if ($EnableExit.IsPresent -and $null -ne $results)
{
diff --git a/docs/markdown/Invoke-ScriptAnalyzer.md b/docs/markdown/Invoke-ScriptAnalyzer.md
index c742babe8..85952aafe 100644
--- a/docs/markdown/Invoke-ScriptAnalyzer.md
+++ b/docs/markdown/Invoke-ScriptAnalyzer.md
@@ -12,14 +12,14 @@ Evaluates a script or module based on selected best practice rules
### UNNAMED_PARAMETER_SET_1
```
Invoke-ScriptAnalyzer [-Path] [-CustomRulePath ] [-RecurseCustomRulePath]
- [-ExcludeRule ] [-IncludeRule ] [-Severity ] [-Recurse] [-SuppressedOnly] [-Fix] [-EnableExit]
+ [-ExcludeRule ] [-IncludeRule ] [-Severity ] [-Recurse] [-SuppressedOnly] [-Fix] [-EnableExit] [-ReportSummary]
[-Settings ]
```
### UNNAMED_PARAMETER_SET_2
```
Invoke-ScriptAnalyzer [-ScriptDefinition] [-CustomRulePath ] [-RecurseCustomRulePath]
- [-ExcludeRule ] [-IncludeRule ] [-Severity ] [-Recurse] [-SuppressedOnly] [-EnableExit]
+ [-ExcludeRule ] [-IncludeRule ] [-Severity ] [-Recurse] [-SuppressedOnly] [-EnableExit] [-ReportSummary]
[-Settings ]
```
@@ -432,6 +432,21 @@ Accept pipeline input: False
Accept wildcard characters: False
```
+### -ReportSummary
+Writes a report summary of the found warnings to the host.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: False
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
### -Settings
File path that contains user profile or hash table for ScriptAnalyzer