PSQualityCheck.psm1
function Invoke-PSQualityCheck { <# .SYNOPSIS Invoke the PSQualityCheck tests .DESCRIPTION Invoke a series of Pester-based quality tests on the passed files .PARAMETER Path A string array containing paths to check for testable files .PARAMETER File A string array containing testable files .PARAMETER SonarQubeRulesPath A path the the external PSScriptAnalyzer rules for SonarQube .PARAMETER ShowCheckResults Show a summary of the Check results at the end of processing .EXAMPLE Invoke-PSQualityCheck -Path 'C:\Scripts' This will call the quality checks on single path .EXAMPLE Invoke-PSQualityCheck -Path @('C:\Scripts', 'C:\MoreScripts') This will call the quality checks with multiple paths .EXAMPLE Invoke-PSQualityCheck -File 'C:\Scripts\Script.ps1' This will call the quality checks with single script file .EXAMPLE Invoke-PSQualityCheck -File 'C:\Scripts\Script.psm1' This will call the quality checks with single module file .EXAMPLE Invoke-PSQualityCheck -File 'C:\Scripts\Script.psd1' This will call the quality checks with single datafile file Note: The datafile test will fail as it is not a file that is accepted for testing .EXAMPLE Invoke-PSQualityCheck -File @('C:\Scripts\Script.ps1','C:\Scripts\Script2.ps1') This will call the quality checks with multiple files. Files can be either scripts or modules .EXAMPLE Invoke-PSQualityCheck -File 'C:\Scripts\Script.ps1' -SonarQubeRulesPath 'C:\SonarQubeRules' This will call the quality checks with single file and the extra PSScriptAnalyzer rules used by SonarQube .EXAMPLE Invoke-PSQualityCheck -Path 'C:\Scripts' -ShowCheckResults This will display a summary of the checks performed (example below uses sample data): Name Files Tested Total Passed Failed Skipped ---- ------------ ----- ------ ------ ------- Module Tests 2 14 14 0 0 Extracting functions 2 2 2 0 0 Extracted function script tests 22 330 309 0 21 Total 24 346 325 0 21 For those who have spotted that the Total files tested isn't a total of the rows above, this is because the Module Tests and Extracting function Tests operate on the same file and are then not counted twice .LINK Website: https://github.com/andrewrdavidson/PSQualityCheck SonarQube rules are available here: https://github.com/indented-automation/ScriptAnalyzerRules #> [CmdletBinding()] [OutputType([System.Void], [HashTable])] param ( [Parameter(Mandatory = $true, ParameterSetName = "Path")] [String[]]$Path, [Parameter(Mandatory = $true, ParameterSetName = "File")] [String[]]$File, [Parameter(Mandatory = $false)] [String]$SonarQubeRulesPath, [switch]$ShowCheckResults ) Set-StrictMode -Version Latest # External Modules Import-Module -Name 'Pester' -MinimumVersion '5.1.0' -Force Import-Module -Name 'PSScriptAnalyzer' -MinimumVersion '1.19.1' -Force $modulePath = (Get-Module -Name 'PSQualityCheck').ModuleBase # Analyse the incoming Path and File parameters and produce a list of Modules and Scripts $scriptsToTest = @() $modulesToTest = @() if ($PSBoundParameters.ContainsKey('Path')) { if ($Path -isnot [string[]]) { $Path = @($Path) } foreach ($item in $Path) { # Test whether the item is a directory (also tells us if it exists) if (Test-Path -Path $item -PathType Container) { $scriptsToTest += Get-FileList -Path $item -Extension '.ps1' $modulesToTest += Get-FileList -Path $item -Extension '.psm1' } else { Write-Warning -Message "$item is not a directory, skipping" } } } if ($PSBoundParameters.ContainsKey('File')) { if ($File -isnot [string[]]) { $File = @($File) } foreach ($item in $File) { # Test whether the item is a file (also tells us if it exists) if (Test-Path -Path $item -PathType Leaf) { $itemProperties = Get-ChildItem -Path $item switch ($itemProperties.Extension) { '.psm1' { $modulesToTest += $itemProperties } '.ps1' { $scriptsToTest += $itemProperties } } } else { Write-Warning -Message "$item is not a file, skipping" } } } # Default Pester Parameters $configuration = [PesterConfiguration]::Default $configuration.Run.Exit = $false $configuration.CodeCoverage.Enabled = $false $configuration.Output.Verbosity = 'Detailed' $configuration.Run.PassThru = $true $configuration.Should.ErrorAction = 'Stop' $moduleResults = $null $extractionResults = $null $extractedScriptResults = $null $scriptResults = $null if ($modulesToTest.Count -ge 1) { # Location of files extracted from any passed modules $extractPath = Join-Path -Path ([IO.Path]::GetTempPath()) -ChildPath (New-Guid).Guid # Run the Module tests on all the valid module files found $container1 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath 'Checks\Module.Tests.ps1') -Data @{ Source = $modulesToTest } $configuration.Run.Container = $container1 $moduleResults = Invoke-Pester -Configuration $configuration # Extract all the functions from the modules into individual .ps1 files ready for testing $container2 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath 'Checks\Function-Extraction.Tests.ps1') -Data @{ Source = $modulesToTest; ExtractPath = $extractPath } $configuration.Run.Container = $container2 $extractionResults = Invoke-Pester -Configuration $configuration # Get a list of the 'extracted' function scripts .ps1 files $extractedScriptsToTest = Get-ChildItem -Path $extractPath -Include '*.ps1' -Recurse # Run the Script tests against all the extracted functions .ps1 files $container3 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath 'Checks\Script.Tests.ps1') -Data @{ Source = $extractedScriptsToTest; SonarQubeRules = $SonarQubeRulesPath } $configuration.Run.Container = $container3 $extractedScriptResults = Invoke-Pester -Configuration $configuration } if ($scriptsToTest.Count -ge 1) { # Run the Script tests against all the valid script files found $container3 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath 'Checks\Script.Tests.ps1') -Data @{ Source = $scriptsToTest; SonarQubeRules = $SonarQubeRulesPath } $configuration.Run.Container = $container3 $scriptResults = Invoke-Pester -Configuration $configuration } if ($PSBoundParameters.ContainsKey('ShowCheckResults')) { $qualityCheckResults = @() $filesTested = $total = $passed = $failed = $skipped = 0 if ($null -ne $moduleResults) { $qualityCheckResults += @{ 'Test' = 'Module Tests' 'Files Tested' = $ModulesToTest.Count 'Total' = $moduleResults.TotalCount 'Passed' = $moduleResults.PassedCount 'Failed' = $moduleResults.FailedCount 'Skipped' = $moduleResults.SkippedCount } $filesTested += $ModulesToTest.Count $total += $moduleResults.TotalCount $passed += $moduleResults.PassedCount $failed += $moduleResults.FailedCount $skipped += $moduleResults.SkippedCount } if ($null -ne $extractionResults) { $qualityCheckResults += @{ 'Test' = 'Extracting functions' 'Files Tested' = $ModulesToTest.Count 'Total' = $extractionResults.TotalCount 'Passed' = $extractionResults.PassedCount 'Failed' = $extractionResults.FailedCount 'Skipped' = $extractionResults.SkippedCount } $total += $extractionResults.TotalCount $passed += $extractionResults.PassedCount $failed += $extractionResults.FailedCount $skipped += $extractionResults.SkippedCount } if ($null -ne $extractedScriptResults) { $qualityCheckResults += @{ 'Test' = 'Extracted function script tests' 'Files Tested' = $extractedScriptsToTest.Count 'Total' = $extractedScriptResults.TotalCount 'Passed' = $extractedScriptResults.PassedCount 'Failed' = $extractedScriptResults.FailedCount 'Skipped' = $extractedScriptResults.SkippedCount } $filesTested += $extractedScriptsToTest.Count $total += $extractedScriptResults.TotalCount $passed += $extractedScriptResults.PassedCount $failed += $extractedScriptResults.FailedCount $skipped += $extractedScriptResults.SkippedCount } if ($null -ne $scriptResults) { $qualityCheckResults += @{ 'Test' = "Script Tests" 'Files Tested' = $scriptsToTest.Count 'Total' = $scriptResults.TotalCount 'Passed' = $scriptResults.PassedCount 'Failed' = $scriptResults.FailedCount 'Skipped' = $scriptResults.SkippedCount } $filesTested += $scriptsToTest.Count $total += $scriptResults.TotalCount $passed += $scriptResults.PassedCount $failed += $scriptResults.FailedCount $skipped += $scriptResults.SkippedCount } $qualityCheckResults += @{ 'Test' = "Total" 'Files Tested' = $filesTested 'Total' = $total 'Passed' = $passed 'Failed' = $failed 'Skipped' = $skipped } # This works on PS5 and PS7 $qualityCheckResults | ForEach-Object { [PSCustomObject]@{ 'Test' = $_.Test 'Files Tested' = $_.'Files Tested' 'Total' = $_.total 'Passed' = $_.passed 'Failed' = $_.failed 'Skipped' = $_.skipped } } | Format-Table -AutoSize # This works on PS7 not on PS5 # $qualityCheckResults | Select-Object Name, 'Files Tested', Total, Passed, Failed, Skipped | Format-Table -AutoSize } } |