diff --git a/Tests/Function-Extraction.Tests.ps1 b/Checks/Function-Extraction.Tests.ps1 similarity index 100% rename from Tests/Function-Extraction.Tests.ps1 rename to Checks/Function-Extraction.Tests.ps1 diff --git a/Tests/HelpElementRules.psd1 b/Checks/HelpElementRules.psd1 similarity index 100% rename from Tests/HelpElementRules.psd1 rename to Checks/HelpElementRules.psd1 diff --git a/Tests/Module.Tests.ps1 b/Checks/Module.Tests.ps1 similarity index 100% rename from Tests/Module.Tests.ps1 rename to Checks/Module.Tests.ps1 diff --git a/Tests/Script.Tests.ps1 b/Checks/Script.Tests.ps1 similarity index 100% rename from Tests/Script.Tests.ps1 rename to Checks/Script.Tests.ps1 diff --git a/PSQualityCheck.Functions.psd1 b/PSQualityCheck.Functions.psd1 index dcce0c3..4e5a3f2 100644 --- a/PSQualityCheck.Functions.psd1 +++ b/PSQualityCheck.Functions.psd1 @@ -1,143 +1,139 @@ +# +# Module manifest for module 'PSQualityCheck.Functions' +# +# Generated by: Andrew Davidson +# +# Generated on: 29/12/2020 +# + @{ - # Script module or binary module file associated with this manifest. - RootModule = 'PSQualityCheck.Functions.psm1' +# Script module or binary module file associated with this manifest. +RootModule = 'PSQualityCheck.Functions.psm1' - # Version number of this module. - ModuleVersion = '1.0.7' +# Version number of this module. +ModuleVersion = '1.0.8' - # Supported PSEditions - # CompatiblePSEditions = @() +# Supported PSEditions +# CompatiblePSEditions = @() - # ID used to uniquely identify this module - GUID = '5ddfdef7-3985-476d-92d8-e7db35bf960b' +# ID used to uniquely identify this module +GUID = '5ddfdef7-3985-476d-92d8-e7db35bf960b' - # Author of this module - Author = 'Andrew Davidson' +# Author of this module +Author = 'Andrew Davidson' - # Company or vendor of this module - # CompanyName = 'Unknown' +# Company or vendor of this module +CompanyName = 'Andrew Davidson' - # Copyright statement for this module - Copyright = '(c) Andrew Davidson. All rights reserved.' +# Copyright statement for this module +Copyright = '(c) Andrew Davidson. All rights reserved.' - # Description of the functionality provided by this module - Description = 'This module contains supporting functions for PSQualityCheck.' +# Description of the functionality provided by this module +Description = 'This module contains supporting functions for PSQualityCheck.' - # Minimum version of the PowerShell engine required by this module - PowerShellVersion = '5.0' +# Minimum version of the PowerShell engine required by this module +PowerShellVersion = '5.0' - # Name of the PowerShell host required by this module - # PowerShellHostName = '' +# Name of the PowerShell host required by this module +# PowerShellHostName = '' - # Minimum version of the PowerShell host required by this module - # PowerShellHostVersion = '' +# Minimum version of the PowerShell host required by this module +# PowerShellHostVersion = '' - # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. - # DotNetFrameworkVersion = '' +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# DotNetFrameworkVersion = '' - # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. - # ClrVersion = '' +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# ClrVersion = '' - # Processor architecture (None, X86, Amd64) required by this module - # ProcessorArchitecture = '' +# 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 = @('PSQualityCheck.Pester.Functions') +# Modules that must be imported into the global environment prior to importing this module +# RequiredModules = @() - # Assemblies that must be loaded prior to importing this module - # RequiredAssemblies = @() +# 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 = @() +# 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 = @() +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() - # Format files (.ps1xml) to be loaded when importing this module - # FormatsToProcess = @() +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() - # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess - # NestedModules = @() +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +# NestedModules = @() - # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. - FunctionsToExport = @( - 'Convert-Help', - 'Export-FunctionsFromModule', - 'Get-FileContent', - 'Get-FileList', - 'Get-FunctionCount', - 'Get-ParsedContent', - 'Get-ParsedFile', - 'Get-ScriptParameters', - 'Get-Token', - 'Get-TokenComponent', - 'Get-TokenMarker', - 'Test-HelpForRequiredTokens', - 'Test-HelpForUnspecifiedTokens', - 'Test-HelpTokensCountIsValid', - 'Test-HelpTokensParamsMatch', - 'Test-HelpTokensTextIsValid', - 'Test-ImportModuleIsValid', - 'Test-ParameterVariablesHaveType' - ) +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = 'Convert-Help', 'Export-FunctionsFromModule', 'Get-FileContent', + 'Get-FileList', 'Get-FunctionCount', 'Get-ParsedContent', + 'Get-ParsedFile', 'Get-ScriptParameters', 'Get-Token', + 'Get-TokenComponent', 'Get-TokenMarker', 'Test-HelpForRequiredTokens', + 'Test-HelpForUnspecifiedTokens', 'Test-HelpTokensCountIsValid', + 'Test-HelpTokensParamsMatch', 'Test-HelpTokensTextIsValid', + 'Test-ImportModuleIsValid', 'Test-ParameterVariablesHaveType' - # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. - CmdletsToExport = @() +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = @() - # Variables to export from this module - VariablesToExport = @() +# Variables to export from this module +# VariablesToExport = @() - # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. - AliasesToExport = @() +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = @() - # DSC resources to export from this module - # DscResourcesToExport = @() +# DSC resources to export from this module +# DscResourcesToExport = @() - # List of all modules packaged with this module - # ModuleList = @() +# List of all modules packaged with this module +# ModuleList = @() - # List of all files packaged with this module - FileList = @('Tests\HelpElementRules.psd1') +# List of all files packaged with this module +FileList = 'Checks\HelpElementRules.psd1' - # 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 = @{ +# 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 = @{ + PSData = @{ - # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('powershell', 'powershell-module', 'tests', 'quality', 'quality-check', 'pester', 'pester-tests') + # Tags applied to this module. These help with module discovery in online galleries. + Tags = 'powershell', 'powershell-module', 'tests', 'quality', 'quality-check', + 'pester', 'pester-tests' - # A URL to the license for this module. - LicenseUri = 'https://github.com/andrewrdavidson/PSQualityCheck/blob/main/LICENSE' + # A URL to the license for this module. + LicenseUri = 'https://github.com/andrewrdavidson/PSQualityCheck/blob/main/LICENSE' - # A URL to the main website for this project. - ProjectUri = 'https://github.com/andrewrdavidson/PSQualityCheck' + # A URL to the main website for this project. + ProjectUri = 'https://github.com/andrewrdavidson/PSQualityCheck' - # A URL to an icon representing this module. - # IconUri = '' + # A URL to an icon representing this module. + # IconUri = '' - # ReleaseNotes of this module - # ReleaseNotes = '' + # ReleaseNotes of this module + # ReleaseNotes = '' - # Prerelease string of this module - # Prerelease = '' + # Prerelease string of this module + # Prerelease = '' - # Flag to indicate whether the module requires explicit user acceptance for install/update/save - # RequireLicenseAcceptance = $false + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false - # External dependent modules of this module - # ExternalModuleDependencies = @() + # External dependent modules of this module + # ExternalModuleDependencies = @() - } # End of PSData hashtable + } # End of PSData hashtable - } # End of PrivateData hashtable +} # End of PrivateData hashtable - # HelpInfo URI of this module - HelpInfoURI = 'https://github.com/andrewrdavidson/PSQualityCheck/wiki' +# HelpInfo URI of this module +HelpInfoURI = 'https://github.com/andrewrdavidson/PSQualityCheck/wiki' - # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. - # DefaultCommandPrefix = '' +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' } diff --git a/PSQualityCheck.Functions.psm1 b/PSQualityCheck.Functions.psm1 index a71a758..ce03e61 100644 --- a/PSQualityCheck.Functions.psm1 +++ b/PSQualityCheck.Functions.psm1 @@ -759,14 +759,14 @@ function Test-HelpForRequiredTokens { $module = Get-Module -Name PSQualityCheck - if (Test-Path -Path (Join-Path -Path $module.ModuleBase -ChildPath "Tests\HelpElementRules.psd1")) { + if (Test-Path -Path (Join-Path -Path $module.ModuleBase -ChildPath "Checks\HelpElementRules.psd1")) { - $helpElementRules = (Import-PowerShellDataFile -Path (Join-Path -Path $module.ModuleBase -ChildPath "Tests\HelpElementRules.psd1")) + $helpElementRules = (Import-PowerShellDataFile -Path (Join-Path -Path $module.ModuleBase -ChildPath "Checks\HelpElementRules.psd1")) } else { - throw "Unable to load Tests\HelpElementRules.psd1" + throw "Unable to load Checks\HelpElementRules.psd1" } @@ -826,14 +826,14 @@ function Test-HelpForUnspecifiedTokens { $module = Get-Module -Name PSQualityCheck - if (Test-Path -Path (Join-Path -Path $module.ModuleBase -ChildPath "Tests\HelpElementRules.psd1")) { + if (Test-Path -Path (Join-Path -Path $module.ModuleBase -ChildPath "Checks\HelpElementRules.psd1")) { - $helpElementRules = (Import-PowerShellDataFile -Path (Join-Path -Path $module.ModuleBase -ChildPath "Tests\HelpElementRules.psd1")) + $helpElementRules = (Import-PowerShellDataFile -Path (Join-Path -Path $module.ModuleBase -ChildPath "Checks\HelpElementRules.psd1")) } else { - throw "Unable to load Tests\HelpElementRules.psd1" + throw "Unable to load Checks\HelpElementRules.psd1" } @@ -901,14 +901,14 @@ function Test-HelpTokensCountIsValid { $module = Get-Module -Name PSQualityCheck - if (Test-Path -Path (Join-Path -Path $module.ModuleBase -ChildPath "Tests\HelpElementRules.psd1")) { + if (Test-Path -Path (Join-Path -Path $module.ModuleBase -ChildPath "Checks\HelpElementRules.psd1")) { - $helpElementRules = (Import-PowerShellDataFile -Path (Join-Path -Path $module.ModuleBase -ChildPath "Tests\HelpElementRules.psd1")) + $helpElementRules = (Import-PowerShellDataFile -Path (Join-Path -Path $module.ModuleBase -ChildPath "Checks\HelpElementRules.psd1")) } else { - throw "Unable to load Tests\HelpElementRules.psd1" + throw "Unable to load Checks\HelpElementRules.psd1" } @@ -1239,3 +1239,4 @@ function Test-ParameterVariablesHaveType { } } + diff --git a/PSQualityCheck.psd1 b/PSQualityCheck.psd1 index 6c70565..b773612 100644 --- a/PSQualityCheck.psd1 +++ b/PSQualityCheck.psd1 @@ -1,125 +1,133 @@ +# +# Module manifest for module 'PSQualityCheck' +# +# Generated by: Andrew Davidson +# +# Generated on: 29/12/2020 +# + @{ - # Script module or binary module file associated with this manifest. - RootModule = 'PSQualityCheck.psm1' +# Script module or binary module file associated with this manifest. +RootModule = 'PSQualityCheck.psm1' + +# Version number of this module. +ModuleVersion = '1.0.8' - # Version number of this module. - ModuleVersion = '1.0.7' +# Supported PSEditions +# CompatiblePSEditions = @() - # Supported PSEditions - # CompatiblePSEditions = @() +# ID used to uniquely identify this module +GUID = '9daebff2-9a44-48e4-9d4b-bed18c3c8e4b' - # ID used to uniquely identify this module - GUID = '9daebff2-9a44-48e4-9d4b-bed18c3c8e4b' +# Author of this module +Author = 'Andrew Davidson' - # Author of this module - Author = 'Andrew Davidson' +# Company or vendor of this module +CompanyName = 'Andrew Davidson' - # Company or vendor of this module - # CompanyName = 'Unknown' +# Copyright statement for this module +Copyright = '(c) Andrew Davidson. All rights reserved.' - # Copyright statement for this module - Copyright = '(c) Andrew Davidson. All rights reserved.' +# Description of the functionality provided by this module +Description = 'This module interfaces to a set of Pester tests that enable the enforcement of quality controls rules on PowerShell scripts and modules.' - # Description of the functionality provided by this module - Description = 'This module interfaces to a set of Pester tests that enable the enforcement of quality controls rules on PowerShell scripts and modules.' +# Minimum version of the PowerShell engine required by this module +PowerShellVersion = '5.0' - # Minimum version of the PowerShell engine required by this module - PowerShellVersion = '5.0' +# Name of the PowerShell host required by this module +# PowerShellHostName = '' - # Name of the PowerShell host required by this module - # PowerShellHostName = '' +# Minimum version of the PowerShell host required by this module +# PowerShellHostVersion = '' - # Minimum version of the PowerShell host required by this module - # PowerShellHostVersion = '' +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# DotNetFrameworkVersion = '' - # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. - # DotNetFrameworkVersion = '' +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# ClrVersion = '' - # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. - # ClrVersion = '' +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' - # 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 = @() - # Modules that must be imported into the global environment prior to importing this module - # RequiredModules = @() +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() - # 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 = @() - # 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 = @() - # Type files (.ps1xml) to be loaded when importing this module - # TypesToProcess = @() +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() - # Format files (.ps1xml) to be loaded when importing this module - # FormatsToProcess = @() +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +NestedModules = @('PSQualityCheck.Functions.psd1') - # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess - NestedModules = @( - 'PSQualityCheck.Functions.psd1' - ) +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = 'Invoke-PSQualityCheck' - # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. - FunctionsToExport = 'Invoke-PSQualityCheck' +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = @() - # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. - CmdletsToExport = @() +# Variables to export from this module +# VariablesToExport = @() - # Variables to export from this module - VariablesToExport = @() +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = @() - # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. - AliasesToExport = @() +# DSC resources to export from this module +# DscResourcesToExport = @() - # DSC resources to export from this module - # DscResourcesToExport = @() +# List of all modules packaged with this module +# ModuleList = @() - # List of all modules packaged with this module - # ModuleList = @() +# List of all files packaged with this module +# FileList = @() - # 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 = @{ - # 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 = @{ - PSData = @{ + # Tags applied to this module. These help with module discovery in online galleries. + Tags = 'powershell', 'powershell-module', 'tests', 'quality', 'quality-check', + 'pester', 'pester-tests' - # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('powershell', 'powershell-module', 'tests', 'quality', 'quality-check', 'pester', 'pester-tests') + # A URL to the license for this module. + LicenseUri = 'https://github.com/andrewrdavidson/PSQualityCheck/blob/main/LICENSE' - # A URL to the license for this module. - LicenseUri = 'https://github.com/andrewrdavidson/PSQualityCheck/blob/main/LICENSE' + # A URL to the main website for this project. + ProjectUri = 'https://github.com/andrewrdavidson/PSQualityCheck' - # A URL to the main website for this project. - ProjectUri = 'https://github.com/andrewrdavidson/PSQualityCheck' + # A URL to an icon representing this module. + # IconUri = '' - # A URL to an icon representing this module. - # IconUri = '' + # ReleaseNotes of this module + # ReleaseNotes = '' - # ReleaseNotes of this module - # ReleaseNotes = '' + # Prerelease string of this module + # Prerelease = '' - # Prerelease string of this module - # Prerelease = '' + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false - # Flag to indicate whether the module requires explicit user acceptance for install/update/save - # RequireLicenseAcceptance = $false + # External dependent modules of this module + # ExternalModuleDependencies = @() - # External dependent modules of this module - # ExternalModuleDependencies = @() + } # End of PSData hashtable - } # End of PSData hashtable +} # End of PrivateData hashtable - } # End of PrivateData hashtable +# HelpInfo URI of this module +HelpInfoURI = 'https://github.com/andrewrdavidson/PSQualityCheck/wiki' - # HelpInfo URI of this module - HelpInfoURI = 'https://github.com/andrewrdavidson/PSQualityCheck/wiki' +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' - # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. - # DefaultCommandPrefix = '' } diff --git a/PSQualityCheck.psm1 b/PSQualityCheck.psm1 index d946e0d..a144040 100644 --- a/PSQualityCheck.psm1 +++ b/PSQualityCheck.psm1 @@ -180,12 +180,12 @@ function Invoke-PSQualityCheck { $functionExtractPath = Join-Path -Path $Env:TEMP -ChildPath (New-Guid).Guid # Run the Module tests on all the valid module files found - $container1 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath "Tests\Module.Tests.ps1") -Data @{ Source = $modulesToTest } + $container1 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath "Checks\Module.Tests.ps1") -Data @{ Source = $modulesToTest } $configuration.Run.Container = $container1 $moduleResults = Invoke-Pester -Configuration $configuration # Extract all the functions from the modules into individual .ps1 files ready for testing - $container2 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath "Tests\Function-Extraction.Tests.ps1") -Data @{ Source = $modulesToTest; FunctionExtractPath = $functionExtractPath } + $container2 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath "Checks\Function-Extraction.Tests.ps1") -Data @{ Source = $modulesToTest; FunctionExtractPath = $functionExtractPath } $configuration.Run.Container = $container2 $extractionResults = Invoke-Pester -Configuration $configuration @@ -193,7 +193,7 @@ function Invoke-PSQualityCheck { $extractedScriptsToTest = Get-ChildItem -Path $functionExtractPath -Include '*.ps1' -Recurse # Run the Script tests against all the extracted functions .ps1 files - $container3 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath "Tests\Script.Tests.ps1") -Data @{ Source = $extractedScriptsToTest; SonarQubeRules = $SonarQubeRulesPath } + $container3 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath "Checks\Script.Tests.ps1") -Data @{ Source = $extractedScriptsToTest; SonarQubeRules = $SonarQubeRulesPath } $configuration.Run.Container = $container3 $extractedScriptResults = Invoke-Pester -Configuration $configuration @@ -202,7 +202,7 @@ function Invoke-PSQualityCheck { if ($scriptsToTest.Count -ge 1) { # Run the Script tests against all the valid script files found - $container3 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath "Tests\Script.Tests.ps1") -Data @{ Source = $scriptsToTest; SonarQubeRules = $SonarQubeRulesPath } + $container3 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath "Checks\Script.Tests.ps1") -Data @{ Source = $scriptsToTest; SonarQubeRules = $SonarQubeRulesPath } $configuration.Run.Container = $container3 $scriptResults = Invoke-Pester -Configuration $configuration @@ -229,6 +229,7 @@ function Invoke-PSQualityCheck { $failed += $moduleResults.FailedCount $skipped += $moduleResults.SkippedCount } + if ($null -ne $extractionResults) { $qualityCheckResults += @{ @@ -244,6 +245,7 @@ function Invoke-PSQualityCheck { $failed += $extractionResults.FailedCount $skipped += $extractionResults.SkippedCount } + if ($null -ne $extractedScriptResults) { $qualityCheckResults += @{ @@ -260,6 +262,7 @@ function Invoke-PSQualityCheck { $failed += $extractedScriptResults.FailedCount $skipped += $extractedScriptResults.SkippedCount } + if ($null -ne $scriptResults) { $qualityCheckResults += @{ @@ -276,6 +279,7 @@ function Invoke-PSQualityCheck { $failed += $scriptResults.FailedCount $skipped += $scriptResults.SkippedCount } + $qualityCheckResults += @{ 'Test' = "Total" @@ -304,3 +308,4 @@ function Invoke-PSQualityCheck { } } + diff --git a/Source/Build.Properties.json b/Source/Build.Properties.json new file mode 100644 index 0000000..0ec6541 --- /dev/null +++ b/Source/Build.Properties.json @@ -0,0 +1,37 @@ +{ + "Author": "Andrew Davidson", + "Company": "Andrew Davidson", + "Copyright": "(c) Andrew Davidson. All rights reserved.", + "Description": { + "PSQualityCheck": "This module interfaces to a set of Pester tests that enable the enforcement of quality controls rules on PowerShell scripts and modules.", + "PSQualityCheck.Functions": "This module contains supporting functions for PSQualityCheck." + }, + "FileList": { + "PSQualityCheck": null, + "PSQualityCheck.Functions": [ + "Checks\\HelpElementRules.psd1" + ] + }, + "GUID": { + "PSQualityCheck": "9daebff2-9a44-48e4-9d4b-bed18c3c8e4b", + "PSQualityCheck.Functions": "5ddfdef7-3985-476d-92d8-e7db35bf960b" + }, + "HelpInfoURI": "https://github.com/andrewrdavidson/PSQualityCheck/wiki", + "LicenseUri": "https://github.com/andrewrdavidson/PSQualityCheck/blob/main/LICENSE", + "ModuleVersion": "1.0.8", + "NestedModules": { + "PSQualityCheck": "PSQualityCheck.Functions.psd1", + "PSQualityCheck.Functions": null + }, + "PowerShellVersion": "5.0", + "ProjectUri": "https://github.com/andrewrdavidson/PSQualityCheck", + "Tags": [ + "powershell", + "powershell-module", + "tests", + "quality", + "quality-check", + "pester", + "pester-tests" + ] +} diff --git a/Source/Invoke-Build.ps1 b/Source/Invoke-Build.ps1 new file mode 100644 index 0000000..4ac062d --- /dev/null +++ b/Source/Invoke-Build.ps1 @@ -0,0 +1,91 @@ +[CmdletBinding()] +[OutputType([System.Void])] +param ( +) + +# Get the ModuleVersion and the GUID +$buildProperties = Get-Content -Path "Build.Properties.json" | ConvertFrom-Json + +# Generate build location +$builtModuleLocation = (Split-Path -Path (Get-Location) -Parent) +Write-Verbose "Build Location: $builtModuleLocation" + +# get all the directories in the modules folder which relates to each module to generate +$sourceScriptFolders = Get-ChildItem -Path $PSScriptRoot -Filter "PSQuality*" -Directory + +foreach ($folder in $sourceScriptFolders) { + + # generate a module + Write-Verbose "Processing folder: $($folder.Name)" + + # generate the name of the module + $moduleName = "{0}{1}" -f $folder.Name, '.psm1' + $moduleFileName = Join-Path -Path $builtModuleLocation -ChildPath $moduleName + + # remove the module if it exists + if (Test-Path -Path $moduleFileName) { + Remove-Item -Path $moduleFileName -Force + } + + # now loop through each folder at get the files + # these are the functions + $functionFiles = Get-ChildItem -Path $folder -File + $functionsToExport = @() + + Write-Verbose "Adding content to module" + + foreach ($function in $functionFiles) { + + # Add the content of the function file + Get-Content -Path $function.FullName | Add-Content -Path $moduleFileName + + $functionsToExport += $function.BaseName + + # add a blank line between the functions + "" | Add-Content -Path $moduleFileName + + } + + # generate a manifest + Write-Verbose "Generating manifest" + + # generate the name of the module + $manifestName = "{0}{1}" -f $folder.Name, '.psd1' + $manifestFileName = Join-Path -Path $builtModuleLocation -ChildPath $manifestName + + # remove the module if it exists + if (Test-Path -Path $manifestFileName) { + Remove-Item -Path $manifestFileName -Force + } + + $newModuleManifest = @{ + Path = $manifestFileName + Guid = $buildProperties.Guid.($folder.Name) + RootModule = ("{0}{1}" -f $folder.Name, '.psm1') + + ModuleVersion = $buildProperties.ModuleVersion + PowerShellVersion = $buildProperties.PowerShellVersion + + FunctionsToExport = $functionsToExport + CmdletsToExport = @() + VariablesToExport = @() + AliasesToExport = @() + + Author = $buildProperties.Author + Company = $buildProperties.Company + Copyright = $buildProperties.Copyright + Description = $buildProperties.Description.($folder.Name) + FileList = $buildProperties.FileList.($folder.Name) + HelpInfoURI = $buildProperties.HelpInfoURI + LicenseUri = $buildProperties.LicenseUri + ProjectUri = $buildProperties.ProjectUri + Tags = $buildProperties.Tags + + NestedModules = $buildProperties.NestedModules.($folder.Name) + + + } + + New-ModuleManifest @newModuleManifest + +} diff --git a/Source/PSQualityCheck.Functions/Convert-Help.ps1 b/Source/PSQualityCheck.Functions/Convert-Help.ps1 new file mode 100644 index 0000000..606b04f --- /dev/null +++ b/Source/PSQualityCheck.Functions/Convert-Help.ps1 @@ -0,0 +1,147 @@ +function Convert-Help { + <# + .SYNOPSIS + Convert the help comment into an object + + .DESCRIPTION + Convert the help comment into an object containing all the elements from the help comment + + .PARAMETER HelpComment + A string containing the Help Comment + + .EXAMPLE + $helpObject = Convert-Help -HelpComment $helpComment + #> + [CmdletBinding()] + [OutputType([HashTable], [System.Exception])] + param ( + [parameter(Mandatory = $true)] + [string]$HelpComment + ) + + # These are the possible Help Comment elements that the script will look for + # .SYNOPSIS + # .DESCRIPTION + # .PARAMETER + # .EXAMPLE + # .INPUTS + # .OUTPUTS + # .NOTES + # .LINK + # .COMPONENT + # .ROLE + # .FUNCTIONALITY + # .FORWARDHELPTARGETNAME + # .FORWARDHELPCATEGORY + # .REMOTEHELPRUNSPACE + # .EXTERNALHELP + + # This function will go through the help and work out which elements are where and what text they contain + + try { + + if (-not( + $HelpComment.StartsWith("<#") -and + $HelpComment.EndsWith("#>") + )) { + throw "Help does not appear to be a comment block" + } + + # an array of string help elements to look for + $helpElementsToFind = + '.SYNOPSIS', + '.DESCRIPTION', + '.PARAMETER', + '.EXAMPLE', + '.INPUTS', + '.OUTPUTS', + '.NOTES', + '.LINK', + '.COMPONENT', + '.ROLE', + '.FUNCTIONALITY', + '.FORWARDHELPTARGETNAME', + '.FORWARDHELPCATEGORY', + '.REMOTEHELPRUNSPACE', + '.EXTERNALHELP' + + # Split the single comment string into it's line components + $commentArray = ($HelpComment -split '\n').Trim() + + # initialise an empty HashTable ready for the found help elements to be stored + $foundElements = @{} + $numFound = 0 + + # loop through all the 'lines' of the help comment + for ($line = 0; $line -lt $commentArray.Count; $line++) { + + # get the first 'word' of the help comment. This is required so that we can + # match '.PARAMETER' since it has a parameter name after it + $helpElementName = ($commentArray[$line] -split " ")[0] + + # see whether the $helpElements array contains the first 'word' + if ($helpElementsToFind -contains $helpElementName) { + + $numFound++ + + if ($numFound -ge 2) { + + # of it's the second element then we must set the help comment text of the + # previous element to the found text so far, then reset it + + $lastElement = @($foundElements[$lastHelpElement]) + $lastElement[$lastElement.Count - 1].Text = $help + $foundElements[$lastHelpElement] = $lastElement + + $help = $null + } + + # this should be an array of HashTables {LineNumber, Name & Text} + $currentElement = @($foundElements[$helpElementName]) + + $newElement = @{} + $newElement.LineNumber = $line + $newElement.Name = ($commentArray[$line] -split " ")[1] + $newElement.Text = "" + + if ($null -eq $currentElement[0]) { + + $currentElement = $newElement + + } + else { + $currentElement += $newElement + } + + # update the foundItems HashTable with the new found element + $foundElements[$helpElementName] = $currentElement + + $lastHelpElement = $helpElementName + + } + else { + + if ($numFound -ge 1 -and $line -ne ($commentArray.Count - 1)) { + + $help += $commentArray[$line] + + } + + } + + } + + # process the very last one + $currentElement = @($foundElements[$lastHelpElement]) + $currentElement[$currentElement.Count - 1].Text = $help + $foundElements[$lastHelpElement] = $currentElement + + return $foundElements + } + catch { + + throw $_.Exception.Message + + } + +} diff --git a/Source/PSQualityCheck.Functions/Export-FunctionsFromModule.ps1 b/Source/PSQualityCheck.Functions/Export-FunctionsFromModule.ps1 new file mode 100644 index 0000000..84365bc --- /dev/null +++ b/Source/PSQualityCheck.Functions/Export-FunctionsFromModule.ps1 @@ -0,0 +1,106 @@ +function Export-FunctionsFromModule { + <# + .SYNOPSIS + Export functions from a PowerShell module (.psm1) + + .DESCRIPTION + Takes a PowerShell module and outputs a single file for each function containing the code for that function + + .PARAMETER Path + A string Path containing the full file name and path to the module + + .PARAMETER FunctionExtractPath + A string Path containing the full path to the extraction folder + + .EXAMPLE + Export-FunctionsFromModule -Path 'c:\path.to\module.psm1' -FunctionExtractionPath 'c:\extract' + #> + [CmdletBinding()] + [OutputType([System.Void])] + param ( + [parameter(Mandatory = $true)] + [string]$Path, + [parameter(Mandatory = $true)] + [string]$FunctionExtractPath + ) + + # Get the file properties of our module + $fileProperties = (Get-Item -LiteralPath $Path) + $moduleName = $fileProperties.BaseName + + # Generate a new temporary output path for our extracted functions + $FunctionOutputPath = Join-Path -Path $FunctionExtractPath -ChildPath $moduleName + New-Item $FunctionOutputPath -ItemType 'Directory' + + # Get the plain content of the module file + $ModuleFileContent = Get-Content -Path $Path -ErrorAction Stop + + # Parse the PowerShell module using PSParser + $ParserErrors = $null + $ParsedFileFunctions = [System.Management.Automation.PSParser]::Tokenize($ModuleFileContent, [ref]$ParserErrors) + + # Create an array of where each reference of the keyword 'function' is + $ParsedFunctions = ($ParsedFileFunctions | Where-Object { $_.Type -eq "Keyword" -and $_.Content -like 'function' }) + + # Initialise the $parsedFunction tracking variable + $parsedFunction = 0 + + if ($ParsedFunctions.Count -ge 1) { + + foreach ($Function in $ParsedFunctions) { + + # Counter for the array $ParsedFunction to help find the 'next' function + $parsedFunction++ + + # Get the name of the current function + # Cheat: Simply getting all properties with the same line number as the 'function' statement + $FunctionProperties = $ParsedFileFunctions | Where-Object { $_.StartLine -eq $Function.StartLine } + $FunctionName = ($FunctionProperties | Where-Object { $_.Type -eq "CommandArgument" }).Content + + # Establish the Start and End lines for the function in the main module file + if ($parsedFunction -eq $ParsedFunctions.Count) { + + # This is the last function in the module so set the last line of this function to be the last line in the module file + + $StartLine = ($Function.StartLine) + for ($line = $ModuleFileContent.Count; $line -gt $Function.StartLine; $line--) { + if ($ModuleFileContent[$line] -like "}") { + $EndLine = $line + break + } + } + } + else { + + $StartLine = ($Function.StartLine) + + # EndLine needs to be where the last } is + for ($line = $ParsedFunctions[$parsedFunction].StartLine; $line -gt $Function.StartLine; $line--) { + if ($ModuleFileContent[$line] -like "}") { + $EndLine = $line + break + } + } + + } + + # Setup the FunctionOutputFile for the function file + $FunctionOutputFileName = "{0}\{1}{2}" -f $FunctionOutputPath, $FunctionName, ".ps1" + + # If the file doesn't exist create an empty file so that we can Add-Content to it + if (-not (Test-Path -Path $FunctionOutputFileName)) { + Out-File -FilePath $FunctionOutputFileName + } + + # Output the lines of the function to the FunctionOutputFile + for ($line = $StartLine; $line -lt $EndLine; $line++) { + Add-Content -Path $FunctionOutputFileName -Value $ModuleFileContent[$line] + } + + } + } + else { + Write-Warning "Module contains no functions, skipping" + } + +} diff --git a/Source/PSQualityCheck.Functions/Get-FileContent.ps1 b/Source/PSQualityCheck.Functions/Get-FileContent.ps1 new file mode 100644 index 0000000..05e3fcd --- /dev/null +++ b/Source/PSQualityCheck.Functions/Get-FileContent.ps1 @@ -0,0 +1,74 @@ +function Get-FileContent { + <# + .SYNOPSIS + Gets the content of a script file + + .DESCRIPTION + Gets the content of the file or the content of the function inside the file + + .PARAMETER File + A file name to parse + + .EXAMPLE + $fileContent = Get-FileContent -File 'c:\file.txt' + #> + [CmdletBinding()] + [OutputType([System.String[]])] + param ( + [parameter(Mandatory = $true)] + [string]$File + ) + + $fileContent = Get-Content -Path $File + + $parserErrors = $null + + # If the file content is null (an empty file) then generate an empty parsedFileFunctions array to allow the function to complete + if ([string]::IsNullOrEmpty($fileContent)) { + $parsedFileFunctions = @() + } + else { + $parsedFileFunctions = [System.Management.Automation.PSParser]::Tokenize($fileContent, [ref]$parserErrors) + } + + # Create an array of where each reference of the keyword 'function' is + $parsedFunctions = ($parsedFileFunctions | Where-Object { $_.Type -eq "Keyword" -and $_.Content -like 'function' }) + + if ($parsedFunctions) { + + foreach ($function in $parsedFunctions) { + + $startLine = ($function.StartLine) + + for ($line = $fileContent.Count; $line -gt $function.StartLine; $line--) { + + if ($fileContent[$line] -like "}") { + + $endLine = $line + break + + } + + } + + # Output the lines of the function to the FunctionOutputFile + for ($line = $startLine; $line -lt $endLine; $line++) { + $parsedFileContent += $fileContent[$line] + $parsedFileContent += "`n" + } + + } + + } + else { + + for ($line = 0; $line -lt $fileContent.Count; $line++) { + $parsedFileContent += $fileContent[$line] + $parsedFileContent += "`n" + } + + } + + return $parsedFileContent + +} diff --git a/Source/PSQualityCheck.Functions/Get-FileList.ps1 b/Source/PSQualityCheck.Functions/Get-FileList.ps1 new file mode 100644 index 0000000..83c9311 --- /dev/null +++ b/Source/PSQualityCheck.Functions/Get-FileList.ps1 @@ -0,0 +1,42 @@ +function Get-FileList { + <# + .SYNOPSIS + Return a list of files + + .DESCRIPTION + Return a list of files from the specified path matching the passed extension + + .PARAMETER Path + A string containing the path + + .PARAMETER Extension + A string containing the extension + + .EXAMPLE + $files = Get-FileList -Path 'c:\folder' -Extension ".ps1" + #> + [CmdletBinding()] + [OutputType([System.String[]])] + param ( + [parameter(Mandatory = $true)] + [string]$Path, + [parameter(Mandatory = $true)] + [string]$Extension + ) + + $Extension = $Extension + + $FileNameArray = @() + + if (Test-Path -Path $Path) { + + # Get the list of files + $SelectedFilesArray = Get-ChildItem -Path $Path -Recurse -Exclude "*.Tests.*" | Where-Object { $_.Extension -eq $Extension } | Select-Object -Property FullName + # Convert to a string array of filenames + $SelectedFilesArray | ForEach-Object { $FileNameArray += [string]$_.FullName } + + } + + return $FileNameArray + +} diff --git a/Source/PSQualityCheck.Functions/Get-FunctionCount.ps1 b/Source/PSQualityCheck.Functions/Get-FunctionCount.ps1 new file mode 100644 index 0000000..095b5f9 --- /dev/null +++ b/Source/PSQualityCheck.Functions/Get-FunctionCount.ps1 @@ -0,0 +1,98 @@ +function Get-FunctionCount { + <# + .SYNOPSIS + Return the count of functions within Module and its Manifest + + .DESCRIPTION + Return the count of functions in the Module and Manifest and whether they appear in their counterpart. + e.g. Whether the functions in the manifest appear in the module and vice versa + + .PARAMETER ModuleFile + A string containing the Module filename + + .PARAMETER ManifestFile + A string containing the Manifest filename + + .EXAMPLE + ($ExportedCommandsCount, $CommandFoundInModuleCount, $CommandInModuleCount, $CommandFoundInManifestCount) = Get-FunctionCount -Module $moduleFile -Manifest $manifestFile + + #> + [CmdletBinding()] + [OutputType([Int[]])] + param ( + [parameter(Mandatory = $true)] + [string]$ModuleFile, + [parameter(Mandatory = $true)] + [string]$ManifestFile + ) + + try { + if (Test-Path -Path $ManifestFile) { + $ExportedCommandsCount = (Test-ModuleManifest -Path $ManifestFile).ExportedCommands.Count + } + else { + throw "Manifest file doesn't exist" + } + } + catch { + $ExportedCommands = @() + $ExportedCommandsCount = 0 + } + try { + if (Test-Path -Path $ModuleFile) { + ($ParsedModule, $ParserErrors) = Get-ParsedFile -Path $ModuleFile + } + else { + throw "Module file doesn't exist" + } + } + catch { + $ParsedModule = @() + $ParserErrors = 1 + } + + $CommandFoundInModuleCount = 0 + $CommandFoundInManifestCount = 0 + $CommandInModuleCount = 0 + + if ( -not ([string]::IsNullOrEmpty($ParsedModule))) { + + foreach ($ExportedCommand in $ExportedCommands.Keys) { + + if ( ($ParsedModule | Where-Object { $_.Type -eq "CommandArgument" -and $_.Content -eq $ExportedCommand })) { + + $CommandFoundInModuleCount++ + + } + + } + + $functionNames = @() + + $functionKeywords = ($ParsedModule | Where-Object { $_.Type -eq "Keyword" -and $_.Content -eq "function" }) + $functionKeywords | ForEach-Object { + + $functionLineNo = $_.StartLine + $functionNames += ($ParsedModule | Where-Object { $_.Type -eq "CommandArgument" -and $_.StartLine -eq $functionLineNo }) + + } + } + + if ($ExportedCommandsCount -ge 1) { + + $functionNames | ForEach-Object { + + $CommandInModuleCount++ + if ($ExportedCommands.ContainsKey($_.Content)) { + + $CommandFoundInManifestCount++ + + } + + } + + } + + return ($ExportedCommandsCount, $CommandFoundInModuleCount, $CommandInModuleCount, $CommandFoundInManifestCount) + +} diff --git a/Source/PSQualityCheck.Functions/Get-ParsedContent.ps1 b/Source/PSQualityCheck.Functions/Get-ParsedContent.ps1 new file mode 100644 index 0000000..1751820 --- /dev/null +++ b/Source/PSQualityCheck.Functions/Get-ParsedContent.ps1 @@ -0,0 +1,29 @@ +function Get-ParsedContent { + <# + .SYNOPSIS + Get the tokenized content of the passed data + + .DESCRIPTION + Get and return the tokenized content of the passed PowerShell script content + + .PARAMETER Content + A string containing PowerShell script content + + .EXAMPLE + ($ParsedModule, $ParserErrorCount) = Get-ParsedContent -Content $fileContent + #> + [CmdletBinding()] + [OutputType([System.Object[]], [System.Void])] + param ( + [parameter(Mandatory = $true)] + [string]$Content + ) + + if (-not ([string]::IsNullOrEmpty($Content))) { + $ParserErrors = $null + $ParsedModule = [System.Management.Automation.PSParser]::Tokenize($Content, [ref]$ParserErrors) + + return $ParsedModule, ($ParserErrors.Count) + } + +} diff --git a/Source/PSQualityCheck.Functions/Get-ParsedFile.ps1 b/Source/PSQualityCheck.Functions/Get-ParsedFile.ps1 new file mode 100644 index 0000000..3e53144 --- /dev/null +++ b/Source/PSQualityCheck.Functions/Get-ParsedFile.ps1 @@ -0,0 +1,38 @@ +function Get-ParsedFile { + <# + .SYNOPSIS + Get the tokenized content of the passed file + + .DESCRIPTION + Get and return the tokenized content of the passed PowerShell file + + .PARAMETER Path + A string containing PowerShell filename + + .EXAMPLE + ($ParsedModule, $ParserErrors) = Get-ParsedFile -Path $ModuleFile + + #> + [CmdletBinding()] + [OutputType([System.Object[]])] + param ( + [parameter(Mandatory = $true)] + [string]$Path + ) + + try { + if (-not(Test-Path -Path $Path)) { + throw "$Path doesn't exist" + } + } + catch { + throw $_ + } + + $fileContent = Get-Content -Path $Path -Raw + + ($ParsedModule, $ParserErrorCount) = Get-ParsedContent -Content $fileContent + + return $ParsedModule, $ParserErrorCount + +} diff --git a/Source/PSQualityCheck.Functions/Get-ScriptParameters.ps1 b/Source/PSQualityCheck.Functions/Get-ScriptParameters.ps1 new file mode 100644 index 0000000..39241b8 --- /dev/null +++ b/Source/PSQualityCheck.Functions/Get-ScriptParameters.ps1 @@ -0,0 +1,77 @@ +function Get-ScriptParameters { + <# + .SYNOPSIS + Get a list of the parameters in the param block + + .DESCRIPTION + Create a list of the parameters, and their type (if available) from the param block + + .PARAMETER Content + A string containing the text of the script + + .EXAMPLE + $parameterVariables = Get-ScriptParameters -Content $Content + #> + [CmdletBinding()] + [OutputType([System.Exception], [HashTable])] + param + ( + [parameter(Mandatory = $true)] + [String]$Content + ) + + try { + + $parsedScript = [System.Management.Automation.Language.Parser]::ParseInput($Content, [ref]$null, [ref]$null) + + [string]$paramBlock = $parsedScript.ParamBlock + + ($ParsedContent, $ParserErrorCount) = Get-ParsedContent -Content $paramBlock + + $paramBlockArray = ($paramBlock -split '\n').Trim() + + $parametersFound = @{} + + for ($line = 0; $line -le $paramBlockArray.Count; $line++) { + + $paramToken = @($ParsedContent | Where-Object { $_.StartLine -eq $line }) + + foreach ($token in $paramToken) { + + if ($token.Type -eq 'Attribute' -and $token.Content -eq "Parameter") { + + # break the inner loop because this token doesn't contain a variable for definite + break + } + + if ($token.Type -eq 'Type') { + + # Found a type for a parameter + $foundType = $token.Content + + } + + if ($token.Type -eq 'Variable') { + + # Found a variable + $parametersFound[$token.Content] = $foundType + $foundType = $null + break + + } + + } + + + } + + return $parametersFound + + } + catch { + + throw $_.Exception.Message + + } + +} diff --git a/Source/PSQualityCheck.Functions/Get-Token.ps1 b/Source/PSQualityCheck.Functions/Get-Token.ps1 new file mode 100644 index 0000000..f544d60 --- /dev/null +++ b/Source/PSQualityCheck.Functions/Get-Token.ps1 @@ -0,0 +1,38 @@ +function Get-Token { + <# + .SYNOPSIS + Get token(s) from the tokenized output + + .DESCRIPTION + Get token(s) from the tokenized output matching the passed Type and Content + + .PARAMETER ParsedFileContent + A string array containing the Tokenized data + + .PARAMETER Type + The token type to be found + + .PARAMETER Content + The token content (or value) to be found + + .EXAMPLE + $outputTypeToken = (Get-Token -ParsedFileContent $ParsedFile -Type "Attribute" -Content "OutputType") + #> + [CmdletBinding()] + [OutputType([System.Object[]])] + param ( + [parameter(Mandatory = $true)] + [System.Object[]]$ParsedFileContent, + [parameter(Mandatory = $true)] + [string]$Type, + [parameter(Mandatory = $true)] + [string]$Content + ) + + $token = Get-TokenMarker -ParsedFileContent $ParsedFileContent -Type $Type -Content $Content + + $tokens = Get-TokenComponent -ParsedFileContent $ParsedFileContent -StartLine $token.StartLine + + return $tokens + +} diff --git a/Source/PSQualityCheck.Functions/Get-TokenComponent.ps1 b/Source/PSQualityCheck.Functions/Get-TokenComponent.ps1 new file mode 100644 index 0000000..b9c6fae --- /dev/null +++ b/Source/PSQualityCheck.Functions/Get-TokenComponent.ps1 @@ -0,0 +1,35 @@ +function Get-TokenComponent { + <# + .SYNOPSIS + Get all the tokens components from a single line + + .DESCRIPTION + Get all the tokens components from a single line in the tokenized content + + .PARAMETER ParsedFileContent + A string array containing the tokenized content + + .PARAMETER StartLine + A integer of the starting line to parse + + .EXAMPLE + $tokens = Get-TokenComponent -ParsedFileContent $ParsedFileContent -StartLine 10 + #> + [CmdletBinding()] + [OutputType([System.Object[]])] + param ( + [parameter(Mandatory = $true)] + [System.Object[]]$ParsedFileContent, + [parameter(Mandatory = $true)] + [int]$StartLine + ) + + #* This is just to satisfy the PSScriptAnalyzer + #* which can't find the variables in the 'Where-Object' clause (even though it's valid) + $StartLine = $StartLine + + $tokenComponents = @($ParsedFileContent | Where-Object { $_.StartLine -eq $StartLine }) + + return $tokenComponents + +} diff --git a/Source/PSQualityCheck.Functions/Get-TokenMarker.ps1 b/Source/PSQualityCheck.Functions/Get-TokenMarker.ps1 new file mode 100644 index 0000000..5c62797 --- /dev/null +++ b/Source/PSQualityCheck.Functions/Get-TokenMarker.ps1 @@ -0,0 +1,41 @@ +function Get-TokenMarker { + <# + .SYNOPSIS + Gets token from the tokenized output + + .DESCRIPTION + Gets single token from the tokenized output matching the passed Type and Content + + .PARAMETER ParsedFileContent + A string array containing the Tokenized data + + .PARAMETER Type + The token type to be found + + .PARAMETER Content + The token content (or value) to be found + + .EXAMPLE + $token = Get-TokenMarker -ParsedFileContent $ParsedFileContent -Type $Type -Content $Content + #> + [CmdletBinding()] + [OutputType([System.Object[]])] + param ( + [parameter(Mandatory = $true)] + [System.Object[]]$ParsedFileContent, + [parameter(Mandatory = $true)] + [string]$Type, + [parameter(Mandatory = $true)] + [string]$Content + ) + + #* This is just to satisfy the PSScriptAnalyzer + #* which can't find the variables in the 'Where-Object' clause (even though it's valid) + $Type = $Type + $Content = $Content + + $token = @($ParsedFileContent | Where-Object { $_.Type -eq $Type -and $_.Content -eq $Content }) + + return $token + +} diff --git a/Source/PSQualityCheck.Functions/Test-HelpForRequiredTokens.ps1 b/Source/PSQualityCheck.Functions/Test-HelpForRequiredTokens.ps1 new file mode 100644 index 0000000..131d8f4 --- /dev/null +++ b/Source/PSQualityCheck.Functions/Test-HelpForRequiredTokens.ps1 @@ -0,0 +1,66 @@ +function Test-HelpForRequiredTokens { + <# + .SYNOPSIS + Check that help tokens contain required tokens + + .DESCRIPTION + Check that the help comments contain tokens that are specified in the external verification data file + + .PARAMETER HelpTokens + A string containing the text of the Help Comment + + .EXAMPLE + Test-HelpForRequiredTokens -HelpComment $helpComment + #> + [CmdletBinding()] + [OutputType([System.Exception], [System.Void])] + param ( + [parameter(Mandatory = $true)] + [HashTable]$HelpTokens + ) + + try { + + $module = Get-Module -Name PSQualityCheck + + if (Test-Path -Path (Join-Path -Path $module.ModuleBase -ChildPath "Checks\HelpElementRules.psd1")) { + + $helpElementRules = (Import-PowerShellDataFile -Path (Join-Path -Path $module.ModuleBase -ChildPath "Checks\HelpElementRules.psd1")) + + } + else { + + throw "Unable to load Checks\HelpElementRules.psd1" + + } + + $tokenErrors = @() + + for ($order = 1; $order -le $helpElementRules.Count; $order++) { + + $token = $helpElementRules."$order" + + if ($token.Key -notin $HelpTokens.Keys ) { + + if ($token.Required -eq $true) { + + $tokenErrors += $token.Key + + } + + } + + } + + if ($tokenErrors.Count -ge 1) { + throw "Missing required token(s): $tokenErrors" + } + + } + catch { + + throw $_.Exception.Message + + } + +} diff --git a/Source/PSQualityCheck.Functions/Test-HelpForUnspecifiedTokens.ps1 b/Source/PSQualityCheck.Functions/Test-HelpForUnspecifiedTokens.ps1 new file mode 100644 index 0000000..34f2fb6 --- /dev/null +++ b/Source/PSQualityCheck.Functions/Test-HelpForUnspecifiedTokens.ps1 @@ -0,0 +1,71 @@ +function Test-HelpForUnspecifiedTokens { + <# + .SYNOPSIS + Check that help tokens do not contain unspecified tokens + + .DESCRIPTION + Check that the help comments do not contain tokens that are not specified in the external verification data file + + .PARAMETER HelpTokens + A string containing the text of the Help Comment + + .EXAMPLE + Test-HelpForUnspecifiedTokens -HelpComment $helpComment + #> + [CmdletBinding()] + [OutputType([System.Exception], [System.Void])] + param ( + [parameter(Mandatory = $true)] + [HashTable]$HelpTokens + ) + + try { + + $module = Get-Module -Name PSQualityCheck + + if (Test-Path -Path (Join-Path -Path $module.ModuleBase -ChildPath "Checks\HelpElementRules.psd1")) { + + $helpElementRules = (Import-PowerShellDataFile -Path (Join-Path -Path $module.ModuleBase -ChildPath "Checks\HelpElementRules.psd1")) + + } + else { + + throw "Unable to load Checks\HelpElementRules.psd1" + + } + + $tokenErrors = @() + $helpTokensKeys = @() + + # Create an array of the help element rules elements + for ($order = 1; $order -le $helpElementRules.Count; $order++) { + + $token = $helpElementRules."$order" + + $helpTokensKeys += $token.key + + } + + # search through the found tokens and match them against the rules + foreach ($key in $HelpTokens.Keys) { + + if ( $key -notin $helpTokensKeys ) { + + $tokenErrors += $key + + } + + } + + if ($tokenErrors.Count -ge 1) { + throw "Found extra, non-specified, token(s): $tokenErrors" + } + + } + catch { + + throw $_.Exception.Message + + } + +} diff --git a/Source/PSQualityCheck.Functions/Test-HelpTokensCountIsValid.ps1 b/Source/PSQualityCheck.Functions/Test-HelpTokensCountIsValid.ps1 new file mode 100644 index 0000000..e117540 --- /dev/null +++ b/Source/PSQualityCheck.Functions/Test-HelpTokensCountIsValid.ps1 @@ -0,0 +1,91 @@ +function Test-HelpTokensCountIsValid { + <# + .SYNOPSIS + Check that help tokens count is valid + + .DESCRIPTION + Check that the help tokens count is valid by making sure that they appear between Min and Max times + + .PARAMETER HelpTokens + A string containing the text of the Help Comment + + .EXAMPLE + Test-HelpTokensCountIsValid -HelpComment $helpComment + + .NOTES + This function will only check the Min/Max counts of required help tokens + #> + [CmdletBinding()] + [OutputType([System.Exception], [System.Void])] + param ( + [parameter(Mandatory = $true)] + [HashTable]$HelpTokens + ) + + try { + + $module = Get-Module -Name PSQualityCheck + + if (Test-Path -Path (Join-Path -Path $module.ModuleBase -ChildPath "Checks\HelpElementRules.psd1")) { + + $helpElementRules = (Import-PowerShellDataFile -Path (Join-Path -Path $module.ModuleBase -ChildPath "Checks\HelpElementRules.psd1")) + + } + else { + + throw "Unable to load Checks\HelpElementRules.psd1" + + } + + # create a HashTable for tracking whether the element has been found + $tokenFound = @{} + for ($order = 1; $order -le $helpElementRules.Count; $order++) { + $token = $helpElementRules."$order".Key + $tokenFound[$token] = $false + } + + $tokenErrors = @() + + # loop through all the found tokens + foreach ($key in $HelpTokens.Keys) { + + # loop through all the help element rules + for ($order = 1; $order -le $helpElementRules.Count; $order++) { + + $token = $helpElementRules."$order" + + # if the found token matches against a rule + if ( $token.Key -eq $key ) { + + $tokenFound[$key] = $true + + # if the count is not between min and max AND is required + # that's an error + if ($HelpTokens.$key.Count -lt $token.MinOccurrences -or + $HelpTokens.$key.Count -gt $token.MaxOccurrences -and + $token.Required -eq $true) { + + $tokenErrors += "Found $(($HelpTokens.$key).Count) occurrences of '$key' which is not between $($token.MinOccurrences) and $($token.MaxOccurrences). " + + } + + } + + } + + } + + if ($tokenErrors.Count -ge 1) { + + throw $tokenErrors + + } + + } + catch { + + throw $_.Exception.Message + + } + +} diff --git a/Source/PSQualityCheck.Functions/Test-HelpTokensParamsMatch.ps1 b/Source/PSQualityCheck.Functions/Test-HelpTokensParamsMatch.ps1 new file mode 100644 index 0000000..2f8d0ef --- /dev/null +++ b/Source/PSQualityCheck.Functions/Test-HelpTokensParamsMatch.ps1 @@ -0,0 +1,96 @@ +function Test-HelpTokensParamsMatch { + <# + .SYNOPSIS + Checks to see whether the parameters and help PARAMETER statements match + + .DESCRIPTION + Checks to see whether the parameters in the param block and in the help PARAMETER statements exist in both locations + + .PARAMETER HelpTokens + A string containing the text of the Help Comment + + .PARAMETER ParameterVariables + A object containing the parameters from the param block + + .EXAMPLE + Test-HelpTokensParamsMatch -HelpComment $helpComment -ParameterVariables $ParameterVariables + #> + [CmdletBinding()] + [OutputType([System.Exception], [System.String[]])] + param ( + [parameter(Mandatory = $true)] + [HashTable]$HelpTokens, + [parameter(Mandatory = $true)] + [PSCustomObject]$ParameterVariables + ) + + try { + + $foundInHelpErrors = @() + $foundInParamErrors = @() + + # Loop through each of the parameters from the param block looking for that variable in the PARAMETER help + foreach ($key in $ParameterVariables.Keys) { + + $foundInHelp = $false + + foreach ($token in $HelpTokens.".PARAMETER") { + + if ($key -eq $token.Name) { + + # If we find a match, exit out from the loop + $foundInHelp = $true + break + + } + + } + + if ($foundInHelp -eq $false) { + + $foundInHelpErrors += "Parameter block variable '$key' was not found in help. " + + } + + } + + # Loop through each of the PARAMETER from the help looking for parameters from the param block + foreach ($token in $HelpTokens.".PARAMETER") { + + $foundInParams = $false + + foreach ($key in $ParameterVariables.Keys) { + + if ($key -eq $token.Name) { + + # If we find a match, exit out from the loop + $foundInParams = $true + break + + } + + } + + if ($foundInParams -eq $false) { + + $foundInParamErrors += "Help defined variable '$($token.Name)' was not found in parameter block definition. " + + } + + } + + if ($foundInHelpErrors.Count -ge 1 -or $foundInParamErrors.Count -ge 1) { + + $allErrors = $foundInHelpErrors + $foundInParamErrors + throw $allErrors + + } + + } + catch { + + throw $_.Exception.Message + + } + +} diff --git a/Source/PSQualityCheck.Functions/Test-HelpTokensTextIsValid.ps1 b/Source/PSQualityCheck.Functions/Test-HelpTokensTextIsValid.ps1 new file mode 100644 index 0000000..d45552d --- /dev/null +++ b/Source/PSQualityCheck.Functions/Test-HelpTokensTextIsValid.ps1 @@ -0,0 +1,56 @@ +function Test-HelpTokensTextIsValid { + <# + .SYNOPSIS + Check that Help Tokens text is valid + + .DESCRIPTION + Check that the Help Tokens text is valid by making sure that they its not empty + + .PARAMETER HelpTokens + A string containing the text of the Help Comment + + .EXAMPLE + Test-HelpTokensTextIsValid -HelpComment $helpComment + #> + [CmdletBinding()] + [OutputType([System.Exception], [System.Void])] + param ( + [parameter(Mandatory = $true)] + [HashTable]$HelpTokens + ) + + try { + + # Check that the help blocks aren't empty + foreach ($key in $HelpTokens.Keys) { + + $tokenCount = @($HelpTokens.$key) + + for ($loop = 0; $loop -lt $tokenCount.Count; $loop++) { + + $token = $HelpTokens.$key[$loop] + + if ([string]::IsNullOrWhitespace($token.Text)) { + + $tokenErrors += "Found '$key' does not have any text. " + + } + + } + + } + + if ($tokenErrors.Count -ge 1) { + + throw $tokenErrors + + } + + } + catch { + + throw $_.Exception.Message + + } + +} diff --git a/Source/PSQualityCheck.Functions/Test-ImportModuleIsValid.ps1 b/Source/PSQualityCheck.Functions/Test-ImportModuleIsValid.ps1 new file mode 100644 index 0000000..74cc9b0 --- /dev/null +++ b/Source/PSQualityCheck.Functions/Test-ImportModuleIsValid.ps1 @@ -0,0 +1,70 @@ +function Test-ImportModuleIsValid { + <# + .SYNOPSIS + Test that the Import-Module commands are valid + + .DESCRIPTION + Test that the Import-Module commands contain a -Name parameter, and one of RequiredVersion, MinimumVersion or MaximumVersion + + .PARAMETER ParsedFile + An object containing the source file parsed into its Tokenizer components + + .PARAMETER ImportModuleTokens + An object containing the Import-Module calls found + + .EXAMPLE + TestImportModuleIsValid -ParsedFile $parsedFile + #> + [CmdletBinding()] + [OutputType([System.Exception], [System.Void])] + param( + [parameter(Mandatory = $true)] + [System.Object[]]$ParsedFile, + [parameter(Mandatory = $true)] + [System.Object[]]$ImportModuleTokens + ) + + try { + + $errString = "" + + # loop through each token found looking for the -Name and one of RequiredVersion, MinimumVersion or MaximumVersion + foreach ($token in $importModuleTokens) { + + # Get the full details of the command + $importModuleStatement = Get-TokenComponent -ParsedFileContent $ParsedFile -StartLine $token.StartLine + + # Get the name of the module to be imported (for logging only) + $name = ($importModuleStatement | Where-Object { $_.Type -eq "String" } | Select-Object -First 1).Content + + # if the -Name parameter is not found + if (-not($importModuleStatement | Where-Object { $_.Type -eq "CommandParameter" -and $_.Content -eq "-Name" })) { + + $errString += "Import-Module for '$name' : Missing -Name parameter keyword. " + + } + + # if one of RequiredVersion, MinimumVersion or MaximumVersion is not found + if (-not($importModuleStatement | Where-Object { $_.Type -eq "CommandParameter" -and ( $_.Content -eq "-RequiredVersion" -or $_.Content -eq "-MinimumVersion" -or $_.Content -eq "-MaximumVersion" ) })) { + + $errString += "Import-Module for '$name' : Missing -RequiredVersion, -MinimumVersion or -MaximumVersion parameter keyword. " + + } + + } + + # If there are any problems throw to fail the test + if (-not ([string]::IsNullOrEmpty($errString))) { + + throw $errString + + } + + } + catch { + + throw $_.Exception.Message + + } + +} diff --git a/Source/PSQualityCheck.Functions/Test-ParameterVariablesHaveType.ps1 b/Source/PSQualityCheck.Functions/Test-ParameterVariablesHaveType.ps1 new file mode 100644 index 0000000..4a1c4b7 --- /dev/null +++ b/Source/PSQualityCheck.Functions/Test-ParameterVariablesHaveType.ps1 @@ -0,0 +1,49 @@ +function Test-ParameterVariablesHaveType { + <# + .SYNOPSIS + Check that all the passed parameters have a type variable set. + + .DESCRIPTION + Check that all the passed parameters have a type variable set. + + .PARAMETER ParameterVariables + A HashTable containing the parameters from the param block + + .EXAMPLE + Test-ParameterVariablesHaveType -ParameterVariables $ParameterVariables + #> + [CmdletBinding()] + [OutputType([System.Exception], [System.Void])] + param + ( + [parameter(Mandatory = $true)] + [HashTable]$ParameterVariables + ) + + $variableErrors = @() + + try { + + foreach ($key in $ParameterVariables.Keys) { + + if ([string]::IsNullOrEmpty($ParameterVariables.$key)) { + + $variableErrors += "Parameter '$key' does not have a type defined. " + + } + + } + + if ($variableErrors.Count -ge 1) { + + throw $variableErrors + } + + } + catch { + + throw $_.Exception.Message + + } + +} diff --git a/Source/PSQualityCheck/Invoke-PSQualityCheck.ps1 b/Source/PSQualityCheck/Invoke-PSQualityCheck.ps1 new file mode 100644 index 0000000..0c809e2 --- /dev/null +++ b/Source/PSQualityCheck/Invoke-PSQualityCheck.ps1 @@ -0,0 +1,310 @@ +function Invoke-PSQualityCheck { + <# + .SYNOPSIS + Invoke the PSQualityCheck tests + + .DESCRIPTION + Invoke a series of Pester-based quality tests on the passed files + + .PARAMETER Path + A string array containing paths to check for testable files + + .PARAMETER File + A string array containing testable files + + .PARAMETER SonarQubeRulesPath + A path the the external PSScriptAnalyzer rules for SonarQube + + .PARAMETER ShowCheckResults + Show a summary of the Check results at the end of processing + + .EXAMPLE + Invoke-PSQualityCheck -Path 'C:\Scripts' + + This will call the quality checks on single path + + .EXAMPLE + Invoke-PSQualityCheck -Path @('C:\Scripts', 'C:\MoreScripts') + + This will call the quality checks with multiple paths + + .EXAMPLE + Invoke-PSQualityCheck -File 'C:\Scripts\Script.ps1' + + This will call the quality checks with single script file + + .EXAMPLE + Invoke-PSQualityCheck -File 'C:\Scripts\Script.psm1' + + This will call the quality checks with single module file + + .EXAMPLE + Invoke-PSQualityCheck -File 'C:\Scripts\Script.psd1' + + This will call the quality checks with single datafile file + Note: The datafile test will fail as it is not a file that is accepted for testing + + .EXAMPLE + Invoke-PSQualityCheck -File @('C:\Scripts\Script.ps1','C:\Scripts\Script2.ps1') + + This will call the quality checks with multiple files. Files can be either scripts or modules + + .EXAMPLE + Invoke-PSQualityCheck -File 'C:\Scripts\Script.ps1' -SonarQubeRulesPath 'C:\SonarQubeRules' + + This will call the quality checks with single file and the extra PSScriptAnalyzer rules used by SonarQube + + .EXAMPLE + Invoke-PSQualityCheck -Path 'C:\Scripts' -ShowCheckResults + + This will display a summary of the checks performed (example below uses sample data): + + Name Files Tested Total Passed Failed Skipped + ---- ------------ ----- ------ ------ ------- + Module Tests 2 14 14 0 0 + Extracting functions 2 2 2 0 0 + Extracted function script tests 22 330 309 0 21 + Total 24 346 325 0 21 + + For those who have spotted that the Total files tested isn't a total of the rows above, this is because the Module Tests and Extracting function Tests operate on the same file and are then not counted twice + + .LINK + Website: https://github.com/andrewrdavidson/PSQualityCheck + SonarQube rules are available here: https://github.com/indented-automation/ScriptAnalyzerRules + + #> + [CmdletBinding()] + [OutputType([System.Void], [HashTable])] + param ( + [Parameter(Mandatory = $true, ParameterSetName = "Path")] + [String[]]$Path, + [Parameter(Mandatory = $true, ParameterSetName = "File")] + [String[]]$File, + + [Parameter(Mandatory = $false)] + [String]$SonarQubeRulesPath, + + [switch]$ShowCheckResults + ) + + Set-StrictMode -Version Latest + + # External Modules + Import-Module -Name "Pester" -MinimumVersion "5.1.0" -Force + Import-Module -Name "PSScriptAnalyzer" -MinimumVersion "1.19.1" -Force + + $modulePath = (Get-Module -Name PSQualityCheck).ModuleBase + + # Analyse the incoming Path and File parameters and produce a list of Modules and Scripts + + $scriptsToTest = @() + $modulesToTest = @() + + if ($PSBoundParameters.ContainsKey('Path')) { + + if ($Path -isnot [string[]]) { + $Path = @($Path) + } + + foreach ($item in $Path) { + + # Test whether the item is a directory (also tells us if it exists) + if (Test-Path -Path $item -PathType Container) { + + $scriptsToTest += Get-FileList -Path $item -Extension ".ps1" + $modulesToTest += Get-FileList -Path $item -Extension ".psm1" + + } + else { + + Write-Warning -Message "$item is not a directory, skipping" + + } + + } + + } + + if ($PSBoundParameters.ContainsKey('File')) { + + if ($File -isnot [string[]]) { + $File = @($File) + } + + foreach ($item in $File) { + + # Test whether the item is a file (also tells us if it exists) + if (Test-Path -Path $item -PathType Leaf) { + + $itemProperties = Get-ChildItem -Path $item + + switch ($itemProperties.Extension) { + + '.psm1' { + $modulesToTest += $itemProperties + } + + '.ps1' { + $scriptsToTest += $itemProperties + } + + } + + } + else { + + Write-Warning -Message "$item is not a file, skipping" + + } + + } + + } + + # Default Pester Parameters + $configuration = [PesterConfiguration]::Default + $configuration.Run.Exit = $false + $configuration.CodeCoverage.Enabled = $false + $configuration.Output.Verbosity = "Detailed" + $configuration.Run.PassThru = $true + $configuration.Should.ErrorAction = 'Stop' + + $moduleResults = $null + $extractionResults = $null + $extractedScriptResults = $null + $scriptResults = $null + + if ($modulesToTest.Count -ge 1) { + + # Location of files extracted from any passed modules + $functionExtractPath = Join-Path -Path $Env:TEMP -ChildPath (New-Guid).Guid + + # Run the Module tests on all the valid module files found + $container1 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath "Checks\Module.Tests.ps1") -Data @{ Source = $modulesToTest } + $configuration.Run.Container = $container1 + $moduleResults = Invoke-Pester -Configuration $configuration + + # Extract all the functions from the modules into individual .ps1 files ready for testing + $container2 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath "Checks\Function-Extraction.Tests.ps1") -Data @{ Source = $modulesToTest; FunctionExtractPath = $functionExtractPath } + $configuration.Run.Container = $container2 + $extractionResults = Invoke-Pester -Configuration $configuration + + # Get a list of the 'extracted' function scripts .ps1 files + $extractedScriptsToTest = Get-ChildItem -Path $functionExtractPath -Include '*.ps1' -Recurse + + # Run the Script tests against all the extracted functions .ps1 files + $container3 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath "Checks\Script.Tests.ps1") -Data @{ Source = $extractedScriptsToTest; SonarQubeRules = $SonarQubeRulesPath } + $configuration.Run.Container = $container3 + $extractedScriptResults = Invoke-Pester -Configuration $configuration + + } + + if ($scriptsToTest.Count -ge 1) { + + # Run the Script tests against all the valid script files found + $container3 = New-PesterContainer -Path (Join-Path -Path $modulePath -ChildPath "Checks\Script.Tests.ps1") -Data @{ Source = $scriptsToTest; SonarQubeRules = $SonarQubeRulesPath } + $configuration.Run.Container = $container3 + $scriptResults = Invoke-Pester -Configuration $configuration + + } + + if ($PSBoundParameters.ContainsKey('ShowCheckResults')) { + + $qualityCheckResults = @() + $filesTested = $total = $passed = $failed = $skipped = 0 + + if ($null -ne $moduleResults) { + $qualityCheckResults += + @{ + 'Test' = 'Module Tests' + 'Files Tested' = $ModulesToTest.Count + 'Total' = $moduleResults.TotalCount + 'Passed' = $moduleResults.PassedCount + 'Failed' = $moduleResults.FailedCount + 'Skipped' = $moduleResults.SkippedCount + } + $filesTested += $ModulesToTest.Count + $total += $moduleResults.TotalCount + $passed += $moduleResults.PassedCount + $failed += $moduleResults.FailedCount + $skipped += $moduleResults.SkippedCount + } + + if ($null -ne $extractionResults) { + $qualityCheckResults += + @{ + 'Test' = 'Extracting functions' + 'Files Tested' = $ModulesToTest.Count + 'Total' = $extractionResults.TotalCount + 'Passed' = $extractionResults.PassedCount + 'Failed' = $extractionResults.FailedCount + 'Skipped' = $extractionResults.SkippedCount + } + $total += $extractionResults.TotalCount + $passed += $extractionResults.PassedCount + $failed += $extractionResults.FailedCount + $skipped += $extractionResults.SkippedCount + } + + if ($null -ne $extractedScriptResults) { + $qualityCheckResults += + @{ + 'Test' = 'Extracted function script tests' + 'Files Tested' = $extractedScriptsToTest.Count + 'Total' = $extractedScriptResults.TotalCount + 'Passed' = $extractedScriptResults.PassedCount + 'Failed' = $extractedScriptResults.FailedCount + 'Skipped' = $extractedScriptResults.SkippedCount + } + $filesTested += $extractedScriptsToTest.Count + $total += $extractedScriptResults.TotalCount + $passed += $extractedScriptResults.PassedCount + $failed += $extractedScriptResults.FailedCount + $skipped += $extractedScriptResults.SkippedCount + } + + if ($null -ne $scriptResults) { + $qualityCheckResults += + @{ + 'Test' = "Script Tests" + 'Files Tested' = $scriptsToTest.Count + 'Total' = $scriptResults.TotalCount + 'Passed' = $scriptResults.PassedCount + 'Failed' = $scriptResults.FailedCount + 'Skipped' = $scriptResults.SkippedCount + } + $filesTested += $scriptsToTest.Count + $total += $scriptResults.TotalCount + $passed += $scriptResults.PassedCount + $failed += $scriptResults.FailedCount + $skipped += $scriptResults.SkippedCount + } + + $qualityCheckResults += + @{ + 'Test' = "Total" + 'Files Tested' = $filesTested + 'Total' = $total + 'Passed' = $passed + 'Failed' = $failed + 'Skipped' = $skipped + } + + # This works on PS5 + $qualityCheckResults | ForEach-Object { + [PSCustomObject]@{ + 'Test' = $_.Test + 'Files Tested' = $_.'Files Tested' + 'Total' = $_.total + 'Passed' = $_.passed + 'Failed' = $_.failed + 'Skipped' = $_.skipped + } + } | Format-Table -AutoSize + + # This works on PS7 not on PS5 + # $qualityCheckResults | Select-Object Name, 'Files Tested', Total, Passed, Failed, Skipped | Format-Table -AutoSize + + } + +} diff --git a/Source/TestFiles/Test.Module.psd1 b/Source/TestFiles/Test.Module.psd1 new file mode 100644 index 0000000..558f12a --- /dev/null +++ b/Source/TestFiles/Test.Module.psd1 @@ -0,0 +1,19 @@ +@{ + + RootModule = 'Test.Module.psm1' + ModuleVersion = '1.0.0' + CompatiblePSEditions = @("Core") + GUID = 'a27f1ea3-051a-40dd-9698-9794c3989665' + Author = 'github@kelrys.com' + CompanyName = 'n/a' + Copyright = '(c) github@kelrys.com. All rights reserved.' + PowerShellVersion = "7.0" + FunctionsToExport = @('One', 'Two') + CmdletsToExport = @() + VariablesToExport = '*' + AliasesToExport = @() + PrivateData = @{ + PSData = @{ + } + } +} diff --git a/Source/TestFiles/Test.Module.psm1 b/Source/TestFiles/Test.Module.psm1 new file mode 100644 index 0000000..8f5d9bf --- /dev/null +++ b/Source/TestFiles/Test.Module.psm1 @@ -0,0 +1,45 @@ +function One { + <# + .SYNOPSIS + Test function that does nothing + + .DESCRIPTION + Test function that does nothing but contains all the required elements to pass the checks + + .EXAMPLE + One + #> + [CmdletBinding()] + [OutputType([System.Void])] + param( + ) + + # any valid function commands here, must not be empty + Write-Output "Test Message" + +} + +function Two { + <# + .SYNOPSIS + Test function that does nothing + + .DESCRIPTION + Test function that does nothing but contains all the required elements to pass the checks + + .PARAMETER ThisParameter + Test function that does nothing but contains all the required elements to pass the checks + + .EXAMPLE + Two + #> + [CmdletBinding()] + [OutputType([System.Void])] + param( + [string]$ThisParameter + ) + + # any valid function commands here, must not be empty + Write-Output $ThisParameter + +} diff --git a/Source/TestFiles/TestScript.ps1 b/Source/TestFiles/TestScript.ps1 new file mode 100644 index 0000000..86a3420 --- /dev/null +++ b/Source/TestFiles/TestScript.ps1 @@ -0,0 +1,105 @@ +<# +.SYNOPSIS + +Adds a file name extension to a supplied name. + +.DESCRIPTION + +Adds a file name extension to a supplied name. +Takes any strings for the file name or extension. + +.PARAMETER Message + +A string containing the message + +.INPUTS + +None. You cannot pipe objects to Add-Extension. + +.OUTPUTS + +System.String. Add-Extension returns a string with the extension +or file name. + +.EXAMPLE + +PS> extension -name "File" +File.txt + +.EXAMPLE + +PS> extension -name "File" -extension "doc" +File.doc + +.EXAMPLE + +PS> extension "File" "doc" +File.doc + +.LINK + +http://www.fabrikam.com/extension.html + +.LINK + +Set-Item + +#> + +<# +.DESCRIPTION +Description of TestScript + +.SYNOPSIS +A detailed synopsis of the function of the script + +.PARAMETER Message +A string containing the message + +.EXAMPLE +Get-TestFunction -Message 'This is a test message' + +.INPUTS +Get-TestFunction -Message 'This is a test message' + +.OUTPUTS +Get-TestFunction -Message 'This is a test message' + +.NOTES +Get-TestFunction -Message 'This is a test message' + +.LINK +Get-TestFunction -Message 'This is a test message' + + +#> + +[CmdletBinding()] +[OutputType([System.Void])] +# Should trip [OutputType] empty test +# [OutputType()] +param ( + [string]$Message = "Test Message" +) + +# Should trip Import-Module Test +# Import-Module "test1.psm1" +# Import-Module "test2.psd1" +# Import-Module -Name "test3.psd1" + +# Should pass Import-Module Test +Import-Module -Name "test4" -RequiredVersion "1.0.0" +Import-Module -Name test5 -MinimumVersion "1.0.0" +Import-Module -Name test6 -MaximumVersion "1.0.0" +# Import-Module -Name "test7.psd1" -RequiredVersion "1.0.0" +# Import-Module -Name "test8.psd1" -MinimumVersion "1.0.0" +# Import-Module -Name "test9.psd1" -MaximumVersion "1.0.0" +# Import-Module -Name "testA.psd1" -RequiredVersion 1.0.0 +# Import-Module -Name "testB.psd1" -MinimumVersion 1.0.0 +# Import-Module -Name "testC.psd1" -MaximumVersion 1.0.0 + +# Shouldn't trip anything +Write-Output $Message + +# should trip PSScriptAnalyzer test +# Write-Host "String"