From fb2fa47a28257203023541578c38594967ed02bb Mon Sep 17 00:00:00 2001 From: TheCakeIsNaOH Date: Tue, 16 Aug 2022 12:08:47 -0500 Subject: [PATCH] (#32) Add PSScriptAnalyzer This adds a task to run PSScriptAnalyzer. It utilizes the Cake.Powershell module to run a script that will get ps1/psm1 files that are not excluded and run them through the specified settings file. Either a default formatting only run can be done, or custom run(s) can be specified. A custom base analysis path, setting file and set of folder exclusions can be specified for each run. For example, a custom run can be created to check that the Chocolatey powershell helpers use only the PSv2 cmdlets. The default run is for formatting, it checks files against the build in formatting related rules. This run can be expanded in the future, once enough projects are up to a baseline of good scripting practices. --- Chocolatey.Cake.Recipe/Content/analyzing.cake | 3 +- .../Content/formatting-settings.psd1 | 70 +++++++++++++++++ .../Content/parameters.cake | 7 ++ .../Content/psscriptanalyzer.cake | 76 +++++++++++++++++++ .../Content/run-psscriptanalyzer.ps1 | 51 +++++++++++++ Chocolatey.Cake.Recipe/Content/tasks.cake | 1 + 6 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 Chocolatey.Cake.Recipe/Content/formatting-settings.psd1 create mode 100644 Chocolatey.Cake.Recipe/Content/psscriptanalyzer.cake create mode 100644 Chocolatey.Cake.Recipe/Content/run-psscriptanalyzer.ps1 diff --git a/Chocolatey.Cake.Recipe/Content/analyzing.cake b/Chocolatey.Cake.Recipe/Content/analyzing.cake index 83cbb82..b599293 100644 --- a/Chocolatey.Cake.Recipe/Content/analyzing.cake +++ b/Chocolatey.Cake.Recipe/Content/analyzing.cake @@ -76,4 +76,5 @@ BuildParameters.Tasks.CreateIssuesReportTask = Task("CreateIssuesReport") BuildParameters.Tasks.AnalyzeTask = Task("Analyze") .IsDependentOn("InspectCode") - .IsDependentOn("CreateIssuesReport"); + .IsDependentOn("CreateIssuesReport") + .IsDependentOn("Run-PSScriptAnalyzer"); diff --git a/Chocolatey.Cake.Recipe/Content/formatting-settings.psd1 b/Chocolatey.Cake.Recipe/Content/formatting-settings.psd1 new file mode 100644 index 0000000..0e23cf7 --- /dev/null +++ b/Chocolatey.Cake.Recipe/Content/formatting-settings.psd1 @@ -0,0 +1,70 @@ +@{ + IncludeRules = @( + 'PSUseBOMForUnicodeEncodedFile', + 'PSMisleadingBacktick', + 'PSAvoidUsingCmdletAliases', + 'PSAvoidTrailingWhitespace', + 'PSAvoidSemicolonsAsLineTerminators', + 'PSUseCorrectCasing', + 'PSPlaceOpenBrace', + 'PSPlaceCloseBrace', + 'PSAlignAssignmentStatement', + 'PSUseConsistentWhitespace', + 'PSUseConsistentIndentation' + ) + + Rules = @{ + + <# + PSAvoidUsingCmdletAliases = @{ + 'allowlist' = @('') + }#> + + PSAvoidSemicolonsAsLineTerminators = @{ + Enable = $true + } + + PSUseCorrectCasing = @{ + Enable = $true + } + + PSPlaceOpenBrace = @{ + Enable = $true + OnSameLine = $true + NewLineAfter = $true + IgnoreOneLineBlock = $false + } + + PSPlaceCloseBrace = @{ + Enable = $true + NewLineAfter = $true + IgnoreOneLineBlock = $false + NoEmptyLineBefore = $true + } + + PSAlignAssignmentStatement = @{ + Enable = $true + CheckHashtable = $true + } + + PSUseConsistentIndentation = @{ + Enable = $true + Kind = 'space' + PipelineIndentation = 'IncreaseIndentationForFirstPipeline' + IndentationSize = 4 + } + + PSUseConsistentWhitespace = @{ + Enable = $true + CheckInnerBrace = $true + CheckOpenBrace = $true + CheckOpenParen = $true + CheckOperator = $true + CheckPipe = $true + CheckPipeForRedundantWhitespace = $false + CheckSeparator = $true + CheckParameter = $false + IgnoreAssignmentOperatorInsideHashTable = $true + } + } +} \ No newline at end of file diff --git a/Chocolatey.Cake.Recipe/Content/parameters.cake b/Chocolatey.Cake.Recipe/Content/parameters.cake index 7708b2c..686152c 100644 --- a/Chocolatey.Cake.Recipe/Content/parameters.cake +++ b/Chocolatey.Cake.Recipe/Content/parameters.cake @@ -28,6 +28,7 @@ public static class BuildParameters public static Func GetFilesToObfuscate { get; private set; } public static Func GetFilesToSign { get; private set; } public static Func> GetILMergeConfigs { get; private set; } + public static Func> GetPSScriptAnalyzerSettings { get; private set; } public static Func GetMsisToSign { get; private set; } public static Func GetProjectsToPack { get; private set; } public static Func GetScriptsToSign { get; private set; } @@ -89,6 +90,7 @@ public static class BuildParameters public static bool ShouldRunReportUnit { get; private set; } public static bool ShouldRunTransifex { get; set; } public static bool ShouldRunxUnit { get; private set; } + public static bool ShouldRunPSScriptAnalyzer { get; private set; } public static bool ShouldStrongNameOutputAssemblies { get; private set; } public static bool ShouldStrongNameSignDependentAssemblies { get; private set; } public static DirectoryPath SolutionDirectoryPath { get; private set; } @@ -197,6 +199,7 @@ public static class BuildParameters context.Information("ShouldRunReportUnit: {0}", BuildParameters.ShouldRunReportUnit); context.Information("ShouldRunTransifex: {0}", BuildParameters.ShouldRunTransifex); context.Information("ShouldRunxUnit: {0}", BuildParameters.ShouldRunxUnit); + context.Information("ShouldRunPSScriptAnalyzer: {0}", BuildParameters.ShouldRunPSScriptAnalyzer); context.Information("ShouldStrongNameOutputAssemblies: {0}", BuildParameters.ShouldStrongNameOutputAssemblies); context.Information("ShouldStrongNameSignDependentAssemblies: {0}", BuildParameters.ShouldStrongNameSignDependentAssemblies); context.Information("SolutionFilePath: {0}", context.MakeAbsolute((FilePath)SolutionFilePath)); @@ -231,6 +234,7 @@ public static class BuildParameters Func getFilesToObfuscate = null, Func getFilesToSign = null, Func> getILMergeConfigs = null, + Func> getPSScriptAnalyzerSettings = null, Func getMsisToSign = null, Func getProjectsToPack = null, Func getScriptsToSign = null, @@ -282,6 +286,7 @@ public static class BuildParameters bool shouldRunReportUnit = true, bool? shouldRunTransifex = null, bool shouldRunxUnit = true, + bool shouldRunPSScriptAnalyzer = true, bool shouldStrongNameOutputAssemblies = true, bool shouldStrongNameSignDependentAssemblies = true, DirectoryPath solutionDirectoryPath = null, @@ -337,6 +342,7 @@ public static class BuildParameters GetFilesToObfuscate = getFilesToObfuscate; GetFilesToSign = getFilesToSign; GetILMergeConfigs = getILMergeConfigs; + GetPSScriptAnalyzerSettings = getPSScriptAnalyzerSettings; GetMsisToSign = getMsisToSign; GetProjectsToPack = getProjectsToPack; GetScriptsToSign = getScriptsToSign; @@ -399,6 +405,7 @@ public static class BuildParameters ShouldRunReportUnit = shouldRunReportUnit; ShouldRunTransifex = shouldRunTransifex ?? TransifexIsConfiguredForRepository(context); ShouldRunxUnit = shouldRunxUnit; + ShouldRunPSScriptAnalyzer = shouldRunPSScriptAnalyzer; ShouldStrongNameOutputAssemblies = shouldStrongNameOutputAssemblies; ShouldStrongNameSignDependentAssemblies = shouldStrongNameSignDependentAssemblies; SolutionDirectoryPath = solutionDirectoryPath ?? sourceDirectoryPath.Combine(title); diff --git a/Chocolatey.Cake.Recipe/Content/psscriptanalyzer.cake b/Chocolatey.Cake.Recipe/Content/psscriptanalyzer.cake new file mode 100644 index 0000000..5e4724d --- /dev/null +++ b/Chocolatey.Cake.Recipe/Content/psscriptanalyzer.cake @@ -0,0 +1,76 @@ +BuildParameters.Tasks.PSScriptAnalyzerTask = Task("Run-PSScriptAnalyzer") + .WithCriteria(() => BuildParameters.ShouldRunPSScriptAnalyzer, "Skipping because PSScriptAnalyzer is not enabled") + .Does(() => +{ + var powerShellAnalysisScript = GetFiles("./tools/Chocolatey.Cake.Recipe*/Content/run-psscriptanalyzer.ps1").FirstOrDefault(); + + if (powerShellAnalysisScript == null) + { + Warning("Unable to find PowerShell Analysis script, so unable to run analysis."); + return; + } + + if (BuildParameters.GetPSScriptAnalyzerSettings != null) + { + foreach (var PSScriptAnalyzerSetting in BuildParameters.GetPSScriptAnalyzerSettings()) + { + Information(string.Format("Running PSScriptAnalyzer {0}", PSScriptAnalyzerSetting.Name); + + StartPowershellFile(MakeAbsolute(powerShellAnalysisScript), new PowershellSettings() + .WithModule("PSScriptAnalyzer") + .SetFormatOutput(true) + .WithArguments(args => { + args.AppendQuoted("AnalyzePath", PSScriptAnalyzerSetting.AnalysisPath.ToString()) + .AppendQuoted("SettingsPath", PSScriptAnalyzerSetting.SettingsPath.ToString()) + .AppendArray("ExcludePaths", PSScriptAnalyzerSetting.ExcludePaths); + })); + } + } + else + { + Information("There are no PSScriptAnalyzer Settings defined for this build, running with default format checking settings."); + + var settingsFile = GetFiles("./tools/Chocolatey.Cake.Recipe*/Content/formatting-settings.psd1").FirstOrDefault(); + + if (settingsFile == null) + { + Warning("Unable to find PowerShell Analysis settings, so unable to run analysis."); + return; + } + + var excludePaths = new List { "tools", "code_drop"}; + + StartPowershellFile(MakeAbsolute(powerShellAnalysisScript), new PowershellSettings() + .WithModule("PSScriptAnalyzer") + .SetFormatOutput(true) + .WithArguments(args => { + args.AppendQuoted("AnalyzePath", BuildParameters.RootDirectoryPath.ToString()) + .AppendQuoted("SettingsPath", settingsFile.ToString()) + .AppendArray("ExcludePaths", excludePaths); + })); + } +}); + +public class PSScriptAnalyzerSettings +{ + public FilePath AnalysisPath { get; set; } + public FilePath SettingsPath { get; set; } + public List ExcludePaths { get; set; } + public string Name { get; set; } + + public PSScriptAnalyzerSettings() + { + Name = "Unnamed"; + } + + public PSScriptAnalyzerSettings(FilePath analysisPath, + FilePath settingsPath, + List excludePaths = null, + string name = "Unnamed") + { + AnalysisPath = analysisPath; + SettingsPath = settingsPath; + ExcludePaths = excludePaths; + Name = name; + } +} \ No newline at end of file diff --git a/Chocolatey.Cake.Recipe/Content/run-psscriptanalyzer.ps1 b/Chocolatey.Cake.Recipe/Content/run-psscriptanalyzer.ps1 new file mode 100644 index 0000000..05477cc --- /dev/null +++ b/Chocolatey.Cake.Recipe/Content/run-psscriptanalyzer.ps1 @@ -0,0 +1,51 @@ +[cmdletBinding()] +Param( + [Parameter()] + [String] + $AnalyzePath, + + [Parameter()] + [String] + $SettingsPath, + + [Parameter()] + [String[]] + $ExcludePaths +) + +#Requires -Modules PSScriptAnalyzer + +Push-Location -Path $AnalyzePath + +try { + if ($PSBoundParameters.ContainsKey('ExcludePaths')) { + $ExcludePaths = $ExcludePaths | ForEach-Object { (Resolve-Path -Path $_).Path } + } + + $scripts = Get-ChildItem -Path $AnalyzePath -Filter "*.ps1" -Recurse | ForEach-Object { + if ($PSBoundParameters.ContainsKey('ExcludePaths')) { + foreach ($path in $ExcludePaths) { + if ($_.FullName.StartsWith($path)) { + return + } + } + } + $_ + } + $modules = Get-ChildItem -Path $AnalyzePath -Filter "*.psm1" -Recurse | ForEach-Object { + if ($PSBoundParameters.ContainsKey('ExcludePaths')) { + foreach ($path in $ExcludePaths) { + if ($_.FullName.StartsWith($path)) { + return + } + } + } + $_ + } + + $modules | Invoke-ScriptAnalyzer -Settings $SettingsPath | Select-Object RuleName, ScriptPath, Line, Message + $Scripts | Invoke-ScriptAnalyzer -Settings $SettingsPath | Select-Object RuleName, ScriptPath, Line, Message +} +finally { + Pop-Location +} \ No newline at end of file diff --git a/Chocolatey.Cake.Recipe/Content/tasks.cake b/Chocolatey.Cake.Recipe/Content/tasks.cake index 7cb12b4..e3c8e8b 100644 --- a/Chocolatey.Cake.Recipe/Content/tasks.cake +++ b/Chocolatey.Cake.Recipe/Content/tasks.cake @@ -22,6 +22,7 @@ public class BuildTasks public CakeTaskBuilder InspectCodeTask { get; set; } public CakeTaskBuilder CreateIssuesReportTask { get; set; } public CakeTaskBuilder AnalyzeTask { get; set; } + public CakeTaskBuilder PSScriptAnalyzerTask { get; set; } // Eazfuscator Tasks public CakeTaskBuilder ObfuscateAssembliesTask { get; set; }