diff --git a/appveyor.yml b/appveyor.yml index 41831d3..0964252 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,6 +10,7 @@ install: Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null Install-Module Pester -MinimumVersion 3.4.0 -Scope CurrentUser -Force | Out-Null Install-Module psake -Scope CurrentUser -Force | Out-Null + Install-Module PSScriptAnalyzer -Scope CurrentUser -Force | Out-Null build_script: - ps: | diff --git a/build.psake.ps1 b/build.psake.ps1 index 3813d0e..970c1c3 100644 --- a/build.psake.ps1 +++ b/build.psake.ps1 @@ -74,7 +74,7 @@ Task Clean -requiredVariables ReleaseDir { } } -Task Build -depends BuildImpl, Sign, PostBuild { +Task Build -depends BuildImpl, Analyze, Sign, PostBuild { } Task BuildImpl -depends Init, Clean, PreBuild -requiredVariables SrcRootDir, OutDir { @@ -134,6 +134,41 @@ Task Sign -depends BuildImpl -requiredVariables SettingsPath, SignScripts { } } +Task Analyze -depends BuildImpl -requiredVariables ScriptAnalysisAction, OutDir { + if ((Get-Host).Name -in $SkipScriptAnalysisHost) { + $ScriptAnalysisAction = 'Skip' + } + + if ($ScriptAnalysisAction -eq 'Skip') { + "Script analysis is not enabled. Skipping Analyze task." + return + } + + $analysisResult = Invoke-ScriptAnalyzer -Path $OutDir -Recurse -Verbose:$VerbosePreference + $analysisResult | Format-Table + switch ($ScriptAnalysisAction) { + 'Error' { + Assert -conditionToCheck ( + ($analysisResult | Where-Object Severity -eq 'Error').Count -eq 0 + ) -failureMessage 'One or more Script Analyzer errors were found. Build cannot continue!' + } + 'Warning' { + Assert -conditionToCheck ( + ($analysisResult | Where-Object { + $_.Severity -eq 'Warning' -or $_.Severity -eq 'Error' + }).Count -eq 0) -failureMessage 'One or more Script Analyzer warnings were found. Build cannot continue!' + } + 'None' { + return + } + default { + Assert -conditionToCheck ( + $analysisResult.Count -eq 0 + ) -failureMessage 'One or more Script Analyzer issues were found. Build cannot continue!' + } + } +} + Task GenerateMarkdown -depends Build, PreBuildHelp -requiredVariables DocsRootDir, ModuleName, OutDir { if ($null -eq $DefaultLocale) { $DefaultLocale = 'en-US' @@ -194,7 +229,7 @@ Task InstallImpl -depends BuildHelp, PreInstall -requiredVariables OutDir { Copy-Item -Path $OutDir\* -Destination $InstallPath -Verbose:$VerbosePreference -Recurse -Force } -Task Test -depends Build -requiredVariables TestRootDir, ModuleName { +Task Test -depends Analyze -requiredVariables TestRootDir, ModuleName { Import-Module Pester try { @@ -375,6 +410,7 @@ function PromptUserForCredentialAndStorePassword { } function AddSetting { + [System.Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '', Scope='Function')] param( [Parameter(Mandatory)] [string]$Key, diff --git a/build.settings.ps1 b/build.settings.ps1 index 647b388..62b60ec 100644 --- a/build.settings.ps1 +++ b/build.settings.ps1 @@ -55,6 +55,23 @@ Properties { [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '', Scope='*', Target='CertSubjectName')] $CertSubjectName = $null + # -------------------- Script Analysis properties ------------------------------ + + # The script analysis task step will run, unless your host is in the array defined below. + # This allows you to control whether code analysis is executed, for hosts where script + # analysis is included in the product. + [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '', Scope='*', Target='SkipCodeAnalysisHost')] + $SkipScriptAnalysisHost = @( + 'Visual Studio Code Host', + 'My Custom Host with scriptanalyzer support' + ) + + # To control the failure of the build with specific script analyzer rule severities, + # the CodeAnalysisStop variable can be used. The supported values for this variable are + # 'Warning', 'Error', 'All', 'None' or 'Skip'. Invalid input will stop on all rules. + # 'Skip' will skip over the code analysis step all together. + [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '', Scope='*', Target='CodeAnalysisStop')] + $ScriptAnalysisAction = 'Error' # -------------------- Publishing properties ------------------------------ diff --git a/src/InvokePlaster.ps1 b/src/InvokePlaster.ps1 index 468615e..081470d 100644 --- a/src/InvokePlaster.ps1 +++ b/src/InvokePlaster.ps1 @@ -24,10 +24,13 @@ Please follow the scripting style of this file when adding new script. General notes #> function Invoke-Plaster { + [System.Diagnostics.CodeAnalysis.SuppressMessage('PSAvoidUsingWriteHost', '', Scope='Function')] + [System.Diagnostics.CodeAnalysis.SuppressMessage('PSAvoidShouldContinueWithoutForce', '', Scope='Function', Target='CopyFileWithConflictDetection')] [System.Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '', Scope='Function', Target='CopyFileWithConflictDetection')] [System.Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '', Scope='Function', Target='ProcessFile')] [System.Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '', Scope='Function', Target='ProcessModifyFile')] [System.Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '', Scope='Function', Target='ProcessNewModuleManifest')] + [System.Diagnostics.CodeAnalysis.SuppressMessage('PSAvoidUsingConvertToSecureStringWithPlainText', '', Scope='Function', Target='ProcessParameter')] [System.Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '', Scope='Function', Target='ProcessRequireModule')] [System.Diagnostics.CodeAnalysis.SuppressMessage('PSAvoidShouldContinueWithoutForce', '', Scope='Function', Target='ProcessFile')] [CmdletBinding(SupportsShouldProcess=$true)] @@ -709,7 +712,8 @@ function Invoke-Plaster { # Copy over empty directories - if any. $gciParams.Remove('File') $gciParams['Directory'] = $true - $dirs = @(Microsoft.PowerShell.Management\Get-ChildItem @gciParams | Where {$_.GetFileSystemInfos().Length -eq 0}) + $dirs = @(Microsoft.PowerShell.Management\Get-ChildItem @gciParams | + Where-Object {$_.GetFileSystemInfos().Length -eq 0}) foreach ($dir in $dirs) { $dirSrcPath = $dir.FullName $relPath = $dirSrcPath.Substring($srcRelRootPathLength) @@ -1112,13 +1116,9 @@ function Invoke-Plaster { } } -<# -██ ██ ███████ ██ ██████ ███████ ██████ ███████ -██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -███████ █████ ██ ██████ █████ ██████ ███████ -██ ██ ██ ██ ██ ██ ██ ██ ██ -██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ -#> +############################################################################### +# Helper functions +############################################################################### function InitializePredefinedVariables([string]$TemplatePath, [string]$DestPath) { # Always set these variables, even if the command has been run with -WhatIf @@ -1212,7 +1212,7 @@ function WriteContentWithEncoding([string]$path, [string[]]$content, [string]$en 'utf8' { $noBomEncoding = New-Object System.Text.UTF8Encoding($false) } } - if ($content -eq $null) { + if ($null -eq $content) { $content = [string]::Empty } diff --git a/src/Templates/NewModule/build.psake.ps1 b/src/Templates/NewModule/build.psake.ps1 index 3813d0e..970c1c3 100644 --- a/src/Templates/NewModule/build.psake.ps1 +++ b/src/Templates/NewModule/build.psake.ps1 @@ -74,7 +74,7 @@ Task Clean -requiredVariables ReleaseDir { } } -Task Build -depends BuildImpl, Sign, PostBuild { +Task Build -depends BuildImpl, Analyze, Sign, PostBuild { } Task BuildImpl -depends Init, Clean, PreBuild -requiredVariables SrcRootDir, OutDir { @@ -134,6 +134,41 @@ Task Sign -depends BuildImpl -requiredVariables SettingsPath, SignScripts { } } +Task Analyze -depends BuildImpl -requiredVariables ScriptAnalysisAction, OutDir { + if ((Get-Host).Name -in $SkipScriptAnalysisHost) { + $ScriptAnalysisAction = 'Skip' + } + + if ($ScriptAnalysisAction -eq 'Skip') { + "Script analysis is not enabled. Skipping Analyze task." + return + } + + $analysisResult = Invoke-ScriptAnalyzer -Path $OutDir -Recurse -Verbose:$VerbosePreference + $analysisResult | Format-Table + switch ($ScriptAnalysisAction) { + 'Error' { + Assert -conditionToCheck ( + ($analysisResult | Where-Object Severity -eq 'Error').Count -eq 0 + ) -failureMessage 'One or more Script Analyzer errors were found. Build cannot continue!' + } + 'Warning' { + Assert -conditionToCheck ( + ($analysisResult | Where-Object { + $_.Severity -eq 'Warning' -or $_.Severity -eq 'Error' + }).Count -eq 0) -failureMessage 'One or more Script Analyzer warnings were found. Build cannot continue!' + } + 'None' { + return + } + default { + Assert -conditionToCheck ( + $analysisResult.Count -eq 0 + ) -failureMessage 'One or more Script Analyzer issues were found. Build cannot continue!' + } + } +} + Task GenerateMarkdown -depends Build, PreBuildHelp -requiredVariables DocsRootDir, ModuleName, OutDir { if ($null -eq $DefaultLocale) { $DefaultLocale = 'en-US' @@ -194,7 +229,7 @@ Task InstallImpl -depends BuildHelp, PreInstall -requiredVariables OutDir { Copy-Item -Path $OutDir\* -Destination $InstallPath -Verbose:$VerbosePreference -Recurse -Force } -Task Test -depends Build -requiredVariables TestRootDir, ModuleName { +Task Test -depends Analyze -requiredVariables TestRootDir, ModuleName { Import-Module Pester try { @@ -375,6 +410,7 @@ function PromptUserForCredentialAndStorePassword { } function AddSetting { + [System.Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '', Scope='Function')] param( [Parameter(Mandatory)] [string]$Key, diff --git a/src/Templates/NewModule/build.settings.ps1 b/src/Templates/NewModule/build.settings.ps1 index 647b388..62b60ec 100644 --- a/src/Templates/NewModule/build.settings.ps1 +++ b/src/Templates/NewModule/build.settings.ps1 @@ -55,6 +55,23 @@ Properties { [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '', Scope='*', Target='CertSubjectName')] $CertSubjectName = $null + # -------------------- Script Analysis properties ------------------------------ + + # The script analysis task step will run, unless your host is in the array defined below. + # This allows you to control whether code analysis is executed, for hosts where script + # analysis is included in the product. + [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '', Scope='*', Target='SkipCodeAnalysisHost')] + $SkipScriptAnalysisHost = @( + 'Visual Studio Code Host', + 'My Custom Host with scriptanalyzer support' + ) + + # To control the failure of the build with specific script analyzer rule severities, + # the CodeAnalysisStop variable can be used. The supported values for this variable are + # 'Warning', 'Error', 'All', 'None' or 'Skip'. Invalid input will stop on all rules. + # 'Skip' will skip over the code analysis step all together. + [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '', Scope='*', Target='CodeAnalysisStop')] + $ScriptAnalysisAction = 'Error' # -------------------- Publishing properties ------------------------------ diff --git a/src/TestPlasterManifest.ps1 b/src/TestPlasterManifest.ps1 index 75c1143..c555b83 100644 --- a/src/TestPlasterManifest.ps1 +++ b/src/TestPlasterManifest.ps1 @@ -26,6 +26,7 @@ #> function Test-PlasterManifest { [CmdletBinding()] + [OutputType([System.Xml.XmlDocument])] param( # Specifies a path to a plasterManifest.xml file. [Parameter(Position=0,