diff --git a/README.md b/README.md index bc6e700bdc..b3447b6b8c 100644 --- a/README.md +++ b/README.md @@ -331,6 +331,7 @@ The following conceptual topics exist in the `PSRule` module: - [Logging.RuleFail](docs/concepts/PSRule/en-US/about_PSRule_Options.md#loggingrulefail) - [Logging.RulePass](docs/concepts/PSRule/en-US/about_PSRule_Options.md#loggingrulepass) - [Output.As](docs/concepts/PSRule/en-US/about_PSRule_Options.md#outputas) + - [Output.Banner](docs/concepts/PSRule/en-US/about_PSRule_Options.md#outputbanner) - [Output.Culture](docs/concepts/PSRule/en-US/about_PSRule_Options.md#outputculture) - [Output.Encoding](docs/concepts/PSRule/en-US/about_PSRule_Options.md#outputencoding) - [Output.Format](docs/concepts/PSRule/en-US/about_PSRule_Options.md#outputformat) diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md index 9898e33ceb..d6e1062d64 100644 --- a/docs/CHANGELOG-v1.md +++ b/docs/CHANGELOG-v1.md @@ -10,6 +10,11 @@ See [upgrade notes][upgrade-notes] for helpful information when upgrading from p ## Unreleased +What's changed since v1.3.0: + +- General improvements: + - PSRule banner can be configured in output when using `Assert-PSRule`. [#708](https://github.com/microsoft/PSRule/issues/708) + ## v1.3.0 What's changed since v1.2.0: diff --git a/docs/concepts/PSRule/en-US/about_PSRule_Options.md b/docs/concepts/PSRule/en-US/about_PSRule_Options.md index bfafb943fc..809aa1ab16 100644 --- a/docs/concepts/PSRule/en-US/about_PSRule_Options.md +++ b/docs/concepts/PSRule/en-US/about_PSRule_Options.md @@ -27,6 +27,7 @@ The following workspace options are available for use: - [Logging.RuleFail](#loggingrulefail) - [Logging.RulePass](#loggingrulepass) - [Output.As](#outputas) +- [Output.Banner](#outputbanner) - [Output.Culture](#outputculture) - [Output.Encoding](#outputencoding) - [Output.Format](#outputformat) @@ -1305,6 +1306,68 @@ variables: value: Summary ``` +### Output.Banner + +The information displayed for PSRule banner. +This option is only applicable when using `Assert-PSRule` cmdlet. + +The following information can be shown or hidden by configuring this option. + +- `Title` (1) - Shows the PSRule title ASCII text. +- `Source` (2) - Shows rules module versions used in this run. +- `SupportLinks` (4) - Shows supporting links for PSRule and rules modules. + +Additionally the following rollup options exist: + +- `Default` - Shows `Title`, `Source`, and `SupportLinks`. +This is the default option. +- `Minimal` - Shows `Source`. + +This option can be configured using one of the named values described above. +Alternatively, this value can be configured by specifying a bit mask as an integer. +For example `6` would show `Source`, and `SupportLinks`. + +This option can be specified using: + +```powershell +# PowerShell: Using the OutputBanner parameter +$option = New-PSRuleOption -OutputBanner Minimal; +``` + +```powershell +# PowerShell: Using the Output.Banner hashtable key +$option = New-PSRuleOption -Option @{ 'Output.Banner' = 'Minimal' }; +``` + +```powershell +# PowerShell: Using the OutputBanner parameter to set YAML +Set-PSRuleOption -OutputBanner Minimal; +``` + +```yaml +# YAML: Using the output/banner property +output: + banner: OutputBanner +``` + +```bash +# Bash: Using environment variable +export PSRULE_OUTPUT_BANNER=Minimal +``` + +```yaml +# GitHub Actions: Using environment variable +env: + PSRULE_OUTPUT_BANNER: Minimal +``` + +```yaml +# Azure Pipelines: Using environment variable +variables: +- name: PSRULE_OUTPUT_BANNER + value: Minimal +``` + ### Output.Culture Specified the name of one or more cultures to use for generating output. @@ -1907,6 +1970,7 @@ logging: output: as: Summary + banner: Minimal culture: - en-US encoding: UTF8 @@ -1993,6 +2057,7 @@ logging: output: as: Detail + banner: Default culture: [ ] encoding: Default format: None diff --git a/schemas/PSRule-options.schema.json b/schemas/PSRule-options.schema.json index 26d9ae552a..97d8044c8c 100644 --- a/schemas/PSRule-options.schema.json +++ b/schemas/PSRule-options.schema.json @@ -312,6 +312,28 @@ ], "default": "Detail" }, + "banner": { + "title": "Banner format", + "description": "The information displayed for Assert-PSRule banner. The default is Default which includes Title, Source, and SupportLinks.", + "markdownDescription": "The information displayed for Assert-PSRule banner. The default is `Default` which includes `Title`, `Source`, and `SupportLinks`. [See help](https://microsoft.github.io/PSRule/concepts/PSRule/en-US/about_PSRule_Options.html#outputbanner)", + "oneOf": [ + { + "type": "string", + "enum": [ + "None", + "Title", + "Source", + "SupportLinks", + "Default", + "Minimal" + ] + }, + { + "type": "integer" + } + ], + "default": "Default" + }, "culture": { "type": "array", "title": "Culture", diff --git a/src/PSRule/Configuration/BannerFormat.cs b/src/PSRule/Configuration/BannerFormat.cs new file mode 100644 index 0000000000..140e2366fe --- /dev/null +++ b/src/PSRule/Configuration/BannerFormat.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; + +namespace PSRule.Configuration +{ + /// + /// The information displayed for Assert-PSRule banner. + /// + [Flags] + [JsonConverter(typeof(StringEnumConverter))] + public enum BannerFormat + { + /// + /// No banner is shown. + /// + None = 0, + + /// + /// The PSRule title ASCII text is shown. + /// + Title = 1, + + /// + /// The rules module versions used in this run are shown. + /// + Source = 2, + + /// + /// Supporting links for PSRule and rules modules are shown. + /// + SupportLinks = 4, + + Default = Title | Source | SupportLinks, + Minimal = Source + } +} diff --git a/src/PSRule/Configuration/OutputOption.cs b/src/PSRule/Configuration/OutputOption.cs index 6d62c1b57f..09ebe85fa0 100644 --- a/src/PSRule/Configuration/OutputOption.cs +++ b/src/PSRule/Configuration/OutputOption.cs @@ -18,10 +18,12 @@ public sealed class OutputOption : IEquatable private const OutputFormat DEFAULT_FORMAT = OutputFormat.None; private const RuleOutcome DEFAULT_OUTCOME = RuleOutcome.Processed; private const OutputStyle DEFAULT_STYLE = OutputStyle.Client; + private const BannerFormat DEFAULT_BANNER = BannerFormat.Default; internal static readonly OutputOption Default = new OutputOption { As = DEFAULT_AS, + Banner = DEFAULT_BANNER, Encoding = DEFAULT_ENCODING, Format = DEFAULT_FORMAT, Outcome = DEFAULT_OUTCOME, @@ -31,6 +33,7 @@ public sealed class OutputOption : IEquatable public OutputOption() { As = null; + Banner = null; Culture = null; Encoding = null; Format = null; @@ -44,6 +47,7 @@ public OutputOption(OutputOption option) return; As = option.As; + Banner = option.Banner; Culture = option.Culture; Encoding = option.Encoding; Format = option.Format; @@ -61,6 +65,7 @@ public bool Equals(OutputOption other) { return other != null && As == other.As && + Banner == other.Banner && Culture == other.Culture && Encoding == other.Encoding && Format == other.Format && @@ -75,6 +80,7 @@ public override int GetHashCode() { int hash = 17; hash = hash * 23 + (As.HasValue ? As.Value.GetHashCode() : 0); + hash = hash * 23 + (Banner.HasValue ? Banner.Value.GetHashCode() : 0); hash = hash * 23 + (Culture != null ? Culture.GetHashCode() : 0); hash = hash * 23 + (Encoding.HasValue ? Encoding.Value.GetHashCode() : 0); hash = hash * 23 + (Format.HasValue ? Format.Value.GetHashCode() : 0); @@ -90,6 +96,7 @@ internal static OutputOption Combine(OutputOption o1, OutputOption o2) var result = new OutputOption(o1) { As = o1.As ?? o2.As, + Banner = o1.Banner ?? o2.Banner, Culture = o1.Culture ?? o2.Culture, Encoding = o1.Encoding ?? o2.Encoding, Format = o1.Format ?? o2.Format, @@ -106,6 +113,15 @@ internal static OutputOption Combine(OutputOption o1, OutputOption o2) [DefaultValue(null)] public ResultFormat? As { get; set; } + /// + /// The information displayed for Assert-PSRule banner. + /// + [DefaultValue(null)] + public BannerFormat? Banner { get; set; } + + /// + /// One or more cultures to use for generating output. + /// [DefaultValue(null)] public string[] Culture { get; set; } @@ -121,6 +137,9 @@ internal static OutputOption Combine(OutputOption o1, OutputOption o2) [DefaultValue(null)] public OutputFormat? Format { get; set; } + /// + /// The outcome of rule results to return. + /// [DefaultValue(null)] public RuleOutcome? Outcome { get; set; } @@ -130,6 +149,9 @@ internal static OutputOption Combine(OutputOption o1, OutputOption o2) [DefaultValue(null)] public string Path { get; set; } + /// + /// The style that results will be presented in. + /// [DefaultValue(null)] public OutputStyle? Style { get; set; } @@ -138,6 +160,9 @@ internal void Load(EnvironmentHelper env) if (env.TryEnum("PSRULE_OUTPUT_AS", out ResultFormat value)) As = value; + if (env.TryEnum("PSRULE_OUTPUT_BANNER", out BannerFormat banner)) + Banner = banner; + if (env.TryStringArray("PSRULE_OUTPUT_CULTURE", out string[] culture)) Culture = culture; @@ -162,6 +187,9 @@ internal void Load(Dictionary index) if (index.TryPopEnum("Output.As", out ResultFormat value)) As = value; + if (index.TryPopEnum("Output.Banner", out BannerFormat banner)) + Banner = banner; + if (index.TryPopStringArray("Output.Culture", out string[] culture)) Culture = culture; diff --git a/src/PSRule/PSRule.psm1 b/src/PSRule/PSRule.psm1 index 2623a6c918..52f87a8bc3 100644 --- a/src/PSRule/PSRule.psm1 +++ b/src/PSRule/PSRule.psm1 @@ -1108,6 +1108,11 @@ function New-PSRuleOption { [ValidateSet('Detail', 'Summary')] [PSRule.Configuration.ResultFormat]$OutputAs = 'Detail', + # Sets the Output.Banner option + [Parameter(Mandatory = $False)] + [ValidateSet('Default', 'Minimal', 'None', 'Title', 'Source', 'SupportLinks')] + [PSRule.Configuration.BannerFormat]$OutputBanner = 'Default', + # Sets the Output.Culture option [Parameter(Mandatory = $False)] [String[]]$OutputCulture, @@ -1323,6 +1328,11 @@ function Set-PSRuleOption { [ValidateSet('Detail', 'Summary')] [PSRule.Configuration.ResultFormat]$OutputAs = 'Detail', + # Sets the Output.Banner option + [Parameter(Mandatory = $False)] + [ValidateSet('Default', 'Minimal', 'None', 'Title', 'Source', 'SupportLinks')] + [PSRule.Configuration.BannerFormat]$OutputBanner = 'Default', + # Sets the Output.Culture option [Parameter(Mandatory = $False)] [String[]]$OutputCulture, @@ -1948,6 +1958,11 @@ function SetOptions { [ValidateSet('Detail', 'Summary')] [PSRule.Configuration.ResultFormat]$OutputAs = 'Detail', + # Sets the Output.Banner option + [Parameter(Mandatory = $False)] + [ValidateSet('Default', 'Minimal', 'None', 'Title', 'Source', 'SupportLinks')] + [PSRule.Configuration.BannerFormat]$OutputBanner = 'Default', + # Sets the Output.Culture option [Parameter(Mandatory = $False)] [String[]]$OutputCulture, @@ -2080,6 +2095,11 @@ function SetOptions { $Option.Output.As = $OutputAs; } + # Sets option Output.As + if ($PSBoundParameters.ContainsKey('OutputBanner')) { + $Option.Output.Banner = $OutputBanner; + } + # Sets option Output.Culture if ($PSBoundParameters.ContainsKey('OutputCulture')) { $Option.Output.Culture = $OutputCulture; diff --git a/src/PSRule/Pipeline/AssertPipeline.cs b/src/PSRule/Pipeline/AssertPipeline.cs index dd87d2ab87..d3bc7b5be3 100644 --- a/src/PSRule/Pipeline/AssertPipeline.cs +++ b/src/PSRule/Pipeline/AssertPipeline.cs @@ -93,7 +93,7 @@ protected AssertFormatterBase(Source[] source, IPipelineWriter writer, PSRuleOpt Option = option; Banner(); Source(source); - Help(source); + SupportLinks(source); } public void Error(ErrorRecord errorRecord) @@ -185,6 +185,9 @@ protected virtual void Warning(string message) protected void Banner() { + if (!Option.Output.Banner.GetValueOrDefault(BannerFormat.Default).HasFlag(BannerFormat.Title)) + return; + WriteLine(FormatterStrings.Banner.Replace("\\n", Environment.NewLine)); LineBreak(); } @@ -203,6 +206,9 @@ protected void StartObject(InvokeResult result, out RuleRecord[] records, Consol private void Source(Source[] source) { + if (!Option.Output.Banner.GetValueOrDefault(BannerFormat.Default).HasFlag(BannerFormat.Source)) + return; + var version = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion; WriteLineFormat(FormatterStrings.PSRuleVersion, version); var list = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -217,8 +223,11 @@ private void Source(Source[] source) LineBreak(); } - private void Help(Source[] source) + private void SupportLinks(Source[] source) { + if (!Option.Output.Banner.GetValueOrDefault(BannerFormat.Default).HasFlag(BannerFormat.SupportLinks)) + return; + WriteLine(OUTPUT_SEPARATOR_BAR); WriteLine(FormatterStrings.HelpDocs); WriteLine(FormatterStrings.HelpContribute); diff --git a/src/PSRule/Pipeline/PipelineBuilder.cs b/src/PSRule/Pipeline/PipelineBuilder.cs index f57705803b..486c261826 100644 --- a/src/PSRule/Pipeline/PipelineBuilder.cs +++ b/src/PSRule/Pipeline/PipelineBuilder.cs @@ -168,6 +168,7 @@ public virtual IPipelineBuilder Configure(PSRuleOption option) Option.Input.Format = Option.Input.Format ?? InputOption.Default.Format; Option.Output = new OutputOption(option.Output); Option.Output.Outcome = Option.Output.Outcome ?? OutputOption.Default.Outcome; + Option.Output.Banner = Option.Output.Banner ?? OutputOption.Default.Banner; return this; } diff --git a/tests/PSRule.Tests/PSRule.Options.Tests.ps1 b/tests/PSRule.Tests/PSRule.Options.Tests.ps1 index d53534bbed..6b2998e2fb 100644 --- a/tests/PSRule.Tests/PSRule.Options.Tests.ps1 +++ b/tests/PSRule.Tests/PSRule.Options.Tests.ps1 @@ -974,6 +974,45 @@ Describe 'New-PSRuleOption' -Tag 'Option','New-PSRuleOption' { } } + Context 'Read Output.Banner' { + It 'from default' { + $option = New-PSRuleOption -Default; + $option.Output.Banner | Should -Be 'Default'; + } + + It 'from Hashtable' { + $option = New-PSRuleOption -Option @{ 'Output.Banner' = 'Minimal' }; + $option.Output.Banner | Should -Be 'Minimal'; + + $option = New-PSRuleOption -Option @{ 'Output.Banner' = 1 }; + $option.Output.Banner | Should -Be 'Title'; + } + + It 'from YAML' { + $option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests.yml'); + $option.Output.Banner | Should -Be 'Minimal'; + + $option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests2.yml'); + $option.Output.Banner | Should -Be 'Title'; + } + + It 'from Environment' { + try { + $Env:PSRULE_OUTPUT_BANNER = 'Minimal'; + $option = New-PSRuleOption; + $option.Output.Banner | Should -Be 'Minimal'; + } + finally { + Remove-Item 'Env:PSRULE_OUTPUT_BANNER' -Force; + } + } + + It 'from parameter' { + $option = New-PSRuleOption -OutputBanner 'Minimal' -Path $emptyOptionsFilePath; + $option.Output.Banner | Should -Be 'Minimal'; + } + } + Context 'Read Output.Culture' { It 'from default' { $option = New-PSRuleOption -Default; @@ -1447,6 +1486,13 @@ Describe 'Set-PSRuleOption' -Tag 'Option','Set-PSRuleOption' { } } + Context 'Read Output.Banner' { + It 'from parameter' { + $option = Set-PSRuleOption -OutputBanner 'Minimal' @optionParams; + $option.Output.Banner | Should -Be 'Minimal'; + } + } + Context 'Read Output.Culture' { It 'from parameter' { # Single diff --git a/tests/PSRule.Tests/PSRule.Tests.yml b/tests/PSRule.Tests/PSRule.Tests.yml index a3603b42b3..470f1039a3 100644 --- a/tests/PSRule.Tests/PSRule.Tests.yml +++ b/tests/PSRule.Tests/PSRule.Tests.yml @@ -62,6 +62,7 @@ logging: # Configure output options output: as: Summary + banner: Minimal culture: [ 'en-CC' ] encoding: UTF7 format: Json diff --git a/tests/PSRule.Tests/PSRule.Tests2.yml b/tests/PSRule.Tests/PSRule.Tests2.yml index 37118cc2e2..5298583a4a 100644 --- a/tests/PSRule.Tests/PSRule.Tests2.yml +++ b/tests/PSRule.Tests/PSRule.Tests2.yml @@ -23,6 +23,7 @@ input: # Configure output output: + banner: 1 culture: - 'en-CC' - 'en-DD'