Notices: This section not yet converted to new layout. Download stats are rolling back out.

PSCodeHealth (PowerShell Module)

0.2.26

This package skips automatic verification:

PS 5 is required for this module and it throws an exception if it's not installed.


This package was approved by moderator flcdrg on 5/13/2018.

PSCodeHealth allows you to measure the quality and maintainability of your PowerShell code, based on a variety of metrics related to:

  • Code length
  • Code complexity
  • Code smells, styling issues and violations of best practices
  • Tests and test coverage
  • Comment-based help

It can allow you to ensure that your code is compliant with metrics goals (quality gates). You can use the default (built-in) compliance rules, and you can also customize some (or all) compliance rules to fit your goals.

These features can be leveraged from within your PowerShell release pipeline.

PSCodeHealth can also generate a highly visual HTML report so that you can interpret the results at a glance, and easily share them.

NOTE: This is an automatically updated package. If you find it is out of date by more than a week, please contact the maintainer(s) and let them know the package is no longer updating correctly.

To install PSCodeHealth (PowerShell Module), run the following command from the command line or from PowerShell:

C:\> choco install pscodehealth

To upgrade PSCodeHealth (PowerShell Module), run the following command from the command line or from PowerShell:

C:\> choco upgrade pscodehealth

Files

Hide
  • tools\.skipAutoUninstaller
  • tools\chocolateyBeforeModify.ps1 Show
    $ErrorActionPreference = 'Stop'
    
    $moduleName = $env:ChocolateyPackageName      # this could be different from package name
    Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue
  • tools\chocolateyInstall.ps1 Show
    $ErrorActionPreference = 'Stop'
    
    $toolsDir         = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
    $moduleName       = 'PSCodeHealth'  # this may be different from the package name and different case
    
    if ($PSVersionTable.PSVersion.Major -lt 5) {
        throw "PSCodeHealth module requires a minimum of PowerShell 5."
    }
    
    # module may already be installed outside of Chocolatey
    Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue
    
    $manifestFile = Join-Path -Path $toolsDir -ChildPath "$moduleName\$moduleName.psd1"
    $manifest     = Test-ModuleManifest -Path $manifestFile -WarningAction Ignore -ErrorAction Stop
    $sourcePath = Join-Path -Path $toolsDir -ChildPath "$modulename\*"
    $destPath = Join-Path -Path $env:ProgramFiles -ChildPath "WindowsPowerShell\Modules\$moduleName\$($manifest.Version.ToString())"
    
    Write-Verbose "Creating destination directory '$destPath' for module."
    New-Item -Path $destPath -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
    
    Write-Verbose "Moving '$moduleName' files from '$sourcePath' to '$destPath'."
    Move-Item -Path $sourcePath -Destination $destPath -Force
  • tools\chocolateyUninstall.ps1 Show
    $ErrorActionPreference = 'Stop'
    
    $moduleName = $env:ChocolateyPackageName
    $sourcePath = Join-Path -Path $env:ProgramFiles -ChildPath "WindowsPowerShell\Modules\$moduleName"
    
    Write-Verbose "Removing all version of '$moduleName' from '$sourcePath'."
    Remove-Item -Path $sourcePath -Recurse -Force -ErrorAction SilentlyContinue
    
    if ($PSVersionTable.PSVersion.Major -lt 4) {
        $modulePaths = [Environment]::GetEnvironmentVariable('PSModulePath', 'Machine') -split ';'
    
        Write-Verbose "Removing '$sourcePath' from PSModulePath."
        $newModulePath = $modulePaths | Where-Object { $_ -ne $sourcePath }
    
        [Environment]::SetEnvironmentVariable('PSModulePath', $newModulePath, 'Machine')
        $env:PSModulePath = $newModulePath
    }
  • tools\LICENSE.txt Show
    From: https://github.com/MathieuBuisson/PSCodeHealth/blob/master/LICENSE.md
    
    LICENSE
    
    MIT License
    
    Copyright (c) 2017 Mathieu BUISSON
    
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
    
    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
  • tools\PSCodeHealth\Assets\HealthReport.css
  • tools\PSCodeHealth\Assets\HealthReport.html
  • tools\PSCodeHealth\Assets\HealthReport.js Show
    Chart.pluginService.register({
                afterUpdate: function (chart) {
                    if (chart.config.options.elements.center) {
                        var helpers = Chart.helpers;
                        var centerConfig = chart.config.options.elements.center;
                        var globalConfig = Chart.defaults.global;
                        var ctx = chart.chart.ctx;
    
                        var fontStyle = helpers.getValueOrDefault(centerConfig.fontStyle, globalConfig.defaultFontStyle);
                        var fontFamily = helpers.getValueOrDefault(centerConfig.fontFamily, globalConfig.defaultFontFamily);
    
                        // Figure out the best font size, if one is not specified
                        ctx.save();
                        var fontSize = helpers.getValueOrDefault(centerConfig.minFontSize, 12);
                        var maxFontSize = helpers.getValueOrDefault(centerConfig.maxFontSize, 55);
                        var maxText = helpers.getValueOrDefault(centerConfig.maxText, centerConfig.text);
    
                        do {
                            ctx.font = helpers.fontString(fontSize, fontStyle, fontFamily);
                            var textWidth = ctx.measureText(maxText).width;
    
                            // Check if it fits, is within configured limits and that we are not simply toggling back and forth
                            if (textWidth < chart.innerRadius * 2 && fontSize < maxFontSize)
                                fontSize += 1;
                            else {
                                // Reverse last step
                                fontSize -= 1;
                                break;
                            }
                        } while (true);
                        ctx.restore();
    
                        // save properties
                        chart.center = {
                            font: helpers.fontString(fontSize, fontStyle, fontFamily),
                            fillStyle: helpers.getValueOrDefault(centerConfig.fontColor, globalConfig.defaultFontColor)
                        };
                    }
                },
                afterDraw: function (chart) {
                    if (chart.center) {
                        var centerConfig = chart.config.options.elements.center;
                        var ctx = chart.chart.ctx;
    
                        ctx.save();
                        ctx.font = chart.center.font;
                        ctx.fillStyle = chart.center.fillStyle;
                        ctx.textAlign = 'center';
                        ctx.textBaseline = 'middle';
                        var centerX = (chart.chartArea.left + chart.chartArea.right) / 2;
                        var centerY = (chart.chartArea.top + chart.chartArea.bottom) / 2;
                        ctx.fillText(centerConfig.text, centerX, centerY);
                        ctx.restore();
                    }
                },
            });
    
            function createTestPassRateChart(ctx) {
                var data = {
                    labels: ["Pass","Fail"],
                    datasets: [
                    {
                        data: [{NUMBER_OF_PASSED_TESTS},{NUMBER_OF_FAILED_TESTS}],
                        backgroundColor: ["#a3d48d","#d59595"],
                        hoverBackgroundColor: ["#a3d48d","#d59595"]
                    }]
                };
                var newTestPassRateChart = new Chart(ctx, {
                    type: 'doughnut',
                    data: data,
                    options: {
                        cutoutPercentage: 64,
                        legend: { position: 'top' },
                        elements: {
                            center: {
                                // This evaluates the max length of the text
                                maxText: '99.99%',
                                text: '{TESTS_PASS_RATE}%',
                                fontColor: '#a3d48d',
                                fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
                                fontStyle: 'normal'
                            }
                        }
                    }
                });
            }
            var testPassRateCharts = $(".testPassRateChart");
            for (var i = 0; i < testPassRateCharts.length; i++) {
                createTestPassRateChart(testPassRateCharts[i]);
            }
    
            function createTestCoverageChart(ctx) {
                var data = {
                    labels: ["Covered","Missed"],
                    datasets: [
                    {
                        data: [{TEST_COVERAGE},{CODE_NOT_COVERED}],
                        backgroundColor: ["#a3d48d","#d59595"],
                        hoverBackgroundColor: ["#a3d48d","#d59595"]
                    }]
                };
                var newTestCoverageChart = new Chart(ctx, {
                    type: 'doughnut',
                    data: data,
                    options: {
                        cutoutPercentage: 64,
                        legend: { position: 'top' },
                        elements: {
                            center: {
                                // This evaluates the max length of the text
                                maxText: '99.99%',
                                text: '{TEST_COVERAGE}%',
                                fontColor: '#d59595',
                                fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
                                fontStyle: 'normal'
                            }
                        }
                    }
                });
            }
            var testCoverageCharts = $(".testCoverageChart");
            for (var i = 0; i < testCoverageCharts.length; i++) {
                createTestCoverageChart(testCoverageCharts[i]);
            }
    
            $(document).ready(function(){
                $("td > table").hide();
                var expandCollapseButtons = $(".cell-expand-collapse");
    
                if (expandCollapseButtons.length) {
                    expandCollapseButtons.click(function(){
                        var elementToToggle = $(this).siblings("table");
                        elementToToggle.slideToggle("fast");
                        $(this).text($(this).text() == ' Expand' ? ' Collapse' : ' Expand');
                    });
                }
            });
  • tools\PSCodeHealth\Assets\PSCodeHealthLogo.png
  • tools\PSCodeHealth\Private\Get-ExternalHelpCommand.ps1 Show
    Function Get-ExternalHelpCommand {
        <#
        .SYNOPSIS
            Gets the name of the commands listed in external help files.
        .DESCRIPTION
            Gets the name of the commands listed in external (MAML-formatted) help files.
        
        .PARAMETER Path
            Root directory where the function looks for external help files.  
            The function looks for files with a name ending with "-help.xml" in a "en-US" subdirectory.
        
        .EXAMPLE
            PS C:\> Get-ExternalHelpCommand -Path 'C:\GitRepos\MyModule'
        
            Gets the name of all the commands listed in external help files found in the folder : C:\GitRepos\MyModule\.
            
        .NOTES
            https://info.sapien.com/index.php/scripting/scripting-help/writing-xml-help-for-advanced-functions
        #>
        [CmdletBinding()]
        Param (
            [Parameter(Position=0, Mandatory)]
            [ValidateScript({ Test-Path $_ -PathType Container })]
            [string[]]$Path
        )
    
        $LocaleFolder = Get-ChildItem -Path $Path -Directory -Filter 'en-US' -Recurse
        If ( $LocaleFolder ) {
            $MamlHelpFile = Get-ChildItem -Path $LocaleFolder.FullName -File -Filter '*-help.xml'
            If ( $MamlHelpFile ) {
                Try {
                    [xml]$Maml = Get-Content -Path $MamlHelpFile.FullName
                }
                Catch {
                    Write-Warning "The content of the file $($MamlHelpFile.FullName) was not valid XML"
                    return
                }
                return $Maml.helpItems.command.details.name
            }
        }
    }
  • tools\PSCodeHealth\Private\Get-PowerShellFile.ps1 Show
    Function Get-PowerShellFile {
    <#
    .SYNOPSIS
        Gets all PowerShell files in the specified directory.
    .DESCRIPTION
        Gets all PowerShell files (.ps1, .psm1 and .psd1) in the specified directory.
        The following PowerShell-related files are excluded : format data files, type data files and files containing Pester Tests.
    
    .PARAMETER Path
        To specify the path of the directory to search.
    
    .PARAMETER Recurse
        To search the Path directory and all subdirectories recursively.
    
    .PARAMETER Exclude
        To specify file(s) to exclude. The value of this parameter qualifies the Path parameter.
        Enter a path element or pattern, such as *example*. Wildcards are permitted.
    
    .EXAMPLE
        PS C:\> Get-PowerShellFile -Path C:\GitRepos\MyModule\ -Recurse
    
        Gets all PowerShell files in the directory C:\GitRepos\MyModule\ and any subdirectories.
    
    .EXAMPLE
        PS C:\> Get-PowerShellFile -Path C:\GitRepos\MyModule\ -Recurse -Exclude "*example*"
    
        Gets PowerShell files in the directory C:\GitRepos\MyModule\ and any subdirectories, except for files containing "example" in their name.
    
    .OUTPUTS
        System.String
    
    #>
        [CmdletBinding()]
        [OutputType([String[]])]
    
        Param (
            [Parameter(Position=0, Mandatory, ValueFromPipeline=$True)]
            [ValidateScript({ Test-Path $_ -PathType Container })]
            [string]$Path,
    
            [switch]$Recurse,
    
            [Parameter(Mandatory=$False)]
            [string[]]$Exclude
        )
    
        $ChildItems = Get-ChildItem @PSBoundParameters -File
        $PowerShellFilter = { $_.Name -like '*.ps*1' }
        $PowerShellFiles = $ChildItems | Where-Object $PowerShellFilter
    
        Foreach ( $File in $PowerShellFiles ) {
            $FileAst = [System.Management.Automation.Language.Parser]::ParseFile($File.FullName, [ref]$Null, [ref]$Null)
            $Predicate = {
                Param($Ast) $Ast -is [System.Management.Automation.Language.CommandAst] -and
                $Ast.GetCommandName() -eq 'Describe' -and
                $Ast.CommandElements.StaticType -contains [scriptblock]
            }
            $DescribeBlock = $FileAst.Find($Predicate, $False)
            If ( -not($DescribeBlock) ) {
                $File.FullName
            }
        }
    }
  • tools\PSCodeHealth\Private\HtmlReport\New-PSCodeHealthTableData.ps1 Show
    Function New-PSCodeHealthTableData {
    <#
    .SYNOPSIS
        Generate table rows for the HTML report, based on the data contained in a PSCodeHealth.Overall.HealthReport object.  
    
    .DESCRIPTION
        Generate table rows for the HTML report, based on the data contained in a PSCodeHealth.Overall.HealthReport object.  
        This provides the rows for the following tables :  
          - Best Practices (per function)  
          - Maintainability (per function)  
          - Failed Tests Details  
          - Test Coverage (per function)  
    
    .PARAMETER HealthReport
        To specify the input PSCodeHealth.Overall.HealthReport object containing the data.
    
    .EXAMPLE
        New-PSCodeHealthTableData -HealthReport $HealthReport  
    
        This generates the rows for the tables Best Practices, Maintainability, Failed Tests Details and Test Coverage tables, based on the data in $HealthReport.  
    
    .OUTPUTS
        PSCustomObject
    
    .NOTES    
    #>
        [CmdletBinding()]
        [OutputType([PSCustomObject])]
        Param (
            [Parameter(Mandatory, Position=0)]
            [PSTypeName('PSCodeHealth.Overall.HealthReport')]
            [PSCustomObject]$HealthReport
        )
    
        [System.Collections.ArrayList]$BestPracticesRows = @()
        Foreach ( $Function in $HealthReport.FunctionHealthRecords ) {
    
            If ( $Function.ScriptAnalyzerFindings -gt 0 ) {
                [System.Collections.ArrayList]$FindingsDetails = @()
                $Null = $FindingsDetails.Add(@"
    `n                                            <button type="button" class="btn btn-{$($Function.FunctionName)_FINDINGS_DETAILS} btn-sm cell-expand-collapse"> Expand</button>
                                                <table>`n
    "@)
                Foreach ( $Finding in $Function.ScriptAnalyzerResultDetails ) {
    
                    $ScriptName = Split-Path -Path $Function.FilePath -Leaf
                    $FindingDetail = @"
                                                    <tr>
                                                        <td class="{$($Function.FunctionName)_FINDINGS_DETAILS} cell-largeContent">ScriptName : $ScriptName<br>
    Line (in the function) : $($Finding.Line)<br>
    Severity                      : $($Finding.Severity)<br>
    RuleName                      : $($Finding.RuleName)<br>
    Message                       : $($Finding.Message)<br>
                                                        </td>
                                                    </tr>`n
    "@
                    $Null = $FindingsDetails.Add($FindingDetail)
                }
                $CloseTable = @"
                                                </table>`n                                        
    "@
                $Null = $FindingsDetails.Add($CloseTable)
            }
            Else {
                [string]$FindingsDetails = ''
            }
            $Row = @"
                                        <tr>
                                            <td>$($Function.FunctionName)</td>
                                            <td class="{$($Function.FunctionName)_SCRIPTANALYZER_FINDINGS}">$($Function.ScriptAnalyzerFindings)</td>
                                            <td class="{$($Function.FunctionName)_FINDINGS_DETAILS}">$($FindingsDetails)</td>
                                            <td class="{$($Function.FunctionName)_CONTAINS_HELP}">$($Function.ContainsHelp)</td>
                                        </tr>
    "@
            $Null = $BestPracticesRows.Add($Row)
        }
    
        [System.Collections.ArrayList]$MaintainabilityRows = @()
        Foreach ( $Function in $HealthReport.FunctionHealthRecords ) {
    
            $Row = @"
                                        <tr>
                                            <td>$($Function.FunctionName)</td>
                                            <td class="{$($Function.FunctionName)_LINES_OF_CODE_COMPLIANCE}">$($Function.LinesOfCode)</td>
                                            <td class="{$($Function.FunctionName)_COMPLEXITY_COMPLIANCE}">$($Function.Complexity)</td>
                                            <td class="{$($Function.FunctionName)_MAXIMUM_NESTING_DEPTH_COMPLIANCE}">$($Function.MaximumNestingDepth)</td>
                                        </tr>
    "@
            $Null = $MaintainabilityRows.Add($Row)
        }
    
        If ( $HealthReport.NumberOfFailedTests -gt 0 ) {
            [System.Collections.ArrayList]$FailedTestsRows = @()
            Foreach ( $FailedTest in $HealthReport.FailedTestsDetails ) {
                
                $Row = @"
                                        <tr>
                                            <td>$($FailedTest.File)</td>
                                            <td>$($FailedTest.Line)</td>
                                            <td>$($FailedTest.Describe)</td>
                                            <td>$($FailedTest.TestName)</td>
                                            <td>$($FailedTest.ErrorMessage)</td>
                                        </tr>
    "@
                $Null = $FailedTestsRows.Add($Row)
            }
        }
        Else {
            [string]$FailedTestsRows = ''
        }
    
        [System.Collections.ArrayList]$CoverageRows = @()
        Foreach ( $Function in $HealthReport.FunctionHealthRecords ) {
    
            $Row = @"
                                        <tr>
                                            <td>$($Function.FunctionName)</td>
                                            <td class="{$($Function.FunctionName)_TEST_COVERAGE_COMPLIANCE}">$($Function.TestCoverage)</td>
                                            <td class="{$($Function.FunctionName)_COMMANDS_MISSED_COMPLIANCE}">$($Function.CommandsMissed)</td>
                                        </tr>
    "@
            $Null = $CoverageRows.Add($Row)
        }
    
        $ObjectProperties = [ordered]@{
            'BestPracticesRows'   = $BestPracticesRows
            'MaintainabilityRows' = $MaintainabilityRows
            'FailedTestsRows'     = $FailedTestsRows
            'CoverageRows'        = $CoverageRows
        }
    
        $CustomObject = New-Object -TypeName PSObject -Property $ObjectProperties
        return $CustomObject
    }
  • tools\PSCodeHealth\Private\HtmlReport\Set-PSCodeHealthHtmlColor.ps1 Show
    Function Set-PSCodeHealthHtmlColor {
    <#
    .SYNOPSIS
        Sets classes to the elements in the HTML report which use color coding to reflect their compliance, and returns the modified HTML.  
    
    .DESCRIPTION
        Sets the class attribute to the elements in the HTML report which use color coding to reflect their compliance.  
        These classes corresponds to CSS declaration blocks to apply the appropriate styling to the elements, in particular the colors.  
        Then, it returns the modified HTML content to the caller.
    
    .PARAMETER HealthReport
        To specify the input PSCodeHealth.Overall.HealthReport object containing the data.
    
    .PARAMETER Compliance
        To input the overall compliance information, based on the current health report and the compliance rules.
    
    .PARAMETER PerFunctionCompliance
        To input the per-function compliance information, based on the functions in the current health report and the compliance rules.
    
    .PARAMETER Html
        To input the original HTML content (containing placeholders to be substituted with the appropriate class values).
    
    .EXAMPLE
        Set-PSCodeHealthHtmlColor -HealthReport $HealthReport -Compliance $OverallCompliance -PerFunctionCompliance $PerFunctionCompliance -Html $HtmlContent  
    
        This sets classes to the elements in the HTML report which use color coding to reflect their compliance and returns the modified HTML content.
    
    .OUTPUTS
        System.String
    
    .NOTES    
    #>
        [CmdletBinding()]
        [OutputType([string[]])]
        Param (
            [Parameter(Mandatory, Position=0)]
            [PSTypeName('PSCodeHealth.Overall.HealthReport')]
            [PSCustomObject]$HealthReport,
    
            [Parameter(Mandatory, Position=1)]
            [PSTypeName('PSCodeHealth.Compliance.Result')]
            [PSCustomObject[]]$Compliance,
    
            [Parameter(Mandatory, Position=2)]
            [AllowNull()]
            [PSTypeName('PSCodeHealth.Compliance.FunctionResult')]
            [PSCustomObject[]]$PerFunctionCompliance,
    
            [Parameter(Mandatory, Position=2)]
            [AllowEmptyString()]
            [string[]]$Html
        )
    
            Function ConvertTo-HtmlClass ($ComplianceResult) {
                Switch ($ComplianceResult) {
                    'Fail' { return 'danger' }
                    'Warning' { return 'warning' }
                    'Pass' { return 'success' }
                    $True { return 'success' }
                    $False { return 'danger' }
                }
            }
    
            Function Get-FindingsHtmlClass ([Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]]$Findings) {
                [string[]]$FindingsSeverity = $Findings.Severity
                If ( $FindingsSeverity -contains 'Error' ) {
                    return 'danger'
                }
                If ( $FindingsSeverity -contains 'Warning' ) {
                    return 'warning'
                }
                If ( $FindingsSeverity -contains 'Information' ) {
                    return 'info'
                }
                return ''
            }
    
            $OverallPlaceholders = @{
                LINES_OF_CODE_TOTAL_COMPLIANCE = ConvertTo-HtmlClass $Compliance.Where({ $_.MetricName -eq 'LinesOfCodeTotal' }).Result
                SCRIPTANALYZER_TOTAL_COMPLIANCE = ConvertTo-HtmlClass $Compliance.Where({ $_.MetricName -eq 'ScriptAnalyzerFindingsTotal' }).Result
                SCRIPTANALYZER_ERRORS_COMPLIANCE = ConvertTo-HtmlClass $Compliance.Where({ $_.MetricName -eq 'ScriptAnalyzerErrors' }).Result
                SCRIPTANALYZER_WARNINGS_COMPLIANCE = ConvertTo-HtmlClass $Compliance.Where({ $_.MetricName -eq 'ScriptAnalyzerWarnings' }).Result
                SCRIPTANALYZER_INFO_COMPLIANCE = ConvertTo-HtmlClass $Compliance.Where({ $_.MetricName -eq 'ScriptAnalyzerInformation' }).Result
                TESTS_PASS_RATE_COMPLIANCE = ConvertTo-HtmlClass $Compliance.Where({ $_.MetricName -eq 'TestsPassRate' }).Result
                TEST_COVERAGE_COMPLIANCE = ConvertTo-HtmlClass $Compliance.Where({ $_.MetricName -eq 'TestCoverage' -and $_.SettingsGroup -eq 'OverallMetrics'}).Result
                SCRIPTANALYZER_AVERAGE_COMPLIANCE = ConvertTo-HtmlClass $Compliance.Where({ $_.MetricName -eq 'ScriptAnalyzerFindingsAverage' }).Result
                LINES_OF_CODE_AVERAGE_COMPLIANCE = ConvertTo-HtmlClass $Compliance.Where({ $_.MetricName -eq 'LinesOfCodeAverage' }).Result
                COMPLEXITY_AVERAGE_COMPLIANCE = ConvertTo-HtmlClass $Compliance.Where({ $_.MetricName -eq 'ComplexityAverage' }).Result
                NESTING_DEPTH_AVERAGE_COMPLIANCE = ConvertTo-HtmlClass $Compliance.Where({ $_.MetricName -eq 'NestingDepthAverage' }).Result
                NUMBER_OF_FAILED_TESTS_COMPLIANCE = ConvertTo-HtmlClass $Compliance.Where({ $_.MetricName -eq 'NumberOfFailedTests' }).Result
                COMMANDS_MISSED_TOTAL_COMPLIANCE = ConvertTo-HtmlClass $Compliance.Where({ $_.MetricName -eq 'CommandsMissedTotal' }).Result
                COMPLEXITY_HIGHEST_COMPLIANCE = ConvertTo-HtmlClass $Compliance.Where({ $_.MetricName -eq 'ComplexityHighest' }).Result
                NESTING_DEPTH_HIGHEST_COMPLIANCE = ConvertTo-HtmlClass $Compliance.Where({ $_.MetricName -eq 'NestingDepthHighest' }).Result
            }
            $HtmlOverall = Set-PSCodeHealthPlaceholdersValue -Html $Html -PlaceholdersData $OverallPlaceholders
    
            $HtmlFunction = $HtmlOverall
            Foreach ( $Function in $HealthReport.FunctionHealthRecords.FunctionName ) {
    
                $FunctionRecord = $HealthReport.FunctionHealthRecords.Where({ $_.FunctionName -eq $Function })
    
                $FunctionPlaceholders = @{
                    "$($Function)_SCRIPTANALYZER_FINDINGS" = ConvertTo-HtmlClass $PerFunctionCompliance.Where({ $_.FunctionName -eq $Function -and $_.MetricName -eq 'ScriptAnalyzerFindings' }).Result
                    "$($Function)_CONTAINS_HELP" = ConvertTo-HtmlClass $FunctionRecord.ContainsHelp
                    "$($Function)_LINES_OF_CODE_COMPLIANCE" = ConvertTo-HtmlClass $PerFunctionCompliance.Where({ $_.FunctionName -eq $Function -and $_.MetricName -eq 'LinesOfCode' }).Result
                    "$($Function)_COMPLEXITY_COMPLIANCE" = ConvertTo-HtmlClass $PerFunctionCompliance.Where({ $_.FunctionName -eq $Function -and $_.MetricName -eq 'Complexity' }).Result
                    "$($Function)_MAXIMUM_NESTING_DEPTH_COMPLIANCE" = ConvertTo-HtmlClass $PerFunctionCompliance.Where({ $_.FunctionName -eq $Function -and $_.MetricName -eq 'MaximumNestingDepth' }).Result
                    "$($Function)_TEST_COVERAGE_COMPLIANCE" = ConvertTo-HtmlClass $PerFunctionCompliance.Where({ $_.FunctionName -eq $Function -and $_.MetricName -eq 'TestCoverage' }).Result
                    "$($Function)_COMMANDS_MISSED_COMPLIANCE" = ConvertTo-HtmlClass $PerFunctionCompliance.Where({ $_.FunctionName -eq $Function -and $_.MetricName -eq 'CommandsMissed' }).Result
                    "$($Function)_FINDINGS_DETAILS" = Get-FindingsHtmlClass -Findings $FunctionRecord.ScriptAnalyzerResultDetails
                }
                $HtmlFunction = Set-PSCodeHealthPlaceholdersValue -Html $HtmlFunction -PlaceholdersData $FunctionPlaceholders
            }
            return $HtmlFunction
    }
  • tools\PSCodeHealth\Private\HtmlReport\Set-PSCodeHealthPlaceholdersValue.ps1 Show
    Function Set-PSCodeHealthPlaceholdersValue {
    <#
    .SYNOPSIS
        Replaces Placeholders in template files with their specified value.  
    
    .DESCRIPTION
        Replaces Placeholders in template files with their specified string value and outputs the new content with the replaced value.  
    
    .PARAMETER TemplatePath
        Path of the template file containing placeholders to replace.  
    
    .PARAMETER PlaceholdersData
        Hashtable with a key-value pair for each placeholder. The key is corresponds to the name of the placeholder to replace and the value corresponds to its string value.  
    
    .EXAMPLE
        PS C:\> $PlaceholdersData = @{
            REPORT_TITLE = $HealthReport.ReportTitle
            ANALYZED_PATH = $HealthReport.AnalyzedPath
        }
        PS C:\> Set-PSCodeHealthPlaceholdersValue -TemplatePath '.\HealthReportTemplate.html' -PlaceholdersData $PlaceholdersData
    
        Returns the content of the template file with the placeholders 'REPORT_TITLE' and 'ANALYZED_PATH' substituted by the string values specified in the hashtable $PlaceholdersData.  
    
    .OUTPUTS
        System.String
    
    .NOTES    
    #>
        [CmdletBinding(DefaultParameterSetName = 'File')]
        [OutputType([string[]])]
        Param (
            [Parameter(Position=0, Mandatory, ParameterSetName='File')]
            [ValidateScript({ Test-Path $_ -PathType Leaf })]
            [string]$TemplatePath,
    
            [Parameter(Position=1, Mandatory)]
            [Hashtable]$PlaceholdersData,
    
            [Parameter(Position=0, Mandatory, ParameterSetName='Html')]
            [AllowEmptyString()]
            [string[]]$Html
        )
    
        If ( $PSCmdlet.ParameterSetName -ne 'Html' ) {
            $Html = Get-Content -Path $TemplatePath
        }
    
        Foreach ( $Placeholder in $PlaceholdersData.GetEnumerator() ) {
            $PlaceholderPattern = '{{{0}}}' -f $Placeholder.Key
    
            # Handling values containing a collection
            $PlaceholderValue = If ( $($Placeholder.Value).Count -ne 1 ) { $Placeholder.Value | Out-String } Else { $Placeholder.Value }
            $Html = $Html.ForEach('Replace', $PlaceholderPattern, [string]$PlaceholderValue)
        }
        $Html
    }
  • tools\PSCodeHealth\Private\Merge-PSCodeHealthSetting.ps1 Show
    Function Merge-PSCodeHealthSetting {
    <#
    .SYNOPSIS
        Merges user-defined settings (metrics thresholds, etc...) into the default PSCodeHealth settings.
    
    .DESCRIPTION
        Merges user-defined settings (metrics thresholds, etc...) into the default PSCodeHealth settings.  
        The default PSCodeHealth settings are stored in PSCodeHealthSettings.json, but user-defined custom settings can override these defaults.  
        The custom settings are stored in JSON format in a file (similar to PSCodeHealthSettings.json).
        Any setting specified in the custom settings file override the default, and settings not specified in the custom settings file will use the defaults from PSCodeHealthSettings.json.  
    
    .PARAMETER DefaultSettings
        PSCustomObject converted from the JSON data in PSCodeHealthSettings.json.
    
    .PARAMETER CustomSettings
        PSCustomObject converted from the JSON data in a user-defined custom settings file.
    
    .OUTPUTS
        System.Management.Automation.PSCustomObject
    #>
        [CmdletBinding()]
        [OutputType([PSCustomObject])]
        Param(
            [Parameter(Mandatory,Position=0)]
            [PSCustomObject]$DefaultSettings,
    
            [Parameter(Mandatory,Position=1)]
            [PSCustomObject]$CustomSettings
        )
    
        # Checking if $CustomSettings contains something
        $ContainsSettings = $CustomSettings | Get-Member -MemberType Properties
        If ( -not($ContainsSettings) ) {
            Write-VerboseOutput -Message 'Custom settings do not contain any data, the resulting settings will be the defaults.'
            return $DefaultSettings
        }
    
        $ContainsFunctionHealthRecordSettings = 'PerFunctionMetrics' -in $ContainsSettings.Name
        $ContainsOverallHealthReportSettings = 'OverallMetrics' -in $ContainsSettings.Name
    
        If ( -not($ContainsFunctionHealthRecordSettings) -and -not($ContainsOverallHealthReportSettings) ) {
            Write-Warning -Message 'Custom settings do not contain any of the settings groups expected by PSCodeHealth.'
            return $DefaultSettings
        }
    
        If ( $ContainsFunctionHealthRecordSettings) {
            $CustomFunctionSettings = $CustomSettings.PerFunctionMetrics | Where-Object { $_ }
    
            # Casting to a list in case we need to add elements to it
            $DefaultFunctionSettings = ($DefaultSettings.PerFunctionMetrics | Where-Object { $_ }) -as [System.Collections.ArrayList]
                    
            Foreach ( $CustomFunctionSetting in $CustomFunctionSettings ) {
                $MetricName = ($CustomFunctionSetting | Get-Member -MemberType Properties).Name
                Write-VerboseOutput -Message "Processing custom settings for metric : $MetricName"
    
                $DefaultFunctionSetting = $DefaultFunctionSettings | Where-Object { $_.$($MetricName) }
                If ( $DefaultFunctionSetting ) {
                    Write-VerboseOutput -Message "The setting '$MetricName' is present in the default settings, overriding it."
                    $DefaultFunctionSetting.$($MetricName) = $CustomFunctionSetting.$($MetricName)
                }
                Else {
                    Write-VerboseOutput -Message "The setting '$MetricName' is absent from the default settings, adding it."
                    $Null = $DefaultFunctionSettings.Add($CustomFunctionSetting)
                }
            }
        }
    
        If ( $ContainsOverallHealthReportSettings ) {
            $CustomOverallSettings = $CustomSettings.OverallMetrics | Where-Object { $_ }
    
            # Casting to a list in case we need to add elements to it
            $DefaultOverallSettings = ($DefaultSettings.OverallMetrics | Where-Object { $_ }) -as [System.Collections.ArrayList]
    
            Foreach ( $CustomOverallSetting in $CustomOverallSettings ) {
                $MetricName = ($CustomOverallSetting | Get-Member -MemberType Properties).Name
                Write-VerboseOutput -Message "Processing custom settings for metric : $MetricName"
    
                $DefaultOverallSetting = $DefaultOverallSettings | Where-Object { $_.$($MetricName) }
                If ( $DefaultOverallSetting ) {
                    Write-VerboseOutput -Message "The setting '$MetricName' is present in the default settings, overriding it."
                    $DefaultOverallSetting.$($MetricName) = $CustomOverallSetting.$($MetricName)
                }
                Else {
                    Write-VerboseOutput -Message "The setting '$MetricName' is absent from the default settings, adding it."
                    $Null = $DefaultOverallSettings.Add($CustomOverallSetting)
                }
            }
        }
        $MergedSettingsProperties = [ordered]@{
            PerFunctionMetrics = $DefaultFunctionSettings
            OverallMetrics = $DefaultOverallSettings
        }
        $MergedSettings = New-Object -TypeName PSCustomObject -Property $MergedSettingsProperties
        return $MergedSettings
    }
  • tools\PSCodeHealth\Private\Metrics\Get-FunctionDefinition.ps1 Show
    Function Get-FunctionDefinition {
    <#
    .SYNOPSIS
        Gets all the function definitions in the specified files.
    .DESCRIPTION
        Gets all the function definitions (including private functions but excluding nested functions) in the specified PowerShell file.
    
    .PARAMETER Path
        To specify the path of the file to analyze.
    
    .EXAMPLE
        PS C:\> Get-FunctionDefinition -Path C:\GitRepos\MyModule\MyModule.psd1
    
        Gets all function definitions in the module specified by its manifest, as FunctionDefinitionAst objects.
    
    .OUTPUTS
        System.Management.Automation.Language.FunctionDefinitionAst
    
    .NOTES
        
    #>
        [CmdletBinding()]
        [OutputType([System.Management.Automation.Language.FunctionDefinitionAst[]])]
        Param (
            [Parameter(Position=0, Mandatory, ValueFromPipeline=$True)]
            [ValidateScript({ Test-Path $_ -PathType Leaf })]
            [string[]]$Path
        )
        Process {
            Foreach ( $PowerShellFile in $Path ) {
                Write-VerboseOutput -Message "Parsing file : $PowerShellFile"
    
                $PowerShellFile = (Resolve-Path -Path $PowerShellFile).Path
                $FileAst = [System.Management.Automation.Language.Parser]::ParseFile($PowerShellFile, [ref]$Null, [ref]$Null)
                
                $AstToInclude = [System.Management.Automation.Language.FunctionDefinitionAst]
                # Excluding class methods, since we don't support classes
                $AstToExclude = [System.Management.Automation.Language.FunctionMemberAst]
    
                $Predicate = { $args[0] -is $AstToInclude -and $args[0].Parent -isnot $AstToExclude }
                $FileFunctions = $FileAst.FindAll($Predicate, $False)
                If ( $FileFunctions ) {
                    Foreach ( $FunctionName in $FileFunctions.Name ) {
                        Write-VerboseOutput -Message "Found function : $FunctionName"
                    }
                }
                $FileFunctions
            }
        }
    }
  • tools\PSCodeHealth\Private\Metrics\Get-FunctionLinesOfCode.ps1 Show
    Function Get-FunctionLinesOfCode {
    <#
    .SYNOPSIS
        Gets the number of lines in the specified function definition (excluding comments).
    .DESCRIPTION
        Gets the number of lines of code in the specified function definition specified as a [System.Management.Automation.Language.FunctionDefinitionAst].
        The single line comments, multiple lines comments and comment-based help are not executable code, so they are excluded.
    
    .PARAMETER FunctionDefinition
        To specify the function definition to analyze.
    
    .EXAMPLE
        PS C:\> Get-FunctionLinesOfCode -FunctionDefinition $MyFunctionAst
    
        Returns the number of lines of code in the specified function definition.
    
    .OUTPUTS
        System.Int32
    
    .NOTES
        
    #>
        [CmdletBinding()]
        [OutputType([System.Int32])]
        Param (
            [Parameter(Position=0, Mandatory)]
            [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionDefinition
        )
        
        $FunctionText = $FunctionDefinition.Extent.Text
        Write-VerboseOutput -Message "Function name : $($FunctionDefinition.Name)"
    
        $AstTokens = [System.Management.Automation.PSParser]::Tokenize($FunctionText, [ref]$Null)
        $NoCommentTokens = $AstTokens.Where({ $_.Type -ne 'Comment' })
    
        # Substracting 1 from the number of lines if the last token is a NewLine
        [System.Int32]$NumberofLinesToSubstract = If ( $NoCommentTokens[-1].Type -eq 'NewLine' ) { 1 } Else { 0 }
        Write-VerboseOutput -Message "Number of lines to substract : $($NumberofLinesToSubstract)."
    
        [System.Int32]$NumberOfLines = ($NoCommentTokens.Where({ $_.Type -eq 'NewLine' })).Count - $NumberofLinesToSubstract
        return $NumberOfLines
    }
  • tools\PSCodeHealth\Private\Metrics\Get-FunctionScriptAnalyzerResult.ps1 Show
    Function Get-FunctionScriptAnalyzerResult {
    <#
    .SYNOPSIS
        Gets the best practices violations details in the specified function definition, using PSScriptAnalyzer.
    .DESCRIPTION
        Gets the best practices violations details in the specified function definition specified as a [System.Management.Automation.Language.FunctionDefinitionAst].
        It uses the PSScriptAnalyzer PowerShell module.
    
    .PARAMETER FunctionDefinition
        To specify the function definition to analyze.
    
    .EXAMPLE
        PS C:\> Get-FunctionScriptAnalyzerResult -FunctionDefinition $MyFunctionAst
    
        Returns the best practices violations details (PSScriptAnalyzer results) in the specified function definition.
    
    .OUTPUTS
        Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord
    
    .NOTES
        
    #>
        [CmdletBinding()]
        [OutputType([Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord])]
        Param (
            [Parameter(Position=0, Mandatory)]
            [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionDefinition
        )
        
        $Results = Invoke-ScriptAnalyzer -ScriptDefinition $FunctionDefinition.Extent.Text -Verbose:$False
        return $Results
    }
  • tools\PSCodeHealth\Private\Metrics\Get-FunctionTestCoverage.ps1 Show
    Function Get-FunctionTestCoverage {
    <#
    .SYNOPSIS
        Gets test coverage information for the specified function.
    
    .DESCRIPTION
        Gets test coverage information for the specified function. This includes 2 pieces of information :  
          - Code coverage percentage (lines of code that are exercized by unit tests)  
          - Missed Commands (lines of codes or commands not being exercized by unit tests)  
    
        It uses Pester with its CodeCoverage parameter.
    
    .PARAMETER FunctionDefinition
        To specify the function definition to analyze.
    
    .PARAMETER TestsPath
        To specify the file or directory where the Pester tests are located.
        If a directory is specified, the directory and all subdirectories will be searched recursively for tests.
        If not specified, the directory of the file containing the specified function, and all subdirectories will be searched for tests.
    
    .EXAMPLE
        PS C:\> Get-FunctionTestCoverage -FunctionDefinition $MyFunctionAst -TestsPath $MyModule.ModuleBase
    
        Gets test coverage information for the function $MyFunctionAst given the tests found in the module's parent directory.
    
    .OUTPUTS
        PSCodeHealth.Function.TestCoverageInfo
    
    .NOTES
        
    #>
        [CmdletBinding()]
        [OutputType([PSCustomObject])]
        Param (
            [Parameter(Position=0, Mandatory)]
            [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionDefinition,
    
            [Parameter(Position=1, Mandatory=$False)]
            [ValidateScript({ Test-Path $_ })]
            [string]$TestsPath
        )
    
        [string]$SourcePath = $FunctionDefinition.Extent.File
        $FunctionName = $FunctionDefinition.Name
        Write-VerboseOutput -Message "The function [$FunctionName] comes from the file :  $SourcePath"
    
        If ( -not $TestsPath ) {
            $TestsPath = Split-Path -Path $SourcePath -Parent
        }
    
        # Find all the files under the test path that contain the function name
        $Tests = Get-ChildItem -Path $TestsPath -Recurse -Filter *.tests.ps1 |
        Select-String -Pattern $FunctionName |
        Where-Object { $_.Line -notmatch 'Describe|Context|It |Mock ' } |
        Select-Object -ExpandProperty Path -Unique
    
        # Invoke-Pester didn't have the "Show" parameter prior to version 4.x
        $SuppressOutput = If ((Get-Module -Name Pester).Version.Major -lt 4) { @{Quiet = $True} } Else { @{Show = 'None'} }
    
        $TestsResult = Invoke-Pester -Script $Tests -CodeCoverage @{ Path = $SourcePath; Function = $FunctionName } -PassThru -Verbose:$False @SuppressOutput
    
        If ( $TestsResult.CodeCoverage ) {
            $CodeCoverage = $TestsResult.CodeCoverage
            $CommandsFound = $CodeCoverage.NumberOfCommandsAnalyzed
            Write-VerboseOutput -Message "Number of commands found in the function : $($CommandsFound)"
    
            # To prevent any "Attempted to divide by zero" exceptions
            If ( $CommandsFound -ne 0 ) {
                $Commandsexercised = $CodeCoverage.NumberOfCommandsExecuted
                Write-VerboseOutput -Message "Number of commands exercized in the tests : $($CommandsExercised)"
                [System.Double]$CodeCoveragePerCent = [math]::Round(($CommandsExercised / $CommandsFound) * 100, 2)
            }
            Else {
                [System.Double]$CodeCoveragePerCent = 0
            }
    
            $ObjectProperties = [ordered]@{
                'CodeCoveragePerCent'         = $CodeCoveragePerCent
                'CommandsMissed'              = $CodeCoverage.MissedCommands
            }
            $CustomObject = New-Object -TypeName PSObject -Property $ObjectProperties
            $CustomObject.psobject.TypeNames.Insert(0, 'PSCodeHealth.Function.TestCoverageInfo')
            return $CustomObject
        }
    }
  • tools\PSCodeHealth\Private\Metrics\Get-SwitchCombination.ps1 Show
    Function Get-SwitchCombination {
    <#
    .SYNOPSIS
        Calculates the number of additional code paths, given the number of Switch clauses which don't contain a Break statement.
    
    .DESCRIPTION
        Calculates the number of additional code paths, given the number of Switch clauses which don't contain a Break statement.  
        The formula is 2 to the power of the input integer. This is based on :
        https://math.stackexchange.com/questions/161565/what-is-the-total-number-of-combinations-of-5-items-together-when-there-are-no-d
        But we don't substract 1, because the case where none of the Switch clause apply is a possible code path which should be included.
    
    .PARAMETER Integer
        The number of clauses in a given Switch statement which don't contain a Break statement.
    
    .EXAMPLE
        PS C:\> Get-SwitchCombination -Integer 3
    
        Calculates the number of additional code paths due to 3 clauses which don't contain a Break statement in a Switch statement.
    
    .OUTPUTS
        System.Int32
    
    .NOTES
        https://math.stackexchange.com/a/161568
    #>
        [CmdletBinding()]
        [OutputType([System.Int32])]
        Param(
            [Parameter(Position=0, Mandatory)]
            [System.Int32]$Integer
        )
    
        $CombinationsTotal = If ( $Integer -le 1 ) { $Integer } Else { [System.Math]::Pow(2,$Integer) }
    
        If ( $CombinationsTotal -ge [System.Int32]::MaxValue ) {
            return [System.Int32]::MaxValue
        }
        return ($CombinationsTotal -as [System.Int32])
    }
  • tools\PSCodeHealth\Private\Metrics\Measure-FunctionComplexity.ps1 Show
    Function Measure-FunctionComplexity {
    <#
    .SYNOPSIS
        Measures the code complexity.
    .DESCRIPTION
        Measures the code complexity, in the specified function definition.
        This complexity is measured according to the Cyclomatic complexity.
        Cyclomatic complexity counts the number of possible paths through a given section of code.
        The number of possible paths depends on the number of conditional logic constructs, because conditional logic constructs are where the flow of execution branches out to one or more different path(s).
    
    .PARAMETER FunctionDefinition
        To specify the function definition to analyze.
    
    .EXAMPLE
        PS C:\> Measure-FunctionComplexity -FunctionDefinition $MyFunctionAst
    
        Gets the number of additional code paths due to While statements in the specified function definition.
    
    .OUTPUTS
        System.Int32
    
    .NOTES
        For more information on Cyclomatic complexity, please refer to the following article
        https://en.wikipedia.org/wiki/Cyclomatic_complexity
    
        A simple example of measuring the Cyclomatic complexity of a piece od code can be found here :
        https://www.tutorialspoint.com/software_testing_dictionary/cyclomatic_complexity.htm
    #>
        [CmdletBinding()]
        [OutputType([System.Int32])]
        Param (
            [Parameter(Position=0, Mandatory)]
            [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionDefinition
        )
        # Default complexity value for code which contains no branching statement (1 code path)
        [int]$DefaultComplexity = 1
    
        $ForPaths = Measure-FunctionForCodePath -FunctionDefinition $FunctionDefinition
        Write-VerboseOutput -Message "Number of code paths due to For loops : $($ForPaths)"
    
        $IfPaths = Measure-FunctionIfCodePath -FunctionDefinition $FunctionDefinition
        Write-VerboseOutput -Message "Number of code paths due to If and ElseIf statements : $($IfPaths)"
    
        $LogicalOpPaths = Measure-FunctionLogicalOpCodePath -FunctionDefinition $FunctionDefinition
        Write-VerboseOutput -Message "Number of code paths due to logical operators : $($LogicalOpPaths)"
    
        $SwitchPaths = Measure-FunctionSwitchCodePath -FunctionDefinition $FunctionDefinition
        Write-VerboseOutput -Message "Number of code paths due to Switch statements : $($SwitchPaths)"
    
        $TrapCatchPaths = Measure-FunctionTrapCatchCodePath -FunctionDefinition $FunctionDefinition
        Write-VerboseOutput -Message "Number of code paths due to Trap statements and Catch clauses : $($TrapCatchPaths)"
    
        $WhilePaths = Measure-FunctionWhileCodePath -FunctionDefinition $FunctionDefinition
        Write-VerboseOutput -Message "Number of code paths due to While loops : $($WhilePaths)"
    
        [int]$TotalComplexity = $DefaultComplexity + $ForPaths + $IfPaths + $LogicalOpPaths + $SwitchPaths + $TrapCatchPaths + $WhilePaths
        return $TotalComplexity
    }
  • tools\PSCodeHealth\Private\Metrics\Measure-FunctionForCodePath.ps1 Show
    Function Measure-FunctionForCodePath {
    <#
    .SYNOPSIS
        Gets the number of additional code paths due to For loops.
    .DESCRIPTION
        Gets the number of additional code paths due to For loops (where the For statement contains a condition), in the specified function definition.
    
    .PARAMETER FunctionDefinition
        To specify the function definition to analyze.
    
    .EXAMPLE
        PS C:\> Measure-FunctionForCodePath -FunctionDefinition $MyFunctionAst
    
        Gets the number of additional code paths due to for loops in the specified function definition.
    
    .OUTPUTS
        System.Int32
    
    .NOTES
        
    #>
        [CmdletBinding()]
        [OutputType([System.Int32])]
        Param (
            [Parameter(Position=0, Mandatory)]
            [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionDefinition
        )
        
        $FunctionText = $FunctionDefinition.Extent.Text
    
        # Converting the function definition to a generic ScriptBlockAst because the FindAll method of FunctionDefinitionAst object work strangely
        $FunctionAst = [System.Management.Automation.Language.Parser]::ParseInput($FunctionText, [ref]$null, [ref]$null)
        $ForStatements = $FunctionAst.FindAll({ $args[0] -is [System.Management.Automation.Language.ForStatementAst] }, $True)
    
        # Taking into account the rare cases where For statements don't contain a condition
        $ConditionalForStatements = $ForStatements | Where-Object Condition
        
        If ( -not($ConditionalForStatements) ) {
            return [int]0
        }
        return $ConditionalForStatements.Count
    }
  • tools\PSCodeHealth\Private\Metrics\Measure-FunctionIfCodePath.ps1 Show
    Function Measure-FunctionIfCodePath {
    <#
    .SYNOPSIS
        Gets the number of additional code paths due to If statements.
    .DESCRIPTION
        Gets the number of additional code paths due to If statements (including If/Else and If/ElseIf/Else statements), in the specified function definition.
    
    .PARAMETER FunctionDefinition
        To specify the function definition to analyze.
    
    .EXAMPLE
        PS C:\> Measure-FunctionIfCodePath -FunctionDefinition $MyFunctionAst
    
        Gets the number of additional code paths due to If statements in the specified function definition.
    
    .OUTPUTS
        System.Int32
    
    .NOTES
        
    #>
        [CmdletBinding()]
        [OutputType([System.Int32])]
        Param (
            [Parameter(Position=0, Mandatory)]
            [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionDefinition
        )
        
        $FunctionText = $FunctionDefinition.Extent.Text
    
        # Converting the function definition to a generic ScriptBlockAst because the FindAll method of FunctionDefinitionAst object work strangely
        $FunctionAst = [System.Management.Automation.Language.Parser]::ParseInput($FunctionText, [ref]$null, [ref]$null)
        $IfStatements = $FunctionAst.FindAll({ $args[0] -is [System.Management.Automation.Language.IfStatementAst] }, $True)
    
        If ( -not($IfStatements) ) {
            return [int]0
        }
        # If and ElseIf clauses are creating an additional path, not Else clauses
        return $IfStatements.Clauses.Count
    }
  • tools\PSCodeHealth\Private\Metrics\Measure-FunctionLogicalOpCodePath.ps1 Show
    Function Measure-FunctionLogicalOpCodePath {
    <#
    .SYNOPSIS
        Gets the number of additional code paths due to Switch statements.
    .DESCRIPTION
        Gets the number of additional code paths due to Switch statements, in the specified function definition.
    
    .PARAMETER FunctionDefinition
        To specify the function definition to analyze.
    
    .EXAMPLE
        PS C:\> Measure-FunctionLogicalOpCodePath -FunctionDefinition $MyFunctionAst
    
        Gets the number of additional code paths due to Switch statements in the specified function definition.
    
    .OUTPUTS
        System.Int32
    
    .NOTES
        
    #>
        [CmdletBinding()]
        [OutputType([System.Int32])]
        Param (
            [Parameter(Position=0, Mandatory)]
            [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionDefinition
        )
        
        $FunctionText = $FunctionDefinition.Extent.Text
        $Tokens = $Null
        $Null = [System.Management.Automation.Language.Parser]::ParseInput($FunctionText, [ref]$Tokens, [ref]$Null)
        $LogicalOperators = $Tokens.Where({$_.Kind.ToString() -In 'And','Or','Xor'})
    
        If ( -not($LogicalOperators) ) {
            return [int]0
        }
        return $LogicalOperators.Count
    }
  • tools\PSCodeHealth\Private\Metrics\Measure-FunctionMaxNestingDepth.ps1 Show
    Function Measure-FunctionMaxNestingDepth {
    <#
    .SYNOPSIS
        Gets the depth of the most deeply nested statement in the function.
    .DESCRIPTION
        Gets the depth of the most deeply nested statement in the function.
        Measuring the maximum nesting depth in a function is a way of evaluating its complexity.
    
    .PARAMETER FunctionDefinition
        To specify the function definition to analyze.
    
    .EXAMPLE
        PS C:\> Measure-FunctionMaxNestingDepth -FunctionDefinition $MyFunctionAst
    
        Gets the depth of the most deeply nested statement in the specified function definition.
    
    .OUTPUTS
        System.Int32
    
    .LINK
        Additional information on why maximum nesting depth is an interesting measure of code complexity :
        https://www.cqse.eu/en/blog/mccabe-cyclomatic-complexity/
        
    #>
        [CmdletBinding()]
        [OutputType([System.Int32])]
        Param (
            [Parameter(Position=0, Mandatory)]
            [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionDefinition
        )
    
        $FunctionText = $FunctionDefinition.Extent.Text
        $Tokens = $Null
        $Null = [System.Management.Automation.Language.Parser]::ParseInput($FunctionText, [ref]$Tokens, [ref]$Null)
    
        [System.Collections.ArrayList]$NestingDepthValues = @()
        [System.Int32]$NestingDepth = 0
        [System.Collections.ArrayList]$CurlyBraces = $Tokens.Where({ $_.Kind -in 'AtCurly','LCurly','RCurly' })
    
        # Removing the first opening curly and the last closing curly because they belong to the function itself
        $CurlyBraces.RemoveAt(0)
        $CurlyBraces.RemoveAt(($CurlyBraces.Count - 1))
        If ( -not $CurlyBraces ) {
            return $NestingDepth
        }
    
        Foreach ( $CurlyBrace in $CurlyBraces ) {
    
            If ( $CurlyBrace.Kind -in 'AtCurly','LCurly' ) {
                $NestingDepth++
            }
            ElseIf ( $CurlyBrace.Kind -eq 'RCurly' ) {
                $NestingDepth--
            }
            $Null = $NestingDepthValues.Add($NestingDepth)
        }
            Write-VerboseOutput -Message "Number of nesting depth values : $($NestingDepthValues.Count)"
            $MaxDepthValue = ($NestingDepthValues | Measure-Object -Maximum).Maximum -as [System.Int32]
            return $MaxDepthValue
    }
  • tools\PSCodeHealth\Private\Metrics\Measure-FunctionSwitchCodePath.ps1 Show
    Function Measure-FunctionSwitchCodePath {
    <#
    .SYNOPSIS
        Gets the number of additional code paths due to Switch statements.
    .DESCRIPTION
        Gets the number of additional code paths due to Switch statements, in the specified function definition.
    
    .PARAMETER FunctionDefinition
        To specify the function definition to analyze.
    
    .EXAMPLE
        PS C:\> Measure-FunctionSwitchCodePath -FunctionDefinition $MyFunctionAst
    
        Gets the number of additional code paths due to Switch statements in the specified function definition.
    
    .OUTPUTS
        System.Int32
    
    .NOTES
        
    #>
        [CmdletBinding()]
        [OutputType([System.Int32])]
        Param (
            [Parameter(Position=0, Mandatory)]
            [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionDefinition
        )
        
        $FunctionText = $FunctionDefinition.Extent.Text
    
        # Converting the function definition to a generic ScriptBlockAst because the FindAll method of FunctionDefinitionAst object work strangely
        $FunctionAst = [System.Management.Automation.Language.Parser]::ParseInput($FunctionText, [ref]$null, [ref]$null)
        $SwitchStatements = $FunctionAst.FindAll({ $args[0] -is [System.Management.Automation.Language.SwitchStatementAst] }, $True)
    
        If ( -not($SwitchStatements) ) {
            return [int]0
        }
        [int]$SwitchCodePaths = 0
        Foreach ( $SwitchStatement in $SwitchStatements ) {
            [int]$ClausesWithBreak = (@($SwitchStatement.Clauses).Where({ $_ -match 'Break' })).Count
            [int]$ClausesWithoutBreak = (@($SwitchStatement.Clauses).Where({ $_ -notmatch 'Break' })).Count
            $SwitchCodePaths += ($ClausesWithBreak + (Get-SwitchCombination -Integer $ClausesWithoutBreak))
        }
        # Each clause is creating an additional path, except for the "catch-all" Default clause
        return $SwitchCodePaths
    }
  • tools\PSCodeHealth\Private\Metrics\Measure-FunctionTrapCatchCodePath.ps1 Show
    Function Measure-FunctionTrapCatchCodePath {
    <#
    .SYNOPSIS
        Gets the number of additional code paths due to Trap statements and Catch clauses in Try statements.
    .DESCRIPTION
        Gets the number of additional code paths due to Trap statements and Catch clauses in Try statements, in the specified function definition.
    
    .PARAMETER FunctionDefinition
        To specify the function definition to analyze.
    
    .EXAMPLE
        PS C:\> Measure-FunctionTrapCatchCodePath -FunctionDefinition $MyFunctionAst
    
        Gets the number of additional code paths due to Trap statements and Catch clauses in Try statements, in the specified function definition.
    
    .OUTPUTS
        System.Int32
    
    .NOTES
        
    #>
        [CmdletBinding()]
        [OutputType([System.Int32])]
        Param (
            [Parameter(Position=0, Mandatory)]
            [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionDefinition
        )
        
        $FunctionText = $FunctionDefinition.Extent.Text
    
        # Converting the function definition to a generic ScriptBlockAst because the FindAll method of FunctionDefinitionAst object work strangely
        $FunctionAst = [System.Management.Automation.Language.Parser]::ParseInput($FunctionText, [ref]$null, [ref]$null)
        $TrapStatements = $FunctionAst.FindAll({ $args[0] -is [System.Management.Automation.Language.TrapStatementAst] }, $True)
        $CatchClauses = $FunctionAst.FindAll({ $args[0] -is [System.Management.Automation.Language.CatchClauseAst] }, $True)
    
        [int]$ErrorHandlingCodePaths = $TrapStatements.Count + $CatchClauses.Count
        return $ErrorHandlingCodePaths
    }
  • tools\PSCodeHealth\Private\Metrics\Measure-FunctionWhileCodePath.ps1 Show
    Function Measure-FunctionWhileCodePath {
    <#
    .SYNOPSIS
        Gets the number of additional code paths due to While statements.
    .DESCRIPTION
        Gets the number of additional code paths due to While statements, in the specified function definition.
    
    .PARAMETER FunctionDefinition
        To specify the function definition to analyze.
    
    .EXAMPLE
        PS C:\> Measure-FunctionWhileCodePath -FunctionDefinition $MyFunctionAst
    
        Gets the number of additional code paths due to While statements in the specified function definition.
    
    .OUTPUTS
        System.Int32
    
    .NOTES
        
    #>
        [CmdletBinding()]
        [OutputType([System.Int32])]
        Param (
            [Parameter(Position=0, Mandatory)]
            [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionDefinition
        )
        
        $FunctionText = $FunctionDefinition.Extent.Text
    
        # Converting the function definition to a generic ScriptBlockAst because the FindAll method of FunctionDefinitionAst object work strangely
        $FunctionAst = [System.Management.Automation.Language.Parser]::ParseInput($FunctionText, [ref]$null, [ref]$null)
        $WhileStatements = $FunctionAst.FindAll({ $args[0] -is [System.Management.Automation.Language.WhileStatementAst] }, $True)
    
        If ( -not($WhileStatements) ) {
            return [int]0
        }
        return $WhileStatements.Count
    }
  • tools\PSCodeHealth\Private\Metrics\Test-FunctionHelpCoverage.ps1 Show
    Function Test-FunctionHelpCoverage {
    <#
    .SYNOPSIS
        Tells whether or not the specified function definition contains help information.
    .DESCRIPTION
        Tells whether or not the specified function definition specified as a [System.Management.Automation.Language.FunctionDefinitionAst] contains help information.
        This function returns $True if the specified function definition AST has a CommentHelpInfo or if the function name is listed in an external help file.
    
    .PARAMETER FunctionDefinition
        To specify the function definition to analyze.
    
    .EXAMPLE
        PS C:\> Test-FunctionHelpCoverage -FunctionDefinition $MyFunctionAst
    
        Returns $True if the specified function definition contains help information, returns $False if not.
    
    .OUTPUTS
        System.Boolean
    
    .NOTES
        
    #>
        [CmdletBinding()]
        [OutputType([System.Boolean])]
        Param (
            [Parameter(Position=0, Mandatory)]
            [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionDefinition
        )
        
        $FunctionHelpInfo = $FunctionDefinition.GetHelpContent()
        [bool]$CommentBasedHelpPresent = $FunctionHelpInfo -is [System.Management.Automation.Language.CommentHelpInfo]
        
        [bool]$ExternalHelpPresent = $FunctionDefinition.Name -in $Script:ExternalHelpCommandNames
        $HelpPresent = $CommentBasedHelpPresent -or $ExternalHelpPresent
        return $HelpPresent
    }
    
  • tools\PSCodeHealth\Private\New-FailedTestsInfo.ps1 Show
    Function New-FailedTestsInfo {
    <#
    .SYNOPSIS
        Creates one or more custom objects of the type : 'PSCodeHealth.Overall.FailedTestsInfo'.  
    
    .DESCRIPTION
        Creates one or more custom objects of the type : 'PSCodeHealth.Overall.FailedTestsInfo'.  
        This outputs an object containing key information about each failed test. This information is used in the overall health report.  
    
    .PARAMETER TestsResult
        To specify the Pester tests result object.
    
    .EXAMPLE
        PS C:\> New-FailedTestsInfo -TestsResult $TestsResult
    
        Returns a new custom object of the type 'PSCodeHealth.Overall.FailedTestsInfo' for each failed test in the input $TestsResult.
    
    .OUTPUTS
        PSCodeHealth.Overall.FailedTestsInfo
    
    .NOTES    
    #>
        [CmdletBinding()]
        [OutputType([PSCustomObject[]])]
        Param (
            [Parameter(Position=0, Mandatory)]
            [PSCustomObject]$TestsResult
        )
        $FailedTests = $TestsResult.TestResult.Where({ -not $_.Passed })
    
        Foreach ( $FailedTest in $FailedTests ) {
    
            $SplitStackTrace = $FailedTest.StackTrace -split ':\s'
            $File = ($SplitStackTrace[0] -split '\\')[-1]
            $Line = ((($SplitStackTrace[1] -split '\n') | Where-Object { $_ -match 'line' }) -split '\s')
            $LineNumber = $Line | Where-Object { $_ -match '\d+' }
    
            $ObjectProperties = [ordered]@{
                'File'         = $File
                'Line'         = $LineNumber
                'Describe'     = $FailedTest.Describe
                'TestName'     = $FailedTest.Name
                'ErrorMessage' = $FailedTest.FailureMessage
            }
    
            $CustomObject = New-Object -TypeName PSObject -Property $ObjectProperties
            $CustomObject.psobject.TypeNames.Insert(0, 'PSCodeHealth.Overall.FailedTestsInfo')
            $CustomObject
        }
    }
  • tools\PSCodeHealth\Private\New-FunctionHealthRecord.ps1 Show
    Function New-FunctionHealthRecord {
    <#
    .SYNOPSIS
        Creates a new custom object and gives it the TypeName : 'PSCodeHealth.Function.HealthRecord'.
    .DESCRIPTION
        Creates a new custom object and gives it the TypeName : 'PSCodeHealth.Function.HealthRecord'.
    
    .PARAMETER FunctionDefinition
        To specify the function definition.
    
    .PARAMETER FunctionTestCoverage
        To specify the percentage of lines of code in the specified function that are tested by unit tests.
    
    .EXAMPLE
        PS C:\> New-FunctionHealthRecord -FunctionDefinition $MyFunctionAst -FunctionTestCoverage $TestCoverage
    
        Returns new custom object of the type PSCodeHealth.Function.HealthRecord.
    
    .OUTPUTS
        PSCodeHealth.Function.HealthRecord
    
    .NOTES
        
    #>
        [CmdletBinding()]
        [OutputType([PSCustomObject])]
        Param (
            [Parameter(Position=0, Mandatory)]
            [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionDefinition,
    
            [Parameter(Position=1, Mandatory)]
            [AllowNull()]
            [PSTypeName('PSCodeHealth.Function.TestCoverageInfo')]
            [PSCustomObject]$FunctionTestCoverage
        )
    
        $ScriptAnalyzerResultDetails = Get-FunctionScriptAnalyzerResult -FunctionDefinition $FunctionDefinition
    
        $ObjectProperties = [ordered]@{
            'FunctionName'                = $FunctionDefinition.Name
            'FilePath'                    = $FunctionDefinition.Extent.File
            'LinesOfCode'                 = Get-FunctionLinesOfCode -FunctionDefinition $FunctionDefinition
            'ScriptAnalyzerFindings'      = $ScriptAnalyzerResultDetails.Count
            'ScriptAnalyzerResultDetails' = $ScriptAnalyzerResultDetails
            'ContainsHelp'                = Test-FunctionHelpCoverage -FunctionDefinition $FunctionDefinition
            'TestCoverage'                = $FunctionTestCoverage.CodeCoveragePerCent
            'CommandsMissed'              = ($FunctionTestCoverage.CommandsMissed | Measure-Object).Count
            'Complexity'                  = Measure-FunctionComplexity -FunctionDefinition $FunctionDefinition
            'MaximumNestingDepth'         = Measure-FunctionMaxNestingDepth -FunctionDefinition $FunctionDefinition
        }
    
        $CustomObject = New-Object -TypeName PSObject -Property $ObjectProperties
        $CustomObject.psobject.TypeNames.Insert(0, 'PSCodeHealth.Function.HealthRecord')
        return $CustomObject
    }
  • tools\PSCodeHealth\Private\New-PSCodeHealthComplianceResult.ps1 Show
    Function New-PSCodeHealthComplianceResult {
    <#
    .SYNOPSIS
        Creates a new custom object and gives it the TypeName : 'PSCodeHealth.Compliance.Result'.  
    
    .DESCRIPTION
        Creates a new custom object based on a PSCodeHealth.Compliance.Rule object and a compliance result, and gives it the TypeName : 'PSCodeHealth.Compliance.Result'.  
    
    .PARAMETER ComplianceRule
        The compliance rule which was evaluated.
    
    .PARAMETER Value
        The value from the health report for the evaluated metric.
    
    .PARAMETER Result
        The compliance result, based on the compliance rule and the actual value from the health report.
    
    .PARAMETER FunctionName
        To get compliance results for a specific function.  
        If this parameter is specified, this creates a PSCodeHealth.Compliance.FunctionResult object, instead of PSCodeHealth.Compliance.Result.
    
    .EXAMPLE
        PS C:\> New-PSCodeHealthComplianceResult -ComplianceRule $Rule -Value 81.26 -Result Warning
    
        Returns new custom object of the type PSCodeHealth.Compliance.Result.
    
    .EXAMPLE
        PS C:\> New-PSCodeHealthComplianceResult -ComplianceRule $Rule -Value 81.26 -Result Warning -FunctionName 'Get-Something'
    
        Returns new custom object of the type PSCodeHealth.Compliance.FunctionResult for the function 'Get-Something'.
    
    .OUTPUTS
        PSCodeHealth.Compliance.Result, PSCodeHealth.Compliance.FunctionResult
    #>
        [CmdletBinding()]
        [OutputType([PSCustomObject])]
        Param (
            [Parameter(Mandatory, Position=0)]
            [PSTypeName('PSCodeHealth.Compliance.Rule')]
            [PSCustomObject]$ComplianceRule,
    
            [Parameter(Mandatory, Position=1)]
            [PSObject]$Value,
    
            [Parameter(Mandatory, Position=2)]
            [ValidateSet('Fail','Warning','Pass')]
            [string]$Result,
    
            [Parameter(Mandatory=$False, Position=3)]
            [string]$FunctionName
        )
    
        $PropsDictionary = [ordered]@{
            'SettingsGroup'    = $ComplianceRule.SettingsGroup
            'MetricName'       = $ComplianceRule.MetricName
            'WarningThreshold' = $ComplianceRule.WarningThreshold
            'FailThreshold'    = $ComplianceRule.FailThreshold
            'HigherIsBetter'   = $ComplianceRule.HigherIsBetter
            'Value'            = $Value
            'Result'           = $Result
        }
    
        If ( $PSBoundParameters.ContainsKey('FunctionName') ) {
            $PropsDictionary.Insert(0, 'FunctionName', $FunctionName)
    
            $CustomObject = New-Object -TypeName PSObject -Property $PropsDictionary
            $CustomObject.psobject.TypeNames.Insert(0, 'PSCodeHealth.Compliance.FunctionResult')
        }
        Else {
            $CustomObject = New-Object -TypeName PSObject -Property $PropsDictionary
            $CustomObject.psobject.TypeNames.Insert(0, 'PSCodeHealth.Compliance.Result')
        }
        return $CustomObject
    }
  • tools\PSCodeHealth\Private\New-PSCodeHealthComplianceRule.ps1 Show
    Function New-PSCodeHealthComplianceRule {
    <#
    .SYNOPSIS
        Creates a new custom object and gives it the TypeName : 'PSCodeHealth.Compliance.Rule'.
    .DESCRIPTION
        Creates a new custom object and gives it the TypeName : 'PSCodeHealth.Compliance.Rule'.
    
    .PARAMETER MetricRule
        To specify the original metric rule object.
    
    .PARAMETER SettingsGroup
        To specify from which settings group the current metric rule comes from.
    
    .EXAMPLE
        PS C:\> New-PSCodeHealthComplianceRule -MetricRule $MetricRule -SettingsGroup PerFunctionMetrics
    
        Returns new custom object of the type PSCodeHealth.Compliance.Rule.
    
    .OUTPUTS
        PSCodeHealth.Compliance.Rule
    #>
        [CmdletBinding()]
        [OutputType([PSCustomObject])]
        Param (
            [Parameter(Mandatory, Position=0)]
            [PSCustomObject]$MetricRule,
    
            [Parameter(Mandatory, Position=1)]
            [ValidateSet('PerFunctionMetrics','OverallMetrics')]
            [string]$SettingsGroup
        )
    
        $MetricName = ($MetricRule | Get-Member -MemberType Properties).Name
    
        $ObjectProperties = [ordered]@{
            'SettingsGroup'    = $SettingsGroup
            'MetricName'       = $MetricName
            'WarningThreshold' = $MetricRule.$($MetricName).WarningThreshold
            'FailThreshold'    = $MetricRule.$($MetricName).FailThreshold
            'HigherIsBetter'   = $MetricRule.$($MetricName).HigherIsBetter
        }
    
        $CustomObject = New-Object -TypeName PSObject -Property $ObjectProperties
        $CustomObject.psobject.TypeNames.Insert(0, 'PSCodeHealth.Compliance.Rule')
        return $CustomObject
    }
  • tools\PSCodeHealth\Private\New-PSCodeHealthReport.ps1 Show
    Function New-PSCodeHealthReport {
    <#
    .SYNOPSIS
        Creates a new custom object and gives it the TypeName : 'PSCodeHealth.Overall.HealthReport'.
    .DESCRIPTION
        Creates a new custom object and gives it the TypeName : 'PSCodeHealth.Overall.HealthReport'.
        This output object contains metrics for the code in all the PowerShell files specified via the Path parameter, uses the function health records specified via the FunctionHealthRecord parameter.
        The value of the TestsPath parameter specifies the location of the tests when calling Pester to generate test coverage information.
    
    .PARAMETER ReportTitle
        To specify the title of the health report.  
        This is mainly used when generating an HTML report.
    
    .PARAMETER AnalyzedPath
        To specify the code path being analyzed.  
        This corresponds to the original Path value of Invoke-PSCodeHealth.
    
    .PARAMETER Path
        To specify the path of one or more PowerShell file(s) to analyze.
    
    .PARAMETER FunctionHealthRecord
        To specify the PSCodeHealth.Function.HealthRecord objects which will be the basis for the report.
    
    .PARAMETER TestsPath
        To specify the file or directory where the Pester tests are located.
        If a directory is specified, the directory and all subdirectories will be searched recursively for tests.
    
    .PARAMETER TestsResult
        To use an existing Pester tests result object for generating the following metrics :  
          - NumberOfTests  
          - NumberOfFailedTests  
          - FailedTestsDetails  
          - NumberOfPassedTests  
          - TestsPassRate (%)  
          - TestCoverage (%)  
          - CommandsMissedTotal  
    
    .EXAMPLE
        PS C:\> New-PSCodeHealthReport -ReportTitle 'MyTitle' -AnalyzedPath 'C:\Folder' -Path $MyPath -FunctionHealthRecord $FunctionHealthRecords -TestsPath "$MyPath\Tests"
    
        Returns new custom object of the type PSCodeHealth.Overall.HealthReport, containing metrics for the code in all the PowerShell files in $MyPath, using the function health records in $FunctionHealthRecords and running all tests in "$MyPath\Tests" (and its subdirectories) to generate test coverage information.
    
    .OUTPUTS
        PSCodeHealth.Overall.HealthReport
    
    .NOTES
        
    #>
        [CmdletBinding()]
        [OutputType([PSCustomObject])]
        Param (
            [Parameter(Position=0, Mandatory)]
            [string]$ReportTitle,
    
            [Parameter(Position=1, Mandatory)]
            [string]$AnalyzedPath,
            
            [Parameter(Position=2, Mandatory)]
            [string[]]$Path,
    
            [Parameter(Position=3, Mandatory)]
            [AllowNull()]
            [PSTypeName('PSCodeHealth.Function.HealthRecord')]
            [PSCustomObject[]]$FunctionHealthRecord,
    
            [Parameter(Position=4, Mandatory)]
            [ValidateScript({ Test-Path $_ })]
            [string]$TestsPath,
    
            [Parameter(Position=5, Mandatory=$False)]
            [PSCustomObject]$TestsResult
        )
    
        # Getting ScriptAnalyzer findings from PowerShell manifests or data files and adding them to the report
        # because these findings don't show up in the FunctionHealthRecords
        $Psd1Files = $Path | Where-Object { $_ -like "*.psd1" }
        If ( $Psd1Files ) {
            $Psd1ScriptAnalyzerResults = $Psd1Files | ForEach-Object { Invoke-ScriptAnalyzer -Path $_ }
    
            # Have to do that because even if $Psd1ScriptAnalyzerResults is Null, it adds 1 to the number of items in $AllScriptAnalyzerResults
            If ( $Psd1ScriptAnalyzerResults ) {
                $AllScriptAnalyzerResults = ($FunctionHealthRecord.ScriptAnalyzerResultDetails | Where-Object { $_ }) + $Psd1ScriptAnalyzerResults
            }
            Else {
                $AllScriptAnalyzerResults = ($FunctionHealthRecord.ScriptAnalyzerResultDetails | Where-Object { $_ })
            }
        }
        Else {
            $AllScriptAnalyzerResults = ($FunctionHealthRecord.ScriptAnalyzerResultDetails | Where-Object { $_ })
        }
        $ScriptAnalyzerErrors = $AllScriptAnalyzerResults | Where-Object Severity -EQ 'Error'
        $ScriptAnalyzerWarnings = $AllScriptAnalyzerResults | Where-Object Severity -EQ 'Warning'
        $ScriptAnalyzerInformation = $AllScriptAnalyzerResults | Where-Object Severity -EQ 'Information'
    
        # Gettings overall test coverage for all code in $Path
        If ( ($PSBoundParameters.ContainsKey('TestsResult')) ) {
            $TestsResult = $PSBoundParameters.TestsResult
        }
        Else {
            $OverallPesterParams = @{
                Script = $TestsPath
                CodeCoverage = $Path
                PassThru = $True
                Strict = $True
                Verbose = $False
                WarningAction = 'SilentlyContinue'
            }
    
            # Invoke-Pester didn't have the "Show" parameter prior to version 4.x
            $SuppressOutput = If ((Get-Module -Name Pester).Version.Major -lt 4) { @{Quiet = $True} } Else { @{Show = 'None'} }
    
            $TestsResult = Invoke-Pester @OverallPesterParams @SuppressOutput
        }
        If ( $TestsResult.CodeCoverage ) {
            $CodeCoverage = $TestsResult.CodeCoverage
            $CommandsMissed = $CodeCoverage.NumberOfCommandsMissed
            Write-VerboseOutput -Message "Number of commands found in the function : $($CommandsMissed)"
    
            $CommandsFound = $CodeCoverage.NumberOfCommandsAnalyzed
            Write-VerboseOutput -Message "Number of commands found in the function : $($CommandsFound)"
    
            # To prevent any "Attempted to divide by zero" exceptions
            If ( $CommandsFound -ne 0 ) {
                $CommandsExercised = $CodeCoverage.NumberOfCommandsExecuted
                Write-VerboseOutput -Message "Number of commands exercised in the tests : $($CommandsExercised)"
                [System.Double]$CodeCoveragePerCent = [math]::Round(($CommandsExercised / $CommandsFound) * 100, 2)
            }
            Else {
                [System.Double]$CodeCoveragePerCent = 0
            }
        }
    
        $FailedTestsDetails = If ($TestsResult.FailedCount -gt 0) { New-FailedTestsInfo -TestsResult $TestsResult } Else { $Null }
    
        $ObjectProperties = [ordered]@{
            'ReportTitle'                   = $ReportTitle
            'ReportDate'                    = Get-Date -Format u
            'AnalyzedPath'                  = $AnalyzedPath
            'Files'                         = $Path.Count
            'Functions'                     = $FunctionHealthRecord.Count
            'LinesOfCodeTotal'              = ($FunctionHealthRecord.LinesOfCode | Measure-Object -Sum).Sum
            'LinesOfCodeAverage'            = [math]::Round(($FunctionHealthRecord.LinesOfCode | Measure-Object -Average).Average, 2)
            'ScriptAnalyzerFindingsTotal'   = ($AllScriptAnalyzerResults | Measure-Object).Count
            'ScriptAnalyzerErrors'          = ($ScriptAnalyzerErrors | Measure-Object).Count
            'ScriptAnalyzerWarnings'        = ($ScriptAnalyzerWarnings | Measure-Object).Count
            'ScriptAnalyzerInformation'     = ($ScriptAnalyzerInformation | Measure-Object).Count
            'ScriptAnalyzerFindingsAverage' = [math]::Round(($FunctionHealthRecord.ScriptAnalyzerFindings | Measure-Object -Average).Average, 2)
            'FunctionsWithoutHelp'          = ($FunctionHealthRecord | Where-Object { -not($_.ContainsHelp) } | Measure-Object).Count
            'NumberOfTests'                 = If ( $TestsResult ) { $TestsResult.TotalCount } Else { 0 }
            'NumberOfFailedTests'           = If ( $TestsResult ) { $TestsResult.FailedCount } Else { 0 }
            'FailedTestsDetails'            = $FailedTestsDetails
            'NumberOfPassedTests'           = If ( $TestsResult ) { $TestsResult.PassedCount } Else { 0 }
            'TestsPassRate'                 = If ($TestsResult.TotalCount) { [math]::Round(($TestsResult.PassedCount / $TestsResult.TotalCount) * 100, 2) } Else { 0 }
            'TestCoverage'                  = $CodeCoveragePerCent
            'CommandsMissedTotal'           = $CommandsMissed
            'ComplexityAverage'             = [math]::Round(($FunctionHealthRecord.Complexity | Measure-Object -Average).Average, 2)
            'ComplexityHighest'             = [math]::Round(($FunctionHealthRecord.Complexity | Measure-Object -Maximum).Maximum, 2)
            'NestingDepthAverage'           = [math]::Round(($FunctionHealthRecord.MaximumNestingDepth | Measure-Object -Average).Average, 2)
            'NestingDepthHighest'           = [math]::Round(($FunctionHealthRecord.MaximumNestingDepth | Measure-Object -Maximum).Maximum, 2)
            'FunctionHealthRecords'         = $FunctionHealthRecord
        }
    
        $CustomObject = New-Object -TypeName PSObject -Property $ObjectProperties
        $CustomObject.psobject.TypeNames.Insert(0, 'PSCodeHealth.Overall.HealthReport')
        return $CustomObject
    }
  • tools\PSCodeHealth\Private\Write-VerboseOutput.ps1 Show
    Function Write-VerboseOutput {
    <#
    .SYNOPSIS
        Helper function for semi-structured logging, to manage verbose output of the other functions.
    #>
        [CmdletBinding()]
        Param(
            [Parameter(Position=0, Mandatory)]
            [string]$Message
        )
    
        $TimeStamp = Get-Date -Format s
        $CallingCommand = (Get-PSCallStack)[1].Command
        $MessageData = '{0} [{1}] : {2}' -f $TimeStamp,$CallingCommand,$Message
    
        Write-Verbose -Message $MessageData
    }
  • tools\PSCodeHealth\PSCodeHealth.Format.ps1xml
  • tools\PSCodeHealth\PSCodeHealth.psd1 Show
    #
    # Module manifest for module 'PSCodeHealth'
    #
    # Generated by: Mathieu Buisson
    #
    # Generated on: 05/02/2017
    #
    
    @{
    
    # Script module or binary module file associated with this manifest.
    RootModule = '.\PSCodeHealth.psm1'
    
    # Version number of this module.
    ModuleVersion = '0.2.26'
    
    # ID used to uniquely identify this module
    GUID = 'ca22dabd-bbb6-4805-9c90-a8aad6dbbfd3'
    
    # Author of this module
    Author = 'Mathieu Buisson'
    
    # Company or vendor of this module
    CompanyName = 'Unknown'
    
    # Copyright statement for this module
    Copyright = '(c) 2017 Mathieu Buisson. All rights reserved.'
    
    # Description of the functionality provided by this module
    Description = 'This module allows you to measure the quality and maintainability of your PowerShell code, based on a variety of metrics.'
    
    # Minimum version of the Windows PowerShell engine required by this module
    PowerShellVersion = '5.0'
    
    # Name of the Windows PowerShell host required by this module
    # PowerShellHostName = ''
    
    # Minimum version of the Windows PowerShell host required by this module
    # PowerShellHostVersion = ''
    
    # Minimum version of Microsoft .NET Framework required by this module
    # DotNetFrameworkVersion = ''
    
    # Minimum version of the common language runtime (CLR) required by this module
    # CLRVersion = ''
    
    # Processor architecture (None, X86, Amd64) required by this module
    # ProcessorArchitecture = ''
    
    # Modules that must be imported into the global environment prior to importing this module
    RequiredModules = @('Pester','PSScriptAnalyzer')
    
    # Assemblies that must be loaded prior to importing this module
    # RequiredAssemblies = @()
    
    # Script files (.ps1) that are run in the caller's environment prior to importing this module.
    # ScriptsToProcess = @()
    
    # Type files (.ps1xml) to be loaded when importing this module
    # TypesToProcess = @()
    
    # Format files (.ps1xml) to be loaded when importing this module
    FormatsToProcess = @('PSCodeHealth.Format.ps1xml')
    
    # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
    # NestedModules = @()
    
    # Functions to export from this module
    FunctionsToExport = @('Invoke-PSCodeHealth','Get-PSCodeHealthComplianceRule','Test-PSCodeHealthCompliance')
    
    # Cmdlets to export from this module
    # CmdletsToExport = '*'
    
    # Variables to export from this module
    # VariablesToExport = '*'
    
    # Aliases to export from this module
    AliasesToExport = 'ipch'
    
    # DSC resources to export from this module
    # DscResourcesToExport = @()
    
    # List of all modules packaged with this module
    # ModuleList = @()
    
    # List of all files packaged with this module
    # FileList = @()
    
    # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
    PrivateData = @{
    
        PSData = @{
    
            # Tags applied to this module. These help with module discovery in online galleries.
            Tags = 'PowerShell', 'Quality', 'Metrics', 'DevOps'
    
            # A URL to the license for this module.
            LicenseUri = 'https://github.com/MathieuBuisson/PSCodeHealth/blob/master/LICENSE.md'
    
            # A URL to the main website for this project.
            ProjectUri = 'https://github.com/MathieuBuisson/PSCodeHealth'
    
            # A URL to an icon representing this module.
            IconUri = 'https://github.com/MathieuBuisson/PSCodeHealth/raw/master/PSCodeHealth/Assets/PSCodeHealthLogo.png'
    
            # ReleaseNotes of this module
            ReleaseNotes = 'https://github.com/MathieuBuisson/PSCodeHealth/blob/master/docs/Release.md'
    
        } # End of PSData hashtable
    
    } # End of PrivateData hashtable
    
    # HelpInfo URI of this module
    # HelpInfoURI = ''
    
    # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
    # DefaultCommandPrefix = ''
    
    }
    
  • tools\PSCodeHealth\PSCodeHealth.psm1 Show
    #Get public and private function definition files.
    $Public  = @( Get-ChildItem -Path "$PSScriptRoot\Public\*.ps1" -File -ErrorAction SilentlyContinue )
    $Private = @( Get-ChildItem -Path "$PSScriptRoot\Private" -File -Filter '*.ps1' -Recurse -ErrorAction SilentlyContinue )
    
    Foreach ( $Import in @($Public + $Private) ) {
        Try {
            . $Import.FullName
        }
        Catch {
            Write-Error -Message "Failed to import function $($Import.FullName): $_"
        }
    }
    $Script:ExternalHelpCommandNames = @()
    
    Export-ModuleMember -Function $Public.Basename
    Set-Alias -Name ipch -Value Invoke-PSCodeHealth -Force
    Export-ModuleMember -Alias 'ipch'
  • tools\PSCodeHealth\PSCodeHealthSettings.json Show
    {
        "PerFunctionMetrics": [
            {
                "LinesOfCode": {
                    "WarningThreshold": 30,
                    "FailThreshold": 60,
                    "HigherIsBetter": false
                }
            },
            {
                "ScriptAnalyzerFindings": {
                    "WarningThreshold": 7,
                    "FailThreshold": 12,
                    "HigherIsBetter": false
                }
            },
            {
                "TestCoverage": {
                    "WarningThreshold": 80,
                    "FailThreshold": 70,
                    "HigherIsBetter": true
                }
            },
            {
                "CommandsMissed": {
                    "WarningThreshold": 6,
                    "FailThreshold": 12,
                    "HigherIsBetter": false
                }
            },        
            {
                "Complexity": {
                    "WarningThreshold": 15,
                    "FailThreshold": 30,
                    "HigherIsBetter": false
                }
            },
            {
                "MaximumNestingDepth": {
                    "WarningThreshold": 4,
                    "FailThreshold": 8,
                    "HigherIsBetter": false
                }
            }
        ],
        "OverallMetrics": [
            {
                "LinesOfCodeTotal": {
                    "WarningThreshold": 1000,
                    "FailThreshold": 2000,
                    "HigherIsBetter": false
                }
            },
            {
                "LinesOfCodeAverage": {
                    "WarningThreshold": 30,
                    "FailThreshold": 60,
                    "HigherIsBetter": false
                }
            },
            {
                "ScriptAnalyzerFindingsTotal": {
                    "WarningThreshold": 30,
                    "FailThreshold": 60,
                    "HigherIsBetter": false
                }
            },
            {
                "ScriptAnalyzerErrors": {
                    "WarningThreshold": 1,
                    "FailThreshold": 3,
                    "HigherIsBetter": false
                }
            },
            {
                "ScriptAnalyzerWarnings": {
                    "WarningThreshold": 10,
                    "FailThreshold": 20,
                    "HigherIsBetter": false
                }
            },
            {
                "ScriptAnalyzerInformation": {
                    "WarningThreshold": 20,
                    "FailThreshold": 40,
                    "HigherIsBetter": false
                }
            },
            {
                "ScriptAnalyzerFindingsAverage": {
                    "WarningThreshold": 7,
                    "FailThreshold": 12,
                    "HigherIsBetter": false
                }
            },
            {
                "NumberOfFailedTests": {
                    "WarningThreshold": 1,
                    "FailThreshold": 3,
                    "HigherIsBetter": false
                }
            },
            {
                "TestsPassRate": {
                    "WarningThreshold": 99,
                    "FailThreshold": 97,
                    "HigherIsBetter": true
                }
            },
            {
                "TestCoverage": {
                    "WarningThreshold": 80,
                    "FailThreshold": 70,
                    "HigherIsBetter": true
                }
            },
            {
                "CommandsMissedTotal": {
                    "WarningThreshold": 200,
                    "FailThreshold": 400,
                    "HigherIsBetter": false
                }
            },
            {
                "ComplexityAverage": {
                    "WarningThreshold": 15,
                    "FailThreshold": 30,
                    "HigherIsBetter": false
                }
            },
            {
                "ComplexityHighest": {
                    "WarningThreshold": 30,
                    "FailThreshold": 60,
                    "HigherIsBetter": false
                }
            },
            {
                "NestingDepthAverage": {
                    "WarningThreshold": 4,
                    "FailThreshold": 8,
                    "HigherIsBetter": false
                }
            },
            {
                "NestingDepthHighest": {
                    "WarningThreshold": 8,
                    "FailThreshold": 16,
                    "HigherIsBetter": false
                }
            }
        ]
    }
  • tools\PSCodeHealth\PSGetModuleInfo.xml Show
    <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
      <Obj RefId="0">
        <TN RefId="0">
          <T>Microsoft.PowerShell.Commands.PSRepositoryItemInfo</T>
          <T>System.Management.Automation.PSCustomObject</T>
          <T>System.Object</T>
        </TN>
        <MS>
          <S N="Name">PSCodeHealth</S>
          <Version N="Version">0.2.26</Version>
          <S N="Type">Module</S>
          <S N="Description">This module allows you to measure the quality and maintainability of your PowerShell code, based on a variety of metrics.</S>
          <S N="Author">Mathieu Buisson</S>
          <S N="CompanyName">MathieuBuisson</S>
          <S N="Copyright">(c) 2017 Mathieu Buisson. All rights reserved.</S>
          <DT N="PublishedDate">2018-05-10T16:50:31+00:00</DT>
          <Nil N="InstalledDate" />
          <Nil N="UpdatedDate" />
          <URI N="LicenseUri">https://github.com/MathieuBuisson/PSCodeHealth/blob/master/LICENSE.md</URI>
          <URI N="ProjectUri">https://github.com/MathieuBuisson/PSCodeHealth</URI>
          <URI N="IconUri">https://github.com/MathieuBuisson/PSCodeHealth/raw/master/PSCodeHealth/Assets/PSCodeHealthLogo.png</URI>
          <Obj N="Tags" RefId="1">
            <TN RefId="1">
              <T>System.Object[]</T>
              <T>System.Array</T>
              <T>System.Object</T>
            </TN>
            <LST>
              <S>PowerShell</S>
              <S>Quality</S>
              <S>Metrics</S>
              <S>DevOps</S>
              <S>PSModule</S>
            </LST>
          </Obj>
          <Obj N="Includes" RefId="2">
            <TN RefId="2">
              <T>System.Collections.Hashtable</T>
              <T>System.Object</T>
            </TN>
            <DCT>
              <En>
                <S N="Key">Function</S>
                <Obj N="Value" RefId="3">
                  <TNRef RefId="1" />
                  <LST>
                    <S>Invoke-PSCodeHealth</S>
                    <S>Get-PSCodeHealthComplianceRule</S>
                    <S>Test-PSCodeHealthCompliance</S>
                  </LST>
                </Obj>
              </En>
              <En>
                <S N="Key">RoleCapability</S>
                <Obj N="Value" RefId="4">
                  <TNRef RefId="1" />
                  <LST />
                </Obj>
              </En>
              <En>
                <S N="Key">Command</S>
                <Obj N="Value" RefId="5">
                  <TNRef RefId="1" />
                  <LST>
                    <S>Invoke-PSCodeHealth</S>
                    <S>Get-PSCodeHealthComplianceRule</S>
                    <S>Test-PSCodeHealthCompliance</S>
                  </LST>
                </Obj>
              </En>
              <En>
                <S N="Key">DscResource</S>
                <Obj N="Value" RefId="6">
                  <TNRef RefId="1" />
                  <LST />
                </Obj>
              </En>
              <En>
                <S N="Key">Workflow</S>
                <Obj N="Value" RefId="7">
                  <TNRef RefId="1" />
                  <LST />
                </Obj>
              </En>
              <En>
                <S N="Key">Cmdlet</S>
                <Obj N="Value" RefId="8">
                  <TNRef RefId="1" />
                  <LST />
                </Obj>
              </En>
            </DCT>
          </Obj>
          <Nil N="PowerShellGetFormatVersion" />
          <S N="ReleaseNotes">https://github.com/MathieuBuisson/PSCodeHealth/blob/master/docs/Release.md</S>
          <Obj N="Dependencies" RefId="9">
            <TNRef RefId="1" />
            <LST>
              <Obj RefId="10">
                <TN RefId="3">
                  <T>System.Collections.Specialized.OrderedDictionary</T>
                  <T>System.Object</T>
                </TN>
                <DCT>
                  <En>
                    <S N="Key">Name</S>
                    <S N="Value">Pester</S>
                  </En>
                  <En>
                    <S N="Key">CanonicalId</S>
                    <S N="Value">nuget:Pester</S>
                  </En>
                </DCT>
              </Obj>
              <Obj RefId="11">
                <TNRef RefId="3" />
                <DCT>
                  <En>
                    <S N="Key">Name</S>
                    <S N="Value">PSScriptAnalyzer</S>
                  </En>
                  <En>
                    <S N="Key">CanonicalId</S>
                    <S N="Value">nuget:PSScriptAnalyzer</S>
                  </En>
                </DCT>
              </Obj>
            </LST>
          </Obj>
          <S N="RepositorySourceLocation">https://www.powershellgallery.com/api/v2/</S>
          <S N="Repository">PSGallery</S>
          <S N="PackageManagementProvider">NuGet</S>
          <Obj N="AdditionalMetadata" RefId="12">
            <TNRef RefId="2" />
            <DCT>
              <En>
                <S N="Key">releaseNotes</S>
                <S N="Value">https://github.com/MathieuBuisson/PSCodeHealth/blob/master/docs/Release.md</S>
              </En>
              <En>
                <S N="Key">versionDownloadCount</S>
                <S N="Value">11</S>
              </En>
              <En>
                <S N="Key">ItemType</S>
                <S N="Value">Module</S>
              </En>
              <En>
                <S N="Key">copyright</S>
                <S N="Value">(c) 2017 Mathieu Buisson. All rights reserved.</S>
              </En>
              <En>
                <S N="Key">CompanyName</S>
                <S N="Value">Unknown</S>
              </En>
              <En>
                <S N="Key">tags</S>
                <S N="Value">PowerShell Quality Metrics DevOps PSModule PSFunction_Invoke-PSCodeHealth PSCommand_Invoke-PSCodeHealth PSFunction_Get-PSCodeHealthComplianceRule PSCommand_Get-PSCodeHealthComplianceRule PSFunction_Test-PSCodeHealthCompliance PSCommand_Test-PSCodeHealthCompliance PSIncludes_Function</S>
              </En>
              <En>
                <S N="Key">created</S>
                <S N="Value">5/10/2018 4:50:31 PM +00:00</S>
              </En>
              <En>
                <S N="Key">description</S>
                <S N="Value">This module allows you to measure the quality and maintainability of your PowerShell code, based on a variety of metrics.</S>
              </En>
              <En>
                <S N="Key">published</S>
                <S N="Value">5/10/2018 4:50:31 PM +00:00</S>
              </En>
              <En>
                <S N="Key">developmentDependency</S>
                <S N="Value">False</S>
              </En>
              <En>
                <S N="Key">NormalizedVersion</S>
                <S N="Value">0.2.26</S>
              </En>
              <En>
                <S N="Key">downloadCount</S>
                <S N="Value">993</S>
              </En>
              <En>
                <S N="Key">GUID</S>
                <S N="Value">ca22dabd-bbb6-4805-9c90-a8aad6dbbfd3</S>
              </En>
              <En>
                <S N="Key">PowerShellVersion</S>
                <S N="Value">5.0</S>
              </En>
              <En>
                <S N="Key">updated</S>
                <S N="Value">2018-05-11T11:56:20Z</S>
              </En>
              <En>
                <S N="Key">isLatestVersion</S>
                <S N="Value">True</S>
              </En>
              <En>
                <S N="Key">IsPrerelease</S>
                <S N="Value">false</S>
              </En>
              <En>
                <S N="Key">isAbsoluteLatestVersion</S>
                <S N="Value">True</S>
              </En>
              <En>
                <S N="Key">packageSize</S>
                <S N="Value">61337</S>
              </En>
              <En>
                <S N="Key">FileList</S>
                <S N="Value">PSCodeHealth.nuspec|PSCodeHealth.Format.ps1xml|PSCodeHealth.psd1|PSCodeHealth.psm1|PSCodeHealthSettings.json|Assets\HealthReport.css|Assets\HealthReport.html|Assets\HealthReport.js|Assets\PSCodeHealthLogo.png|Private\Get-ExternalHelpCommand.ps1|Private\Get-PowerShellFile.ps1|Private\Merge-PSCodeHealthSetting.ps1|Private\New-FailedTestsInfo.ps1|Private\New-FunctionHealthRecord.ps1|Private\New-PSCodeHealthComplianceResult.ps1|Private\New-PSCodeHealthComplianceRule.ps1|Private\New-PSCodeHealthReport.ps1|Private\Write-VerboseOutput.ps1|Private\HtmlReport\New-PSCodeHealthTableData.ps1|Private\HtmlReport\Set-PSCodeHealthHtmlColor.ps1|Private\HtmlReport\Set-PSCodeHealthPlaceholdersValue.ps1|Private\Metrics\Get-FunctionDefinition.ps1|Private\Metrics\Get-FunctionLinesOfCode.ps1|Private\Metrics\Get-FunctionScriptAnalyzerResult.ps1|Private\Metrics\Get-FunctionTestCoverage.ps1|Private\Metrics\Get-SwitchCombination.ps1|Private\Metrics\Measure-FunctionComplexity.ps1|Private\Metrics\Measure-FunctionForCodePath.ps1|Private\Metrics\Measure-FunctionIfCodePath.ps1|Private\Metrics\Measure-FunctionLogicalOpCodePath.ps1|Private\Metrics\Measure-FunctionMaxNestingDepth.ps1|Private\Metrics\Measure-FunctionSwitchCodePath.ps1|Private\Metrics\Measure-FunctionTrapCatchCodePath.ps1|Private\Metrics\Measure-FunctionWhileCodePath.ps1|Private\Metrics\Test-FunctionHelpCoverage.ps1|Public\Get-PSCodeHealthComplianceRule.ps1|Public\Invoke-PSCodeHealth.ps1|Public\Test-PSCodeHealthCompliance.ps1</S>
              </En>
              <En>
                <S N="Key">requireLicenseAcceptance</S>
                <S N="Value">True</S>
              </En>
            </DCT>
          </Obj>
          <S N="InstalledLocation">C:\Users\appveyor\AppData\Local\Temp\1\7542b18a-2d91-449b-9b66-81e735cee295\PSCodeHealth\0.2.26</S>
        </MS>
      </Obj>
    </Objs>
    
  • tools\PSCodeHealth\Public\Get-PSCodeHealthComplianceRule.ps1 Show
    Function Get-PSCodeHealthComplianceRule {
    <#
    .SYNOPSIS
        Get the PSCodeHealth compliance rules (metrics thresholds, etc...) which are currently in effect.  
    
    .DESCRIPTION
        Get the PSCodeHealth compliance rules (metrics warning and fail thresholds, etc...) which are currently in effect.  
        By default, all the compliance rules are coming from the file PSCodeHealthSettings.json in the module root.  
    
        Custom compliance rules can be specified in JSON format in a file, via the parameter CustomSettingsPath.  
        In this case, any compliance rules specified in the custom settings file override the default, and rules not specified in the custom settings file will use the defaults from PSCodeHealthSettings.json.  
    
        By default, this function outputs compliance rules for every metrics in every settings groups, but this can filtered via the MetricName and the SettingsGroup parameters.  
    
    .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 SettingsGroup
        To filter the output compliance rules to only the ones located in the specified group.  
        There are 2 settings groups in PSCodeHealthSettings.json, so there are 2 possible values for this parameter : 'PerFunctionMetrics' and 'OverallMetrics'.  
        Metrics in the PerFunctionMetrics group are generated for each individual function and metrics in the OverallMetrics group are calculated for the entire file or folder specified in the 'Path' parameter of Invoke-PSCodeHealth.  
        If not specified, compliance rules from both groups are output.  
    
    .PARAMETER MetricName
        To filter the output compliance rules to only the ones for the specified metric or metrics.  
        There is a large number of metrics, so for convenience, all the possible values are available via tab completion.
    
    .EXAMPLE
        PS C:\> Get-PSCodeHealthComplianceRule
    
        Gets all the default PSCodeHealth compliance rules (metrics warning and fail thresholds, etc...).
    
    .EXAMPLE
        PS C:\> Get-PSCodeHealthComplianceRule -CustomSettingsPath .\MySettings.json -SettingsGroup OverallMetrics
    
        Gets all PSCodeHealth compliance rules (metrics warning and fail thresholds, etc...) in effect in the group 'OverallMetrics'.  
        This also output any compliance rule overriding the defaults because they are specified in the file MySettings.json.
    
    .EXAMPLE
        PS C:\> Get-PSCodeHealthComplianceRule -MetricName 'TestCoverage','Complexity','MaximumNestingDepth'
    
        Gets the default compliance rules in effect for the TestCoverage, Complexity and MaximumNestingDepth metrics.  
        In the case of TestCoverage, this metric exists in both PerFunctionMetrics and OverallMetrics, so the TestCoverage compliance rules from both groups will be output.  
    
    .OUTPUTS
        PSCodeHealth.Compliance.Rule
    #>
        [CmdletBinding()]
        [OutputType([PSCustomObject[]])]
        Param(
            [Parameter(Mandatory=$False,Position=0)]
            [ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
            [string]$CustomSettingsPath,
    
            [Parameter(Mandatory=$False,Position=1)]
            [ValidateSet('PerFunctionMetrics','OverallMetrics')]
            [string]$SettingsGroup,
    
            [Parameter(Mandatory=$False,Position=2)]
            [ValidateSet('LinesOfCode','ScriptAnalyzerFindings','TestCoverage','CommandsMissed','Complexity','MaximumNestingDepth','LinesOfCodeTotal',
            'LinesOfCodeAverage','ScriptAnalyzerFindingsTotal','ScriptAnalyzerErrors','ScriptAnalyzerWarnings',
            'ScriptAnalyzerInformation','ScriptAnalyzerFindingsAverage','NumberOfFailedTests','TestsPassRate',
            'CommandsMissedTotal','ComplexityAverage','ComplexityHighest','NestingDepthAverage','NestingDepthHighest')]
            [string[]]$MetricName
        )
    
        $MetricsGroups = @('PerFunctionMetrics','OverallMetrics')
        $DefaultSettingsPath = "$PSScriptRoot\..\PSCodeHealthSettings.json"
        $DefaultSettings = ConvertFrom-Json (Get-Content -Path $DefaultSettingsPath -Raw) -ErrorAction Stop | Where-Object { $_ }
    
        If ( $PSBoundParameters.ContainsKey('CustomSettingsPath') ) {
            Try {
                $CustomSettings = ConvertFrom-Json (Get-Content -Path $CustomSettingsPath -Raw) -ErrorAction Stop | Where-Object { $_ }
            }
            Catch {
                Throw "An error occurred when attempting to convert JSON data from the file $CustomSettingsPath to an object. Please verify that the content of this file is in valid JSON format."
            }
        }    
        If ( $CustomSettings ) {
            $SettingsInEffect = Merge-PSCodeHealthSetting -DefaultSettings $DefaultSettings -CustomSettings $CustomSettings
        }
        Else {
            $SettingsInEffect = $DefaultSettings
        }
    
        If ( $PSBoundParameters.ContainsKey('SettingsGroup') ) {
            If ( $PSBoundParameters.ContainsKey('MetricName') ) {
                $MetricsInGroup = $SettingsInEffect.$($SettingsGroup) | Where-Object { ($_ | Get-Member -MemberType Properties).Name -in $MetricName }
                Write-VerboseOutput "Found $($MetricsInGroup.Count) relevant metrics in the group $SettingsGroup"
                Foreach ( $MetricRule in $MetricsInGroup ) {
                    New-PSCodeHealthComplianceRule -MetricRule $MetricRule -SettingsGroup $SettingsGroup
                }
            }
            Else {
                $MetricsInGroup = $SettingsInEffect.$($SettingsGroup)
                Write-VerboseOutput "Found $($MetricsInGroup.Count) relevant metrics in the group $SettingsGroup"
                Foreach ( $MetricRule in $MetricsInGroup ) {
                    New-PSCodeHealthComplianceRule -MetricRule $MetricRule -SettingsGroup $SettingsGroup
                }
            }
        }
        Else {
            If ( $PSBoundParameters.ContainsKey('MetricName') ) {
                Foreach ( $MetricGroup in $MetricsGroups ) {
    
                    $MetricsInGroup = $SettingsInEffect.$($MetricGroup) | Where-Object { ($_ | Get-Member -MemberType Properties).Name -in $MetricName }
                    Write-VerboseOutput "Found $($MetricsInGroup.Count) relevant metrics in the group $MetricGroup"
                    Foreach ( $MetricRule in $MetricsInGroup ) {
                        New-PSCodeHealthComplianceRule -MetricRule $MetricRule -SettingsGroup $MetricGroup
                    }
                }
            }
            Else {
                Foreach ( $MetricGroup in $MetricsGroups ) {
    
                    $MetricsInGroup = $SettingsInEffect.$($MetricGroup)
                    Write-VerboseOutput "Found $($MetricsInGroup.Count) relevant metrics in the group $MetricGroup"
                    Foreach ( $MetricRule in $MetricsInGroup ) {
                        New-PSCodeHealthComplianceRule -MetricRule $MetricRule -SettingsGroup $MetricGroup
                    }
                }
            }
        }
    }
  • tools\PSCodeHealth\Public\Invoke-PSCodeHealth.ps1 Show
    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 ( $PSBoundParameters.ContainsKey('Path') ) {
            $Path = (Resolve-Path -Path $Path).Path
        }
        Else {
            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 ) {
            $ExternalHelpSearchRoot = $Path
            If ( $PSBoundParameters.ContainsKey('Exclude') ) {
                $PowerShellFiles = Get-PowerShellFile -Path $Path -Recurse:$($Recurse.IsPresent) -Exclude $Exclude
            }
            Else {
                $PowerShellFiles = Get-PowerShellFile -Path $Path -Recurse:$($Recurse.IsPresent)
            }
        }
        Else {
            $ExternalHelpSearchRoot = Split-Path -Path $Path -Parent
            $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)"
        }
        $Script:ExternalHelpCommandNames = Get-ExternalHelpCommand -Path $ExternalHelpSearchRoot
    
        $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
            }
        }
    }
  • tools\PSCodeHealth\Public\Test-PSCodeHealthCompliance.ps1 Show
    Function Test-PSCodeHealthCompliance {
    <#
    .SYNOPSIS
        Gets the compliance result(s) of the analyzed PowerShell code, based on a PSCodeHealth report and compliance rules contained in PSCodeHealth settings.  
    
    .DESCRIPTION
        Gets the compliance result(s) of the analyzed PowerShell code, based on a PSCodeHealth report and compliance rules contained in PSCodeHealth settings.  
        The values in the input PSCodeHealth report will be checked for compliance against the rules in the PSCodeHealth settings which are currently in effect.  
        By default, all compliance rules are coming from the file PSCodeHealthSettings.json in the module root. Custom compliance rules can be specified in JSON format in a file, via the parameter CustomSettingsPath.  
    
        The possible compliance levels are :  
          - Pass  
          - Warning  
          - Fail  
        
        By default, this function outputs the compliance results for every metrics in every settings groups, but this can filtered via the MetricName and the SettingsGroup parameters.  
    
    .PARAMETER HealthReport
        The PSCodeHealth report (object of the type PSCodeHealth.Overall.HealthReport) to analyze for compliance.  
        The ouput of the command Invoke-PSCodeHealth is a PSCodeHealth report and can be bound to this parameter via pipeline input.  
    
    .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 SettingsGroup
        To evaluate compliance only for the metrics located in the specified group.  
        There are 2 settings groups in PSCodeHealthSettings.json, so there are 2 possible values for this parameter : 'PerFunctionMetrics' and 'OverallMetrics'.  
        Metrics in the PerFunctionMetrics group are for each individual function and metrics in the OverallMetrics group are for the entire file or folder specified in the 'Path' parameter of Invoke-PSCodeHealth.  
        If not specified, compliance is evaluated for metrics in both groups.  
    
    .PARAMETER MetricName
        To get compliance results only for the specified metric(s).
        There is a large number of metrics, so for convenience, all the possible values are available via tab completion.
        If not specified, compliance is evaluated for all metrics.
    
    .PARAMETER FunctionName
        To get compliance results for a specific function.  
        This is a dynamic parameter which is available when the specified HealthReport contains at least 1 FunctionHealthRecords.  
    
    .PARAMETER Summary
        To output a single overall compliance result based on all the evaluated metrics.  
        This retains the worst compliance level, meaning :  
          - If any evaluated metric has the 'Fail' compliance level, the overall result is 'Fail'  
          - If any evaluated metric has the 'Warning' compliance level and none has 'Fail', the overall result is 'Warning'  
          - If all evaluated metrics has the 'Pass' compliance level, the overall result is 'Pass'  
    
    .EXAMPLE
        PS C:\> Test-PSCodeHealthCompliance -HealthReport $MyProjectHealthReport
    
        Gets the compliance results for every metrics, based on the specified PSCodeHealth report ($MyProjectHealthReport) and the compliance rules in the default settings.
    
    .EXAMPLE
        PS C:\> Invoke-PSCodeHealth | Test-PSCodeHealthCompliance
    
        Gets the compliance results for every metrics, based on the PSCodeHealth report specified via pipeline input and the compliance rules in the default settings.
    
    .EXAMPLE
        PS C:\> Test-PSCodeHealthCompliance -HealthReport $MyProjectHealthReport -CustomSettingsPath .\MySettings.json -SettingsGroup OverallMetrics
    
        Evaluates the compliance results for the metrics in the settings group OverallMetrics, based on the specified PSCodeHealth report ($MyProjectHealthReport).  
        This checks compliance against compliance rules in the defaults compliance rules and any custom compliance rule from the file 'MySettings.json'.  
    
    .EXAMPLE
        PS C:\> Test-PSCodeHealthCompliance -HealthReport $MyProjectHealthReport -MetricName 'TestCoverage','Complexity','MaximumNestingDepth'
    
        Evaluates the compliance results only for the TestCoverage, Complexity and MaximumNestingDepth metrics.  
        In the case of TestCoverage, this metric exists in both PerFunctionMetrics and OverallMetrics, so this evaluates the compliance result for the TestCoverage metric from both groups.  
    
    .EXAMPLE
        PS C:\> Test-PSCodeHealthCompliance -HealthReport $MyProjectHealthReport -FunctionName 'Get-Something'
    
        Evaluates the compliance results specifically for the function Get-Something. Because this is the compliance of a specific function, only the per function metrics are evaluated.  
        If the value of the FunctionName parameter doesn't match any function name in the HealthReport the parameter validation will fail and state the set of possible values.  
    
    .EXAMPLE
        PS C:\> Invoke-PSCodeHealth | Test-PSCodeHealthCompliance -Summary
    
        Evaluates the compliance results for every metrics, based on the PSCodeHealth report specified via pipeline input and the compliance rules in the default settings.  
        This outputs an overall 'Fail','Warning' or 'Pass' value for all the evaluated metrics.
    
    
    .OUTPUTS
        PSCodeHealth.Compliance.Result, PSCodeHealth.Compliance.FunctionResult, System.String
    #>
        [CmdletBinding()]
        [OutputType([PSCustomObject[]], [string])]
        Param(
            [Parameter(Mandatory, Position=0, ValueFromPipeline=$True)]
            [PSTypeName('PSCodeHealth.Overall.HealthReport')]
            [PSCustomObject]$HealthReport,
    
            [Parameter(Mandatory=$False,Position=1)]
            [ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
            [string]$CustomSettingsPath,
    
            [Parameter(Mandatory=$False,Position=2)]
            [ValidateSet('PerFunctionMetrics','OverallMetrics')]
            [string]$SettingsGroup,
    
            [Parameter(Mandatory=$False,Position=3)]
            [ValidateSet('LinesOfCode','ScriptAnalyzerFindings','TestCoverage','CommandsMissed','Complexity','MaximumNestingDepth','LinesOfCodeTotal',
            'LinesOfCodeAverage','ScriptAnalyzerFindingsTotal','ScriptAnalyzerErrors','ScriptAnalyzerWarnings',
            'ScriptAnalyzerInformation','ScriptAnalyzerFindingsAverage','NumberOfFailedTests','TestsPassRate',
            'CommandsMissedTotal','ComplexityAverage','ComplexityHighest','NestingDepthAverage','NestingDepthHighest')]
            [string[]]$MetricName,
    
            [Parameter(Mandatory=$False)]
            [switch]$Summary
        )
    
        DynamicParam {
            # The FunctionName parameter is dynamic because the set of possible values depends on the FunctionHealthRecords contained in the specified HealthReport.
            If ( $HealthReport.FunctionHealthRecords.Count -gt 0 ) {
                
                $ParameterName = 'FunctionName'            
                # Creating a parameter dictionary 
                $RuntimeParameterDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
    
                $AttributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]            
                $ValidationScriptAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute
                $ValidationScriptAttribute.Mandatory = $False
                $AttributeCollection.Add($ValidationScriptAttribute)
                # Generating dynamic values for a ValidateSet
                $SetValues = $HealthReport.FunctionHealthRecords.FunctionName
                $ValidateSetAttribute = New-Object -TypeName System.Management.Automation.ValidateSetAttribute($SetValues)
                # Adding the ValidateSet to the attributes collection
                $AttributeCollection.Add($ValidateSetAttribute)
                $RuntimeParameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
                $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
                return $RuntimeParameterDictionary
            }
        }
        
        Begin {
            If ( $RuntimeParameterDictionary ) { $FunctionName = $RuntimeParameterDictionary[$ParameterName].Value }
            $Null = $PSBoundParameters.Remove('HealthReport')
            If ( $PSBoundParameters.ContainsKey('Summary') ) {
                $Null = $PSBoundParameters.Remove('Summary')
            }
            If ( $PSBoundParameters.ContainsKey('FunctionName') ) {
                $Null = $PSBoundParameters.Remove('FunctionName')
            }        
            [System.Collections.ArrayList]$ComplianceResults = @()
            $ComplianceRules = Get-PSCodeHealthComplianceRule @PSBoundParameters
            Write-VerboseOutput "Evaluating the specified health report against $($ComplianceRules.Count) compliance rules."
        }
    
        Process {
            $FunctionHealthRecords = If ($FunctionName) {$HealthReport.FunctionHealthRecords | Where-Object FunctionName -eq $FunctionName} Else {$HealthReport.FunctionHealthRecords}
    
            Foreach ( $ComplianceRule in $ComplianceRules ) {
                If ( $ComplianceRule.SettingsGroup -eq 'PerFunctionMetrics' ) {
    
                    $MetricsFromReport = $FunctionHealthRecords.$($ComplianceRule.MetricName)
                    If ( $Null -ne $MetricsFromReport ) {
                        If ( $ComplianceRule.HigherIsBetter ) {
                            # We always retain the worst value of all the analyzed functions
                            $RetainedValue = ($MetricsFromReport | Measure-Object -Minimum).Minimum
                            Write-VerboseOutput "Retained value for $($ComplianceRule.MetricName) : $($RetainedValue)"
    
                            Switch ($RetainedValue) {
                                { $_ -lt $ComplianceRule.FailThreshold } { $ComplianceResult = 'Fail'; break}
                                { $_ -lt $ComplianceRule.WarningThreshold } { $ComplianceResult = 'Warning'; break}
                                Default { $ComplianceResult = 'Pass' }
                            }
    
                            $ResultParams = @{
                                ComplianceRule = $ComplianceRule
                                Value = $RetainedValue
                                Result = $ComplianceResult
                            }
                            If ( $FunctionName ) {
                                $ComplianceResultObj = New-PSCodeHealthComplianceResult @ResultParams -FunctionName $FunctionName
                            }
                            Else {
                                $ComplianceResultObj = New-PSCodeHealthComplianceResult @ResultParams
                            }
                            $Null = $ComplianceResults.Add($ComplianceResultObj)
                        }
                        Else {
                            # We always retain the worst value of all the analyzed functions
                            $RetainedValue = ($MetricsFromReport | Measure-Object -Maximum).Maximum
                            Write-VerboseOutput "Retained value for $($ComplianceRule.MetricName) : $($RetainedValue)"
    
                            Switch ($RetainedValue) {
                                { $_ -gt $ComplianceRule.FailThreshold } { $ComplianceResult = 'Fail'; break}
                                { $_ -gt $ComplianceRule.WarningThreshold } { $ComplianceResult = 'Warning'; break}
                                Default { $ComplianceResult = 'Pass' }
                            }
    
                            $ResultParams = @{
                                ComplianceRule = $ComplianceRule
                                Value = $RetainedValue
                                Result = $ComplianceResult
                            }
                            If ( $FunctionName ) {
                                $ComplianceResultObj = New-PSCodeHealthComplianceResult @ResultParams -FunctionName $FunctionName
                            }
                            Else {
                                $ComplianceResultObj = New-PSCodeHealthComplianceResult @ResultParams
                            }
                            $Null = $ComplianceResults.Add($ComplianceResultObj)
                        }
                    }
                }
                ElseIf ( $ComplianceRule.SettingsGroup -eq 'OverallMetrics' -and -not($FunctionName) ) {
                    $MetricFromReport = $HealthReport.$($ComplianceRule.MetricName)
                    If ( $MetricFromReport -or $MetricFromReport -eq 0 ) {
                        If ( $ComplianceRule.HigherIsBetter ) {
    
                            Switch ($MetricFromReport) {
                                { $_ -lt $ComplianceRule.FailThreshold } { $ComplianceResult = 'Fail'; break}
                                { $_ -lt $ComplianceRule.WarningThreshold } { $ComplianceResult = 'Warning'; break}
                                Default { $ComplianceResult = 'Pass' }
                            }
                            $ComplianceResultObj = New-PSCodeHealthComplianceResult -ComplianceRule $ComplianceRule -Value $MetricFromReport -Result $ComplianceResult
                            $Null = $ComplianceResults.Add($ComplianceResultObj)
                        }
                        Else {
    
                            Switch ($MetricFromReport) {
                                { $_ -gt $ComplianceRule.FailThreshold } { $ComplianceResult = 'Fail'; break}
                                { $_ -gt $ComplianceRule.WarningThreshold } { $ComplianceResult = 'Warning'; break}
                                Default { $ComplianceResult = 'Pass' }
                            }
                            $ComplianceResultObj = New-PSCodeHealthComplianceResult -ComplianceRule $ComplianceRule -Value $MetricFromReport -Result $ComplianceResult
                            $Null = $ComplianceResults.Add($ComplianceResultObj)
                        }
                    }
                }
            }
        }
    
        End {
            If ( $Summary ) {
                If ( $ComplianceResults.Result -contains 'Fail') {
                    return 'Fail'
                }
                If ( $ComplianceResults.Result -contains 'Warning') {
                    return 'Warning'
                }
                return 'Pass'
            }
            return $ComplianceResults
        }
    }
  • tools\VERIFICATION.txt Show
    VERIFICATION
    Verification is intended to assist the Chocolatey moderators and community in verifying that this package's contents are trustworthy.
    
    To verify the files using the project source:
    
    1. Please go to the project source location (https://github.com/MathieuBuisson/PSCodeHealth) and download the source files;
    2. Build the source to create the binary files to verify;
    3. Use Get-FileHash -Path <FILE TO VERIFY> to get the file hash value from both the built file (from step 1 above) and the file from the package and compare them;
    
    Alternatively you can download the module from the PowerShell Gallery ...
    
        Save-Module -Name PSCodeHealth -Path <PATH TO DOWNLOAD TO>
    
    ... and compare the files from the package against those in the installed module. Again use Get-FileHash -Path <FILE TO VERIFY> to retrieve those hash values.

Virus Scan Results

In cases where actual malware is found, the packages are subject to removal. Software sometimes has false positives. Moderators do not necessarily validate the safety of the underlying software, only that a package retrieves software from the official distribution point and/or validate embedded software against official distribution point (where distribution rights allow redistribution).

Chocolatey Pro provides runtime protection from possible malware.

Dependencies

Package Maintainer(s)

Software Author(s)

  • Mathieu Buisson

Copyright

2017 Mathieu Buisson

Tags

Version History

Version Downloads Last updated Status
PSCodeHealth (PowerShell Module) 0.2.9 105 Thursday, May 10, 2018 approved

Discussion for the PSCodeHealth (PowerShell Module) Package

Ground rules:

  • This discussion is only about PSCodeHealth (PowerShell Module) and the PSCodeHealth (PowerShell Module) package. If you have feedback for Chocolatey, please contact the google group.
  • This discussion will carry over multiple versions. If you have a comment about a particular version, please note that in your comments.
  • The maintainers of this Chocolatey Package will be notified about new comments that are posted to this Disqus thread, however, it is NOT a guarantee that you will get a response. If you do not hear back from the maintainers after posting a message below, please follow up by using the link on the left side of this page or follow this link to contact maintainers. If you still hear nothing back, please follow the package triage process.
  • Tell us what you love about the package or PSCodeHealth (PowerShell Module), or tell us what needs improvement.
  • Share your experiences with the package, or extra configuration or gotchas that you've found.
  • If you use a url, the comment will be flagged for moderation until you've been whitelisted. Disqus moderated comments are approved on a weekly schedule if not sooner. It could take between 1-5 days for your comment to show up.

comments powered by Disqus
Chocolatey.org uses cookies to enhance the user experience of the site.
Ok