Public/Invoke-PSCodeHealth.ps1
Function Invoke-PSCodeHealth { <# .SYNOPSIS Gets quality and maintainability metrics for PowerShell code contained in scripts, modules or directories. .DESCRIPTION Gets quality and maintainability metrics for PowerShell code contained in scripts, modules or directories. These metrics relate to : - Length of functions - Complexity of functions - Code smells, styling issues and violations of best practices (using PSScriptAnalyzer) - Tests and test coverage (using Pester to run tests) - Comment-based help in functions .PARAMETER Path To specify the path of the directory to search for PowerShell files to analyze. If the Path is not specified and the current location is in a FileSystem PowerShell drive, this will default to the current directory. .PARAMETER TestsPath To specify the file or directory where tests are located. If not specified, the command will look for tests in the same directory as each function. .PARAMETER TestsResult To use an existing Pester tests result object for generating the following metrics : - NumberOfTests - NumberOfFailedTests - NumberOfPassedTests - TestsPassRate (%) - TestCoverage (%) - CommandsMissedTotal .PARAMETER Recurse To search PowerShell files in the Path directory and all subdirectories recursively. .PARAMETER Exclude To specify file(s) to exclude from both the code analysis point of view and the test coverage point of view. The value of this parameter qualifies the Path parameter. Enter a path element or pattern, such as *example*. Wildcards are permitted. .PARAMETER HtmlReportPath To instruct Invoke-PSCodeHealth to generate an HTML report, and specify the path where the HTML file should be saved. The path must include the folder path (which has to exist) and the file name. .PARAMETER CustomSettingsPath To specify the path of a file containing user-defined compliance rules (metrics thresholds, etc...) in JSON format. Any compliance rule specified in this file override the default, and rules not specified in this file will use the default from PSCodeHealthSettings.json. .PARAMETER PassThru When the parameter HtmlReportPath is used, by default, Invoke-PSCodeHealth doesn't output a [PSCodeHealth.Overall.HealthReport] object to the pipeline. The PassThru parameter allows to instruct Invoke-PSCodeHealth to output both an HTML report file and a [PSCodeHealth.Overall.HealthReport] object. .EXAMPLE PS C:\> Invoke-PSCodeHealth -Path 'C:\GitRepos\MyModule' -Recurse -TestsPath 'C:\GitRepos\MyModule\Tests\Unit' Gets quality and maintainability metrics for code from PowerShell files in the directory C:\GitRepos\MyModule\ and any subdirectories. This command will look for tests located in the directory C:\GitRepos\MyModule\Tests\Unit, and any subdirectories. .EXAMPLE PS C:\> Invoke-PSCodeHealth -Path 'C:\GitRepos\MyModule' -TestsPath 'C:\GitRepos\MyModule\Tests' -Recurse -Exclude "*example*" Gets quality and maintainability metrics for code from PowerShell files in the directory C:\GitRepos\MyModule\ and any subdirectories, except for files containing "example" in their name. This command will look for tests located in the directory C:\GitRepos\MyModule\Tests\, and any subdirectories. .EXAMPLE PS C:\> Invoke-PSCodeHealth -Path 'C:\GitRepos\MyModule' -TestsPath 'C:\GitRepos\MyModule\Tests' -HtmlReportPath .\Report.html -PassThru Gets quality and maintainability metrics for code from PowerShell files in the directory C:\GitRepos\MyModule\. This command will create an HTML report (Report.html) in the current directory and a PSCodeHealth.Overall.HealthReport object to the pipeline. The styling of HTML elements will reflect their compliance, based on the default compliance rules. .EXAMPLE PS C:\> Invoke-PSCodeHealth -Path 'C:\GitRepos\MyModule' -TestsPath 'C:\GitRepos\MyModule\Tests' -HtmlReportPath .\Report.html -CustomSettingsPath .\MySettings.json Gets quality and maintainability metrics for code from PowerShell files in the directory C:\GitRepos\MyModule\. This command will create an HTML report (Report.html) in the current directory and a PSCodeHealth.Overall.HealthReport object to the pipeline. The styling of HTML elements will reflect their compliance, based on the default compliance rules and any custom rules in the file .\MySettings.json. .OUTPUTS PSCodeHealth.Overall.HealthReport .NOTES #> [CmdletBinding(DefaultParameterSetName = 'Default')] [OutputType([PSCustomObject])] Param ( [Parameter(Position=0, Mandatory=$False, ValueFromPipeline=$True)] [ValidateScript({ Test-Path $_ })] [string]$Path, [Parameter(Position=1, Mandatory=$False)] [ValidateScript({ Test-Path $_ })] [string]$TestsPath, [Parameter(Position=2, Mandatory=$False)] [ValidateScript({ $_.TotalCount -is [int] })] [PSCustomObject]$TestsResult, [switch]$Recurse, [Parameter(Mandatory=$False)] [string[]]$Exclude, [Parameter(Mandatory, ParameterSetName='HtmlReport')] [ValidateScript({ Test-Path -Path (Split-Path $_ -Parent) -PathType Container })] [string]$HtmlReportPath, [Parameter(Mandatory=$False, ParameterSetName='HtmlReport')] [ValidateScript({ Test-Path -Path $_ -PathType Leaf })] [string]$CustomSettingsPath, [Parameter(Mandatory=$False, ParameterSetName='HtmlReport')] [switch]$PassThru ) If ( -not($PSBoundParameters.ContainsKey('Path')) ) { If ( $PWD.Provider.Name -eq 'FileSystem' ) { $Path = $PWD.ProviderPath } Else { Throw "The current location is from the $($PWD.Provider.Name) provider, please provide a value for the Path parameter or change to a FileSystem location." } } If ( (Get-Item -Path $Path).PSIsContainer ) { If ( $PSBoundParameters.ContainsKey('Exclude') ) { $PowerShellFiles = Get-PowerShellFile -Path $Path -Recurse:$($Recurse.IsPresent) -Exclude $Exclude } Else { $PowerShellFiles = Get-PowerShellFile -Path $Path -Recurse:$($Recurse.IsPresent) } } Else { $PowerShellFiles = $Path } If ( -not $PowerShellFiles ) { return $Null } Else { Write-VerboseOutput -Message 'Found the following PowerShell files in the directory :' Write-VerboseOutput -Message "$($PowerShellFiles | Out-String)" } $FunctionDefinitions = Get-FunctionDefinition -Path $PowerShellFiles [System.Collections.ArrayList]$FunctionHealthRecords = @() If ( -not $FunctionDefinitions ) { $FunctionHealthRecords = $Null } Else { Foreach ( $Function in $FunctionDefinitions ) { Write-VerboseOutput -Message "Gathering metrics for function : $($Function.Name)" $TestCoverageParams = If ( $TestsPath ) { @{ FunctionDefinition = $Function; TestsPath = $TestsPath }} Else { @{ FunctionDefinition = $Function } } $TestCoverage = Get-FunctionTestCoverage @TestCoverageParams $FunctionHealthRecord = New-FunctionHealthRecord -FunctionDefinition $Function -FunctionTestCoverage $TestCoverage $Null = $FunctionHealthRecords.Add($FunctionHealthRecord) } } If ( -not $TestsPath ) { $TestsPath = If ( (Get-Item -Path $Path).PSIsContainer ) { $Path } Else { Split-Path -Path $Path -Parent } } $PathItem = (Get-Item -Path $Path) $ReportTitle = $PathItem.Name $AnalyzedPath = $PathItem.FullName $PSCodeHealthReportParams = @{ ReportTitle = $ReportTitle AnalyzedPath = $AnalyzedPath Path = $PowerShellFiles FunctionHealthRecord = $FunctionHealthRecords TestsPath = $TestsPath } If ( ($PSBoundParameters.ContainsKey('TestsResult')) ) { $HealthReport = New-PSCodeHealthReport @PSCodeHealthReportParams -TestsResult $PSBoundParameters.TestsResult } Else { $HealthReport = New-PSCodeHealthReport @PSCodeHealthReportParams } If ( $PSCmdlet.ParameterSetName -ne 'HtmlReport' ) { return $HealthReport } Else { $JsPlaceholders = @{ NUMBER_OF_PASSED_TESTS = $HealthReport.NumberOfPassedTests NUMBER_OF_FAILED_TESTS = $HealthReport.NumberOfFailedTests TESTS_PASS_RATE = $HealthReport.TestsPassRate TEST_COVERAGE = $HealthReport.TestCoverage CODE_NOT_COVERED = 100 - $HealthReport.TestCoverage } $JsContent = Set-PSCodeHealthPlaceholdersValue -TemplatePath "$PSScriptRoot\..\Assets\HealthReport.js" -PlaceholdersData $JsPlaceholders $TableData = New-PSCodeHealthTableData -HealthReport $HealthReport $HtmlPlaceholders = @{ REPORT_TITLE = $HealthReport.ReportTitle CSS_CONTENT = Get-Content -Path "$PSScriptRoot\..\Assets\HealthReport.css" ANALYZED_PATH = $HealthReport.AnalyzedPath REPORT_DATE = $HealthReport.ReportDate NUMBER_OF_FILES = $HealthReport.Files NUMBER_OF_FUNCTIONS = $HealthReport.Functions LINES_OF_CODE_TOTAL = $HealthReport.LinesOfCodeTotal SCRIPTANALYZER_ERRORS = $HealthReport.ScriptAnalyzerErrors SCRIPTANALYZER_WARNINGS = $HealthReport.ScriptAnalyzerWarnings SCRIPTANALYZER_INFO = $HealthReport.ScriptAnalyzerInformation SCRIPTANALYZER_TOTAL = $HealthReport.ScriptAnalyzerFindingsTotal SCRIPTANALYZER_AVERAGE = $HealthReport.ScriptAnalyzerFindingsAverage FUNCTIONS_WITHOUT_HELP = $HealthReport.FunctionsWithoutHelp BEST_PRACTICES_TABLE_ROWS = $TableData.BestPracticesRows COMPLEXITY_HIGHEST = $HealthReport.ComplexityHighest NESTING_DEPTH_HIGHEST = $HealthReport.NestingDepthHighest LINES_OF_CODE_AVERAGE = $HealthReport.LinesOfCodeAverage COMPLEXITY_AVERAGE = $HealthReport.ComplexityAverage NESTING_DEPTH_AVERAGE = $HealthReport.NestingDepthAverage MAINTAINABILITY_TABLE_ROWS = $TableData.MaintainabilityRows NUMBER_OF_TESTS = $HealthReport.NumberOfTests NUMBER_OF_FAILED_TESTS = $HealthReport.NumberOfFailedTests NUMBER_OF_PASSED_TESTS = $HealthReport.NumberOfPassedTests COMMANDS_MISSED = $HealthReport.CommandsMissedTotal FAILED_TESTS_TABLE_ROWS = $TableData.FailedTestsRows COVERAGE_TABLE_ROWS = $TableData.CoverageRows JS_CONTENT = $JsContent } $HtmlContent = Set-PSCodeHealthPlaceholdersValue -TemplatePath "$PSScriptRoot\..\Assets\HealthReport.html" -PlaceholdersData $HtmlPlaceholders $ComplianceParams = @{ HealthReport = $HealthReport } If ( $PSBoundParameters.ContainsKey('CustomSettingsPath') ) { $ComplianceParams.Add('CustomSettingsPath', $CustomSettingsPath) } $OverallCompliance = Test-PSCodeHealthCompliance @ComplianceParams If ( $Null -eq $FunctionHealthRecords ) { $PerFunctionCompliance = $Null } Else { $PerFunctionCompliance = $FunctionHealthRecords.FunctionName.ForEach({ Test-PSCodeHealthCompliance @ComplianceParams -FunctionName $_ }) } $HtmlColorParams = @{ HealthReport = $HealthReport Compliance = $OverallCompliance PerFunctionCompliance = $PerFunctionCompliance Html = $HtmlContent } $ColoredHtmlContent = Set-PSCodeHealthHtmlColor @HtmlColorParams $Null = New-Item -Path $HtmlReportPath -ItemType File -Force Set-Content -Path $HtmlReportPath -Value $ColoredHtmlContent If ( $PassThru ) { return $HealthReport } } } |